Troubleshooting
Common questions and solutions when working with nestjs-odata.
My entity doesn't show in $metadata
Symptoms: The entity type and entity set are missing from GET /odata/$metadata and the service document.
Checklist:
- The entity class has the
@Entity()decorator from TypeORM - The entity is included in the
ODataTypeOrmModule.forFeature()array:typescriptODataTypeOrmModule.forFeature([Product]) - A controller exists with
@ODataController('Products')referencing the entity set name - The controller is listed in
ODataModule.forRoot({ controllers: [...] }) - The entity is also registered with TypeORM (in
TypeOrmModule.forRoot({ entities: [...] })or via auto-loading)
If any of these are missing, the entity will not appear in the EDM.
DI error for TypeOrmAutoHandler
Error: Nest can't resolve dependencies of the TypeOrmAutoHandler
Cause: ODataTypeOrmModule.forFeature() is not imported in the module where you're injecting TypeOrmAutoHandler.
Fix: Make sure your module imports both the TypeORM module and the OData TypeORM module:
@Module({
imports: [
TypeOrmModule.forRoot({
/* ... */
}),
ODataModule.forRoot({
serviceRoot: '/odata',
controllers: [ProductsController],
}),
ODataTypeOrmModule.forFeature([Product]),
],
})
export class AppModule {}TypeOrmAutoHandler is automatically registered as a provider by forFeature() — do not add it to a providers array manually.
$filter returns 400 Bad Request
Symptoms: Queries with $filter return a 400 error.
Checklist:
Check filter syntax. OData filter expressions follow a specific grammar. Common mistakes:
- Missing spaces around operators:
price lt 100(correct) vsprice<100(wrong) - Wrong string quoting: use single quotes for strings:
name eq 'Widget' - Case-sensitive operators:
eq,ne,lt,gt,le,ge(lowercase)
- Missing spaces around operators:
Check
maxFilterDepth. If your filter has nestedand/orclauses, it may exceed the configured depth limit:typescriptODataModule.forRoot({ serviceRoot: '/odata', maxFilterDepth: 10, // Increase if needed (default: 5) })Check supported functions. Not all OData filter functions may be implemented. See Filter Functions for the full list of supported functions.
$expand returns empty array
Symptoms: $expand=category returns entities but the expanded property is null or an empty array.
Checklist:
Check TypeORM relation decorators. The entity must have a proper relation defined:
typescript@ManyToOne(() => Category, (category) => category.products) category: Category @OneToMany(() => Product, (product) => product.category) products: Product[]Check
maxExpandDepth. Deep expansions may be rejected:typescriptODataModule.forRoot({ serviceRoot: '/odata', maxExpandDepth: 3, // Increase if needed (default: 2) })Check that related data exists. If the database has no related records, the expansion will legitimately return
nullor[].
OData routes conflict with my REST routes
Symptoms: OData endpoints interfere with regular NestJS REST routes, or vice versa.
Solutions:
Use a distinct service root. The
serviceRootoption prefixes all OData routes:typescriptODataModule.forRoot({ serviceRoot: '/odata', // All OData routes under /odata/... })Use the
pathoption in@ODataController. This lets you control the exact route prefix for a specific controller.Order matters. NestJS resolves routes in registration order. If a wildcard REST route matches before the OData route, move the OData module import earlier.
$count returns unexpected number
Symptoms: The @odata.count value in the response doesn't match the number of items in value.
Explanation: This is correct behavior. $count=true returns the total number of entities matching the filter, regardless of $top and $skip. The value array contains only the current page.
# 1000 total products, but only 10 returned in this page
curl 'http://localhost:3000/odata/Products?$top=10&$count=true'{
"@odata.context": "...",
"@odata.count": 1000,
"value": [
/* 10 items */
]
}How do I use composite keys?
OData supports entities with multiple key properties. Decorate each key column with @PrimaryColumn() in TypeORM:
@Entity()
export class OrderItem {
@PrimaryColumn()
orderId: number
@PrimaryColumn()
productId: number
@Column()
quantity: number
}Both properties will appear as <PropertyRef> elements inside the <Key> block in $metadata:
<EntityType Name="OrderItem">
<Key>
<PropertyRef Name="orderId"/>
<PropertyRef Name="productId"/>
</Key>
...
</EntityType>To query by composite key, use the OData multi-key syntax:
curl 'http://localhost:3000/odata/OrderItems(orderId=1,productId=42)'TypeORM synchronize warning in production
Symptoms: Console warning about synchronize: true in production.
Fix: Never use synchronize: true in production. It can cause data loss by auto-altering tables. Use TypeORM migrations instead:
TypeOrmModule.forRoot({
type: 'postgres',
synchronize: false, // Always false in production
migrations: ['dist/migrations/*.js'],
migrationsRun: true,
})Still stuck?
- Check the GitHub Issues for known problems
- Open a new issue with a minimal reproduction
- Review the Configuration page for all available options