Overview
The @Override decorator is a method decorator that allows you to replace the auto-generated CRUD routes with your own custom implementations. This is useful when you need to add custom business logic, validation, or modify the default behavior of a CRUD operation.
Signature
export const Override = (name?: BaseRouteName) => (
target,
key,
descriptor: PropertyDescriptor,
) => PropertyDescriptor
Parameters
The name of the base route to override. If not provided, the decorator infers the route name from the method name by appending Base.Possible values:
getManyBase - Override the get many route
getOneBase - Override the get one route
createOneBase - Override the create one route
createManyBase - Override the create many route
updateOneBase - Override the update one route
replaceOneBase - Override the replace one route
deleteOneBase - Override the delete one route
recoverOneBase - Override the recover one route
Usage
Implicit Route Name
When you don’t specify a route name, the decorator automatically infers it from your method name:
import { Controller } from '@nestjs/common';
import { Crud, Override, ParsedRequest, CrudRequest } from '@nestjsx/crud';
import { User } from './user.entity';
import { UserService } from './user.service';
@Crud({
model: { type: User },
})
@Controller('users')
export class UserController {
constructor(public service: UserService) {}
@Override()
getMany(@ParsedRequest() req: CrudRequest) {
// Custom implementation for getManyBase
return { custom: 'response', data: [] };
}
}
The method name getMany automatically maps to overriding getManyBase.
Explicit Route Name
You can explicitly specify which route to override:
@Crud({
model: { type: User },
})
@Controller('users')
export class UserController {
constructor(public service: UserService) {}
@Override('createManyBase')
createBulk(
@ParsedBody() dto: CreateManyDto<User>,
@ParsedRequest() req: CrudRequest
) {
// Custom implementation for bulk create
console.log('Creating multiple users:', dto.bulk.length);
return this.service.createMany(req, dto);
}
}
Adding Business Logic
import { CrudController } from '@nestjsx/crud';
@Crud({
model: { type: Post },
})
@Controller('posts')
export class PostController implements CrudController<Post> {
constructor(public service: PostService) {}
get base(): CrudController<Post> {
return this;
}
@Override()
async createOne(
@ParsedRequest() req: CrudRequest,
@ParsedBody() dto: Post
) {
// Add custom business logic
if (dto.content.includes('spam')) {
throw new BadRequestException('Spam content detected');
}
// Generate slug from title
dto.slug = dto.title.toLowerCase().replace(/\s+/g, '-');
// Call the base implementation
return this.base.createOneBase(req, dto);
}
}
Modifying Response
@Crud({
model: { type: Product },
})
@Controller('products')
export class ProductController {
constructor(public service: ProductService) {}
@Override('getOneBase')
async getProduct(
@Param('id') id: string,
@ParsedRequest() req: CrudRequest
) {
const product = await this.service.findOne(id);
// Add computed fields
return {
...product,
discountedPrice: product.price * 0.9,
inStock: product.quantity > 0,
};
}
}
Using with Authentication
@Crud({
model: { type: Document },
})
@CrudAuth({
property: 'user',
filter: (user) => ({ userId: user.id }),
})
@Controller('documents')
export class DocumentController implements CrudController<Document> {
constructor(public service: DocumentService) {}
get base(): CrudController<Document> {
return this;
}
@Override()
async deleteOne(
@ParsedRequest() req: CrudRequest,
@Request() request
) {
const user = request.user;
const documentId = req.parsed.paramsFilter[0].value;
// Check if user has permission to delete
const document = await this.service.findOne(documentId);
if (document.userId !== user.id && !user.isAdmin) {
throw new ForbiddenException('You cannot delete this document');
}
return this.base.deleteOneBase(req);
}
}
Complete Integration Test Example
From the source tests:
packages/crud/test/crud.decorator.override.spec.ts:25-56
@Crud({
model: { type: TestModel },
params: {
enumField: {
field: 'enum_field',
type: 'string',
enum: Field,
},
disabledField: {
field: 'disabled_field',
type: 'number',
disabled: true,
},
},
})
@Controller('test')
class TestController implements CrudController<TestModel> {
constructor(public service: TestService<TestModel>) {}
get base(): CrudController<TestModel> {
return this;
}
@Override()
getMany(@ParsedRequest() req: CrudRequest) {
return { foo: 'bar' };
}
@Override('createManyBase')
createBulk(@ParsedBody() dto: CreateManyDto<TestModel>, @ParsedRequest() req: CrudRequest) {
return this.base.createManyBase(req, dto);
}
}
Implementation Details
The decorator stores metadata about the override:
packages/crud/src/decorators/override.decorator.ts:4-11
export const Override = (name?: BaseRouteName) => (
target,
key,
descriptor: PropertyDescriptor,
) => {
Reflect.defineMetadata(OVERRIDE_METHOD_METADATA, name || `${key}Base`, target[key]);
return descriptor;
};
If no name is provided, the decorator uses the method name and appends Base to determine which route to override.
When you override a route, you still have access to the base implementation through the base property if you implement the CrudController interface. This allows you to add logic before or after the base implementation.
Overridden methods must have the same route path and HTTP method as the base route they’re replacing. The framework automatically applies the correct routing metadata.
Tips
- Use
@ParsedRequest() to access the parsed CRUD request with filters, pagination, and joins
- Use
@ParsedBody() to access the validated and transformed request body
- Implement
CrudController<T> to get access to the base methods via this.base
- Validation still applies - DTOs are validated even in overridden routes
- Swagger documentation is automatically generated for overridden routes
See Also