OData V4 Server for Node.js
odata-v4-server is a Node.js library for building OData Version 4.0 compliant servers. It allows developers to expose OData services either as a standalone server, an Express router, or as a stream. The package facilitates defining service metadata using TypeScript decorators or a JSON schema, supporting standard OData features like query language parsing ($filter, $orderby, $top, $skip, $select, $expand, $count) and CRUD operations on entity sets. It also supports complex types, navigation properties, actions, and functions. Currently at version 0.2.13, its release cadence appears to be irregular as it's still in pre-1.0 development. A key differentiator is its extensive use of decorators for defining controllers and handling OData query parameters, streamlining the development of data services.
Common errors
-
ReferenceError: Reflect is not defined
cause Missing `reflect-metadata` polyfill or incorrect `tsconfig.json` settings for decorators.fixInstall `reflect-metadata` (`npm i reflect-metadata`) and import it once at the entry point of your application (`import 'reflect-metadata';`). Also, ensure `"emitDecoratorMetadata": true` is set in `tsconfig.json`. -
error TS2307: Cannot find module 'odata-v4-server' or its corresponding type declarations.
cause Package not installed, or TypeScript cannot find its declaration files.fixEnsure `odata-v4-server` is installed (`npm i odata-v4-server`). If using an older TypeScript version or complex project setup, verify `node_modules/@types/odata-v4-server` (if it existed) or `node_modules/odata-v4-server/dist/index.d.ts` is correctly resolved by your `tsconfig.json`. -
Error: Decorators are not enabled. You must enable them in your TypeScript configuration (experimentalDecorators: true).
cause `experimentalDecorators` is not enabled in `tsconfig.json`.fixAdd `"experimentalDecorators": true` to the `"compilerOptions"` section of your `tsconfig.json`. -
TypeError: Cannot read properties of undefined (reading 'create')
cause Trying to call `ODataServer.create` without ensuring `NorthwindODataServer` extends `ODataServer` and is correctly decorated, or a transpilation issue.fixVerify that your server class properly `extends ODataServer` and that all decorators (`@odata.controller`, `@odata.cors`) are applied and transpiled correctly. Ensure `reflect-metadata` is imported at the top of your entry file.
Warnings
- gotcha The package is still in pre-1.0 development (version 0.2.13), meaning API surface and internal implementations might change frequently with minor version bumps. Backward compatibility is not guaranteed across minor versions.
- breaking This library heavily relies on TypeScript decorators. If you are using JavaScript, you will need a transpiler (like Babel) with decorator support. For TypeScript, `"experimentalDecorators": true` and `"emitDecoratorMetadata": true` must be enabled in `tsconfig.json`.
- gotcha The example code often uses `ObjectID` from `mongodb` or similar for key generation. When integrating with a specific database, ensure proper ID generation and persistence logic is implemented in your controller methods, replacing the dummy data logic.
- gotcha OData query parsing (e.g., for `$filter`) hands over a parsed `ODataQuery` object to the controller. Developers are responsible for implementing the logic to translate this parsed query into database queries or filtering operations. The example provides only a simplified placeholder.
Install
-
npm install odata-v4-server -
yarn add odata-v4-server -
pnpm add odata-v4-server
Imports
- ODataServer
const { ODataServer } = require('odata-v4-server');import { ODataServer } from 'odata-v4-server'; - ODataController
import ODataController from 'odata-v4-server';
import { ODataController } from 'odata-v4-server'; - odata
import { odata } from 'odata-v4-server';import * as odata from 'odata-v4-server';
- ODataQuery
import { ODataQuery } from 'odata-v4-server';
Quickstart
import { ODataController, ODataServer, odata, ODataQuery } from 'odata-v4-server';
import { MongoClient, ObjectId } from 'mongodb'; // Assuming MongoDB for example persistence
// Dummy data for demonstration
const products: any[] = [];
const categories: any[] = [];
// Helper to create a filter function (simplified for example)
const createFilter = (filter: ODataQuery) => (item: any) => {
// In a real application, you'd parse ODataQuery to build a robust filter predicate.
// This is a placeholder.
console.warn('Filter parsing in createFilter is simplified for demonstration.');
return true;
};
export class ProductsController extends ODataController {
@odata.GET
find(@odata.filter filter: ODataQuery) {
if (filter) return products.filter(createFilter(filter));
return products;
}
@odata.GET
findOne(@odata.key key: string) {
return products.find(product => product._id === key);
}
@odata.POST
insert(@odata.body product: any) {
product._id = new ObjectId().toHexString(); // Simulate MongoDB ID
products.push(product);
return product;
}
@odata.PATCH
update(@odata.key key: string, @odata.body delta: any) {
let product = products.find(product => product._id === key);
if (product) {
Object.assign(product, delta);
}
}
@odata.DELETE
remove(@odata.key key: string) {
const index = products.findIndex(product => product._id === key);
if (index > -1) {
products.splice(index, 1);
}
}
}
export class CategoriesController extends ODataController {
@odata.GET
find(@odata.filter filter: ODataQuery) {
if (filter) return categories.filter(createFilter(filter));
return categories;
}
@odata.GET
findOne(@odata.key key: string) {
return categories.find(category => category._id === key);
}
@odata.POST
insert(@odata.body category: any) {
category._id = new ObjectId().toHexString();
categories.push(category);
return category;
}
@odata.PATCH
update(@odata.key key: string, @odata.body delta: any) {
let category = categories.find(category => category._id === key);
if (category) {
Object.assign(category, delta);
}
}
@odata.DELETE
remove(@odata.key key: string) {
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 {}
// Initialize some dummy data
products.push({ _id: new ObjectId().toHexString(), name: 'Product A', price: 10, categoryId: 'cat1' });
categories.push({ _id: 'cat1', name: 'Category X' });
console.log('Starting OData server on port 3000 at /odata...');
NorthwindODataServer.create('/odata', 3000).then(() => {
console.log('OData server started. Try accessing:');
console.log(' http://localhost:3000/odata');
console.log(' http://localhost:3000/odata/$metadata');
console.log(' http://localhost:3000/odata/Products');
console.log(' http://localhost:3000/odata/Products(\'<product_id>\')');
}).catch(err => console.error('Failed to start OData server:', err));