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[]>
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>
The CRUD request object with search criteria
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>
The data transfer object containing entity data to create
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[]>
Object containing a bulk array of entities to create
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>
The CRUD request object with entity identifier
The data to update (partial entity data)
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>
The CRUD request object with entity identifier
The complete entity data to replace
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>
The CRUD request object with entity identifier
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>
The CRUD request object with entity identifier
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
Example:
if (!dto.email) {
this.throwBadRequestException('Email is required');
}
throwNotFoundException()
Throws a NotFoundException with a resource name.
throwNotFoundException(name: string): NotFoundException
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>
Array of entities for the current page
Total number of entities matching the query
Maximum number of entities per page
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
// }
Determines whether pagination should be applied.
decidePagination(
parsed: ParsedRequestParams,
options: CrudRequestOptions
): boolean
parsed
ParsedRequestParams
required
Parsed request parameters
options
CrudRequestOptions
required
CRUD configuration options
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
Query configuration options
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
Number of entities per page
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
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);
}
}