Overview
The CrudService is an abstract class that defines the interface for all CRUD service implementations. It provides utility methods for pagination, error handling, and common operations that are shared across different ORM implementations.
Abstract Class Definition
export abstract class CrudService<T> {
// Utility methods
throwBadRequestException(msg?: unknown): BadRequestException
throwNotFoundException(name: string): NotFoundException
createPageInfo(data: T[], total: number, limit: number, offset: number): GetManyDefaultResponse<T>
decidePagination(parsed: ParsedRequestParams, options: CrudRequestOptions): boolean
getTake(query: ParsedRequestParams, options: QueryOptions): number | null
getSkip(query: ParsedRequestParams, take: number): number | null
getPrimaryParams(options: CrudRequestOptions): string[]
// Abstract methods to be implemented
abstract getMany(req: CrudRequest): Promise<GetManyDefaultResponse<T> | T[]>
abstract getOne(req: CrudRequest): Promise<T>
abstract createOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
abstract createMany(req: CrudRequest, dto: CreateManyDto): Promise<T[]>
abstract updateOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
abstract replaceOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
abstract deleteOne(req: CrudRequest): Promise<void | T>
abstract recoverOne(req: CrudRequest): Promise<void | T>
}
Utility Methods
throwBadRequestException
Throws a standardized BadRequestException with an optional message.
throwBadRequestException(msg?: unknown): BadRequestException
Optional error message to include in the exception.
Always throws a BadRequestException.
Example:
if (!dto || Object.keys(dto).length === 0) {
this.throwBadRequestException('Empty data. Nothing to save.');
}
throwNotFoundException
Throws a standardized NotFoundException with the entity name.
throwNotFoundException(name: string): NotFoundException
The name of the entity that was not found.
Always throws a NotFoundException with the format: ” not found”.
Example:
const entity = await repository.findOne(id);
if (!entity) {
this.throwNotFoundException('User');
// Throws: NotFoundException: "User not found"
}
createPageInfo
Wraps entity data in a paginated response object with metadata.
createPageInfo(
data: T[],
total: number,
limit: number,
offset: number
): GetManyDefaultResponse<T>
The array of entities for the current page.
The total number of entities across all pages.
The maximum number of entities per page.
The number of entities to skip (for pagination).
return
GetManyDefaultResponse<T>
An object containing:
data: The entity array
count: Number of entities in the current page
total: Total number of entities
page: Current page number (1-based)
pageCount: Total number of pages
Example:
const [data, total] = await queryBuilder.getManyAndCount();
const limit = 10;
const offset = 0;
return this.createPageInfo(data, total, limit, offset);
// Returns:
// {
// data: [...],
// count: 10,
// total: 95,
// page: 1,
// pageCount: 10
// }
You can override this method to customize the pagination response format for your application.
Determines whether pagination should be applied to a query.
decidePagination(
parsed: ParsedRequestParams,
options: CrudRequestOptions
): boolean
parsed
ParsedRequestParams
required
The parsed request parameters from the query string.
options
CrudRequestOptions
required
The CRUD options configured for the controller.
Returns true if pagination should be applied, false otherwise.
Logic:
- Returns
true if alwaysPaginate is enabled in options
- Returns
true if page or offset is specified in the request
- Returns
false otherwise
Example:
if (this.decidePagination(parsed, options)) {
const [data, total] = await builder.getManyAndCount();
return this.createPageInfo(data, total, limit, offset);
} else {
return builder.getMany();
}
getTake
Calculates the number of entities to fetch based on request parameters and configured limits.
getTake(
query: ParsedRequestParams,
options: QueryOptions
): number | null
query
ParsedRequestParams
required
The parsed request parameters.
The query options from the controller configuration.
The number of entities to fetch, or null if no limit should be applied.
Priority Order:
- Request
limit parameter (capped by maxLimit if set)
- Configured
limit option (capped by maxLimit if set)
- Configured
maxLimit option
null (no limit)
Example:
const take = this.getTake(parsed, options.query);
if (isFinite(take)) {
builder.take(take);
}
getSkip
Calculates the number of entities to skip for pagination.
getSkip(
query: ParsedRequestParams,
take: number
): number | null
query
ParsedRequestParams
required
The parsed request parameters.
The number of entities per page (from getTake).
The number of entities to skip, or null if no offset should be applied.
Logic:
- If
page is specified: returns take * (page - 1)
- If
offset is specified: returns the offset value
- Otherwise: returns
null
Example:
const take = this.getTake(parsed, options.query);
const skip = this.getSkip(parsed, take);
if (isFinite(skip)) {
builder.skip(skip);
}
getPrimaryParams
Extracts primary parameter field names from the CRUD options.
getPrimaryParams(options: CrudRequestOptions): string[]
options
CrudRequestOptions
required
The CRUD request options.
An array of primary parameter field names.
Example:
const primaryParams = this.getPrimaryParams(req.options);
// Returns: ['id'] or ['userId', 'projectId'] for composite keys
req.parsed.search = primaryParams.reduce(
(acc, p) => ({ ...acc, [p]: saved[p] }),
{}
);
Abstract Methods
These methods must be implemented by concrete service classes:
getMany
abstract getMany(req: CrudRequest): Promise<GetManyDefaultResponse<T> | T[]>
Retrieves multiple entities based on request parameters.
getOne
abstract getOne(req: CrudRequest): Promise<T>
Retrieves a single entity.
createOne
abstract createOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
Creates a single entity.
createMany
abstract createMany(req: CrudRequest, dto: CreateManyDto): Promise<T[]>
Creates multiple entities.
updateOne
abstract updateOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
Updates a single entity (PATCH operation).
replaceOne
abstract replaceOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
Replaces a single entity (PUT operation).
deleteOne
abstract deleteOne(req: CrudRequest): Promise<void | T>
Deletes a single entity.
recoverOne
abstract recoverOne(req: CrudRequest): Promise<void | T>
Recovers a soft-deleted entity.
Usage in Custom Implementations
When creating a custom CRUD service for a different ORM or data source, extend the CrudService class:
import { CrudService } from '@nestjsx/crud';
export class MongoCrudService<T> extends CrudService<T> {
constructor(private model: Model<T>) {
super();
}
async getMany(req: CrudRequest): Promise<GetManyDefaultResponse<T> | T[]> {
const { parsed, options } = req;
const query = this.model.find();
// Apply filters, sorting, pagination
const total = await this.model.countDocuments();
const data = await query.exec();
if (this.decidePagination(parsed, options)) {
const limit = this.getTake(parsed, options.query) || total;
const offset = this.getSkip(parsed, limit) || 0;
return this.createPageInfo(data, total, limit, offset);
}
return data;
}
// Implement other abstract methods...
}
Type Parameters
The entity type that this service manages. Should be your entity/model class.
Interfaces
GetManyDefaultResponse
interface GetManyDefaultResponse<T> {
data: T[]; // Array of entities
count: number; // Number of entities in current page
total: number; // Total number of entities
page: number; // Current page number (1-based)
pageCount: number; // Total number of pages
}
CrudRequest
interface CrudRequest {
parsed: ParsedRequestParams; // Parsed query parameters
options: CrudRequestOptions; // Controller configuration
}
Best Practices
Consistent Error Handling: Always use the provided throwBadRequestException and throwNotFoundException methods for consistent error messages across your application.
Override createPageInfo: If your application uses a different pagination format (e.g., cursor-based pagination), override the createPageInfo method to match your needs.
When implementing abstract methods, ensure you handle all edge cases including:
- Empty results
- Invalid input data
- Missing required fields
- Validation errors
See Also