OData V4 Server for Node.js
odata4-server is an abandoned Node.js library for creating OData V4 compliant servers, originally forked from the equally unmaintained `jaystack/odata-v4-server`. It provides a decorator-driven approach to define OData controllers and expose service metadata (`$metadata`), supporting a wide range of OData query language features like filtering (`$filter`), sorting (`$orderby`), paging (`$skip`, `$top`), projection (`$select`), and expansion (`$expand`). It can operate as a standalone server, an Express router, or a Node.js stream. The package is currently at version 0.3.0, with its last update more than seven years ago (as of April 2026), indicating it is no longer actively maintained. Due to its abandoned status, it's not recommended for new projects or production use.
Common errors
-
ReferenceError: ODataServer is not defined
cause Attempting to use CommonJS `require` syntax for a module primarily designed for ES Modules or TypeScript imports, or incorrect destructuring.fixEnsure you are using `import { ODataServer } from 'odata4-server';` at the top of your file, and that your project is configured for ES Modules or TypeScript compilation. -
TypeError: Decorators are not valid here.
cause The TypeScript compiler is not configured to process decorators, or they are being used in a JavaScript file without transpilation.fixIn your `tsconfig.json`, ensure `compilerOptions.experimentalDecorators` and `compilerOptions.emitDecoratorMetadata` are both set to `true`. If using JavaScript, ensure a transpiler like Babel is configured with decorator support. -
Cannot GET /odata/$metadata (or other OData endpoints)
cause The OData server instance was not correctly created or is not listening on the expected port/path, or the port is already in use.fixVerify `NorthwindODataServer.create('/odata', 3000);` is called. Check console for server startup messages and port conflicts. Ensure you are accessing the correct path (e.g., `http://localhost:3000/odata/$metadata`).
Warnings
- breaking The `odata4-server` package is abandoned, with its last release (v0.3.0) over seven years ago. This means no new features, bug fixes, or security patches will be provided. It is not suitable for new projects or production environments.
- breaking Due to its abandonment, `odata4-server` may not be compatible with newer versions of Node.js (e.g., v16, v18, v20+) or updated OData V4 specifications. Breaking changes in Node.js runtime or dependency updates could cause unexpected behavior or crashes.
- gotcha Using TypeScript decorators (like `@odata.GET`, `@odata.controller`) requires specific compiler options in your `tsconfig.json`, namely `"experimentalDecorators": true` and `"emitDecoratorMetadata": true`.
- gotcha As an abandoned project, `odata4-server` is highly susceptible to unpatched security vulnerabilities. Any issues discovered in its codebase or its underlying dependencies will not be addressed, posing a significant security risk for applications using it.
Install
-
npm install odata4-server -
yarn add odata4-server -
pnpm add odata4-server
Imports
- ODataServer
const { ODataServer } = require('odata4-server')import { ODataServer } from 'odata4-server' - ODataController
const { ODataController } = require('odata4-server')import { ODataController } from 'odata4-server' - odata
import * as odata from 'odata4-server'
Quickstart
import { createFilter, ODataController, ODataServer, ODataQuery } from 'odata4-server';
import * as odata from 'odata4-server';
// Mock data for demonstration
interface Product { _id: string; name: string; categoryId: string; }
interface Category { _id: string; name: string; }
const products: Product[] = [
{ _id: '1', name: 'Apples', categoryId: 'cat1' },
{ _id: '2', name: 'Bananas', categoryId: 'cat1' },
{ _id: '3', name: 'Carrots', categoryId: 'cat2' }
];
const categories: Category[] = [
{ _id: 'cat1', name: 'Fruits' },
{ _id: 'cat2', name: 'Vegetables' }
];
// Simple unique ID generator (replace with proper DB IDs in real app)
let nextId = 10;
const generateId = () => (nextId++).toString();
@odata.controller(products, 'Products')
export class ProductsController extends ODataController{
@odata.GET
find(@odata.filter filter: ODataQuery) {
console.log('GET /Products', filter);
if (filter) return products.filter(createFilter(filter));
return products;
}
@odata.GET
findOne(@odata.key key: string) {
console.log(`GET /Products(${key})`);
return products.filter(product => product._id === key)[0];
}
@odata.POST
insert(@odata.body product: any) {
console.log('POST /Products', product);
product._id = generateId();
products.push(product);
return product;
}
@odata.PATCH
update(@odata.key key: string, @odata.body delta: any) {
console.log(`PATCH /Products(${key})`, delta);
let product = products.filter(product => product._id === key)[0];
if (product) {
for (let prop in delta) {
if (Object.prototype.hasOwnProperty.call(delta, prop)) {
(product as any)[prop] = delta[prop];
}
}
}
}
@odata.DELETE
remove(@odata.key key: string) {
console.log(`DELETE /Products(${key})`);
const index = products.findIndex(product => product._id === key);
if (index > -1) {
products.splice(index, 1);
}
}
}
@odata.controller(categories, 'Categories')
export class CategoriesController extends ODataController{
@odata.GET
find(@odata.filter filter:ODataQuery) {
console.log('GET /Categories', filter);
if (filter) return categories.filter(createFilter(filter));
return categories;
}
@odata.GET
findOne(@odata.key key:string) {
console.log(`GET /Categories(${key})`);
return categories.filter(category => category._id === key)[0];
}
@odata.POST
insert(@odata.body category:any) {
console.log('POST /Categories', category);
category._id = generateId();
categories.push(category);
return category;
}
@odata.PATCH
update(@odata.key key:string, @odata.body delta:any) {
console.log(`PATCH /Categories(${key})`, delta);
let category = categories.filter(category => category._id === key)[0];
if (category) {
for (let prop in delta) {
if (Object.prototype.hasOwnProperty.call(delta, prop)) {
(category as any)[prop] = delta[prop];
}
}
}
}
@odata.DELETE
remove(@odata.key key:string) {
console.log(`DELETE /Categories(${key})`);
const index = categories.findIndex(category => category._id === key);
if (index > -1) {
categories.splice(index, 1);
}
}
}
@odata.cors
@odata.controller(ProductsController, true)
@odata.controller(CategoriesController, true)
export class NorthwindODataServer extends ODataServer{}
const PORT = process.env.PORT || 3000;
NorthwindODataServer.create('/odata', PORT);
console.log(`OData server listening on http://localhost:${PORT}/odata`);
console.log(`Access metadata at http://localhost:${PORT}/odata/$metadata`);
console.log(`Try: http://localhost:${PORT}/odata/Products?$filter=name eq 'Apples'`);
console.log(`Try: http://localhost:${PORT}/odata/Categories('cat1')`);