Skip to main content

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
msg
unknown
Optional error message to include in the exception.
throws
BadRequestException
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
name
string
required
The name of the entity that was not found.
throws
NotFoundException
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>
data
T[]
required
The array of entities for the current page.
total
number
required
The total number of entities across all pages.
limit
number
required
The maximum number of entities per page.
offset
number
required
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.

decidePagination

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.
return
boolean
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.
options
QueryOptions
required
The query options from the controller configuration.
return
number | null
The number of entities to fetch, or null if no limit should be applied.
Priority Order:
  1. Request limit parameter (capped by maxLimit if set)
  2. Configured limit option (capped by maxLimit if set)
  3. Configured maxLimit option
  4. 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.
take
number
required
The number of entities per page (from getTake).
return
number | null
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.
return
string[]
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

T
generic
required
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

Build docs developers (and LLMs) love