Skip to main content

CrudService

The CrudService is an abstract base class that defines the contract for implementing CRUD operations in your NestJS applications. All concrete service implementations should extend this class.

Overview

This abstract class provides:
  • Standard CRUD method signatures
  • Error handling utilities
  • Pagination helpers
  • Query parameter processing

Abstract Methods

These methods must be implemented by concrete service classes:

getMany()

Retrieves multiple entities based on query parameters.
abstract getMany(req: CrudRequest): Promise<GetManyDefaultResponse<T> | T[]>
req
CrudRequest
required
The CRUD request object containing parsed query parameters and options
return
Promise<GetManyDefaultResponse<T> | T[]>
Returns either an array of entities or a paginated response with metadata
Example:
async getMany(req: CrudRequest): Promise<GetManyDefaultResponse<User> | User[]> {
  const { parsed, options } = req;
  // Implementation logic
  return users;
}

getOne()

Retrieves a single entity.
abstract getOne(req: CrudRequest): Promise<T>
req
CrudRequest
required
The CRUD request object with search criteria
return
Promise<T>
Returns the requested entity
Example:
async getOne(req: CrudRequest): Promise<User> {
  const user = await this.repository.findOne(req.parsed.search);
  if (!user) {
    this.throwNotFoundException('User');
  }
  return user;
}

createOne()

Creates a single entity.
abstract createOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
req
CrudRequest
required
The CRUD request object
dto
T | Partial<T>
required
The data transfer object containing entity data to create
return
Promise<T>
Returns the created entity
Example:
async createOne(req: CrudRequest, dto: CreateUserDto): Promise<User> {
  const user = this.repository.create(dto);
  return await this.repository.save(user);
}

createMany()

Creates multiple entities in bulk.
abstract createMany(req: CrudRequest, dto: CreateManyDto): Promise<T[]>
req
CrudRequest
required
The CRUD request object
dto
CreateManyDto
required
Object containing a bulk array of entities to create
return
Promise<T[]>
Returns an array of created entities
Example:
async createMany(req: CrudRequest, dto: CreateManyDto<User>): Promise<User[]> {
  const users = dto.bulk.map(data => this.repository.create(data));
  return await this.repository.save(users);
}

updateOne()

Updates an existing entity (partial update).
abstract updateOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
req
CrudRequest
required
The CRUD request object with entity identifier
dto
T | Partial<T>
required
The data to update (partial entity data)
return
Promise<T>
Returns the updated entity
Example:
async updateOne(req: CrudRequest, dto: UpdateUserDto): Promise<User> {
  const user = await this.getOne(req);
  Object.assign(user, dto);
  return await this.repository.save(user);
}

replaceOne()

Replaces an entire entity (full update).
abstract replaceOne(req: CrudRequest, dto: T | Partial<T>): Promise<T>
req
CrudRequest
required
The CRUD request object with entity identifier
dto
T | Partial<T>
required
The complete entity data to replace
return
Promise<T>
Returns the replaced entity
Unlike updateOne(), replaceOne() replaces the entire entity rather than merging properties.
Example:
async replaceOne(req: CrudRequest, dto: User): Promise<User> {
  const id = req.parsed.search.id;
  await this.repository.delete(id);
  const user = this.repository.create({ ...dto, id });
  return await this.repository.save(user);
}

deleteOne()

Deletes a single entity.
abstract deleteOne(req: CrudRequest): Promise<void | T>
req
CrudRequest
required
The CRUD request object with entity identifier
return
Promise<void | T>
Returns void or the deleted entity if configured to return deleted data
Example:
async deleteOne(req: CrudRequest): Promise<void> {
  const user = await this.getOne(req);
  await this.repository.remove(user);
}

recoverOne()

Recovers a soft-deleted entity.
abstract recoverOne(req: CrudRequest): Promise<void | T>
req
CrudRequest
required
The CRUD request object with entity identifier
return
Promise<void | T>
Returns void or the recovered entity
This method is only applicable when soft delete is enabled on your entity.
Example:
async recoverOne(req: CrudRequest): Promise<User> {
  const user = await this.repository.findOne({
    where: req.parsed.search,
    withDeleted: true
  });
  return await this.repository.recover(user);
}

Utility Methods

throwBadRequestException()

Throws a BadRequestException with an optional message.
throwBadRequestException(msg?: unknown): BadRequestException
msg
unknown
Optional error message
Example:
if (!dto.email) {
  this.throwBadRequestException('Email is required');
}

throwNotFoundException()

Throws a NotFoundException with a resource name.
throwNotFoundException(name: string): NotFoundException
name
string
required
The name of the resource that was not found
Example:
const user = await this.repository.findOne(id);
if (!user) {
  this.throwNotFoundException('User');
}

createPageInfo()

Creates a paginated response wrapper.
createPageInfo(
  data: T[],
  total: number,
  limit: number,
  offset: number
): GetManyDefaultResponse<T>
data
T[]
required
Array of entities for the current page
total
number
required
Total number of entities matching the query
limit
number
required
Maximum number of entities per page
offset
number
required
Number of entities to skip
return
GetManyDefaultResponse<T>
Object containing:
  • data: Array of entities
  • count: Number of entities in current page
  • total: Total number of entities
  • page: Current page number (1-indexed)
  • pageCount: Total number of pages
Override this method to customize the pagination response format.
Example:
const pageInfo = this.createPageInfo(users, 100, 10, 20);
// Returns:
// {
//   data: [...],
//   count: 10,
//   total: 100,
//   page: 3,
//   pageCount: 10
// }

decidePagination()

Determines whether pagination should be applied.
decidePagination(
  parsed: ParsedRequestParams,
  options: CrudRequestOptions
): boolean
parsed
ParsedRequestParams
required
Parsed request parameters
options
CrudRequestOptions
required
CRUD configuration options
return
boolean
Returns true if pagination should be applied
Example:
const shouldPaginate = this.decidePagination(req.parsed, req.options);
if (shouldPaginate) {
  // Apply pagination
}

getTake()

Calculates the number of entities to fetch.
getTake(
  query: ParsedRequestParams,
  options: QueryOptions
): number | null
query
ParsedRequestParams
required
Parsed query parameters
options
QueryOptions
required
Query configuration options
return
number | null
Number of entities to fetch, or null if no limit
Example:
const take = this.getTake(req.parsed, req.options.query);
// Returns the smaller of: query limit, options limit, or maxLimit

getSkip()

Calculates the number of entities to skip for pagination.
getSkip(
  query: ParsedRequestParams,
  take: number
): number | null
query
ParsedRequestParams
required
Parsed query parameters
take
number
required
Number of entities per page
return
number | null
Number of entities to skip, or null if no offset
Example:
const skip = this.getSkip(req.parsed, 10);
// For page=3, returns 20 (skip first 2 pages of 10 items each)

getPrimaryParams()

Extracts primary parameter field names from options.
getPrimaryParams(options: CrudRequestOptions): string[]
options
CrudRequestOptions
required
CRUD configuration options
return
string[]
Array of primary parameter field names
Example:
const primaryParams = this.getPrimaryParams(req.options);
// Returns: ['id'] or ['userId', 'projectId'] for composite keys

Implementation Example

Here’s a complete example of implementing a custom CRUD service:
import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { CrudService, CrudRequest, CreateManyDto, GetManyDefaultResponse } from '@nestjsx/crud';
import { User } from './user.entity';

@Injectable()
export class UserService extends CrudService<User> {
  constructor(
    @InjectRepository(User)
    private userRepository: Repository<User>,
  ) {
    super();
  }

  async getMany(req: CrudRequest): Promise<GetManyDefaultResponse<User> | User[]> {
    const { parsed, options } = req;
    const [data, total] = await this.userRepository.findAndCount({
      take: this.getTake(parsed, options.query),
      skip: this.getSkip(parsed, this.getTake(parsed, options.query)),
    });

    if (this.decidePagination(parsed, options)) {
      return this.createPageInfo(data, total, parsed.limit, parsed.offset);
    }

    return data;
  }

  async getOne(req: CrudRequest): Promise<User> {
    const user = await this.userRepository.findOne({
      where: req.parsed.search,
    });

    if (!user) {
      this.throwNotFoundException('User');
    }

    return user;
  }

  async createOne(req: CrudRequest, dto: User): Promise<User> {
    const user = this.userRepository.create(dto);
    return await this.userRepository.save(user);
  }

  async createMany(req: CrudRequest, dto: CreateManyDto<User>): Promise<User[]> {
    if (!dto.bulk || !dto.bulk.length) {
      this.throwBadRequestException('Empty data. Nothing to save.');
    }

    const users = dto.bulk.map(data => this.userRepository.create(data));
    return await this.userRepository.save(users);
  }

  async updateOne(req: CrudRequest, dto: Partial<User>): Promise<User> {
    const user = await this.getOne(req);
    Object.assign(user, dto);
    return await this.userRepository.save(user);
  }

  async replaceOne(req: CrudRequest, dto: User): Promise<User> {
    const user = await this.getOne(req);
    const replaced = this.userRepository.create({
      ...dto,
      id: user.id,
    });
    return await this.userRepository.save(replaced);
  }

  async deleteOne(req: CrudRequest): Promise<void | User> {
    const user = await this.getOne(req);
    await this.userRepository.remove(user);
    
    if (req.options.routes.deleteOneBase.returnDeleted) {
      return user;
    }
  }

  async recoverOne(req: CrudRequest): Promise<User> {
    const user = await this.userRepository.findOne({
      where: req.parsed.search,
      withDeleted: true,
    });

    if (!user) {
      this.throwNotFoundException('User');
    }

    return await this.userRepository.recover(user);
  }
}