Skip to Content

Services

Services in NestJS CRUD Automator handle the business logic and database operations. The @ApiService decorator automatically adds CRUD methods to your service class, which must extend ApiServiceBase.

Basic Service Setup

A minimal service requires the @ApiService decorator, extension of ApiServiceBase, and a TypeORM repository:

user.service.ts
import { Injectable } from "@nestjs/common"; import { InjectRepository } from "@nestjs/typeorm"; import { Repository } from "typeorm"; import { ApiService, ApiServiceBase } from "@elsikora/nestjs-crud-automator"; import { UserEntity } from "./user.entity"; @Injectable() @ApiService<UserEntity>({ entity: UserEntity, }) export class UserService extends ApiServiceBase<UserEntity> { constructor( @InjectRepository(UserEntity) public repository: Repository<UserEntity>, ) { super(); } }

Auto-Generated CRUD Methods

The @ApiService decorator automatically adds the following methods to your service:

create(properties, eventManager?): Promise<E>

Creates a new entity:

const user = await userService.create({ body: { username: "john_doe", email: "john@example.com", }, });

get(properties, eventManager?): Promise<E>

Retrieves a single entity by criteria:

const user = await userService.get({ where: { id: "uuid-here" }, });

getList(properties, relations, eventManager?): Promise<IApiGetListResponseResult<E>>

Retrieves a paginated list with filtering and sorting:

const result = await userService.getList( { where: { username: { operator: EFilterOperation.CONT, value: "john", }, }, order: { createdAt: "DESC", }, limit: 10, page: 1, }, {} ); // Result contains: // { // items: UserEntity[], // total: number, // page: number, // limit: number // }

getMany(properties, eventManager?): Promise<E[]>

Retrieves multiple entities:

const users = await userService.getMany({ where: { isActive: true, }, });

update(criteria, properties, eventManager?): Promise<E>

Updates an existing entity:

const updatedUser = await userService.update( { id: "uuid-here" }, { body: { username: "new_username", }, } );

delete(criteria, eventManager?): Promise<void>

Deletes an entity:

await userService.delete({ id: "uuid-here", });

Custom Methods

Add custom business logic beyond CRUD operations:

user.service.ts
@Injectable() @ApiService<UserEntity>({ entity: UserEntity, }) export class UserService extends ApiServiceBase<UserEntity> { constructor( @InjectRepository(UserEntity) public repository: Repository<UserEntity>, ) { super(); } // Custom method async findByEmail(email: string): Promise<UserEntity | null> { return this.repository.findOne({ where: { email } }); } // Custom method with complex logic async activateUser(id: string): Promise<UserEntity> { const user = await this.get({ where: { id } }); return this.update( { id }, { body: { isActive: true, activatedAt: new Date(), }, } ); } // Custom bulk operation async bulkUpdateStatus( userIds: string[], isActive: boolean ): Promise<void> { await this.repository.update( { id: In(userIds) }, { isActive } ); } }

Transaction Support

All CRUD methods accept an optional EntityManager parameter for transaction support:

post.service.ts
import { DataSource } from "typeorm"; @Injectable() export class PostService extends ApiServiceBase<PostEntity> { constructor( @InjectRepository(PostEntity) public repository: Repository<PostEntity>, private dataSource: DataSource, ) { super(); } async createPostWithComments( postData: DeepPartial<PostEntity>, commentsData: DeepPartial<CommentEntity>[] ): Promise<PostEntity> { return this.dataSource.transaction(async (manager) => { // Create post within transaction const post = await this.create( { body: postData }, manager ); // Create comments within same transaction for (const commentData of commentsData) { await manager.save(CommentEntity, { ...commentData, postId: post.id, }); } return post; }); } }

Observable Services

Enable function-level subscribers by marking the service as observable:

post.service.ts
import { ApiService, ApiServiceBase, ApiServiceObservable } from "@elsikora/nestjs-crud-automator"; @Injectable() @ApiService<PostEntity>({ entity: PostEntity, }) @ApiServiceObservable() export class PostService extends ApiServiceBase<PostEntity> { constructor( @InjectRepository(PostEntity) public repository: Repository<PostEntity>, ) { super(); } }

This allows function-level subscribers to intercept service operations at the repository level.

Service Dependencies

Inject other services for complex operations:

order.service.ts
@Injectable() @ApiService<OrderEntity>({ entity: OrderEntity, }) export class OrderService extends ApiServiceBase<OrderEntity> { constructor( @InjectRepository(OrderEntity) public repository: Repository<OrderEntity>, private readonly productService: ProductService, private readonly userService: UserService, private readonly paymentService: PaymentService, ) { super(); } async createOrder( userId: string, productIds: string[] ): Promise<OrderEntity> { // Validate user const user = await this.userService.get({ where: { id: userId } }); // Validate products const products = await this.productService.getMany({ where: { id: In(productIds) }, }); // Calculate total const total = products.reduce((sum, p) => sum + p.price, 0); // Create order return this.create({ body: { userId, total, status: OrderStatus.PENDING, }, }); } }

Repository Access

Access the underlying TypeORM repository for advanced queries:

@Injectable() @ApiService<UserEntity>({ entity: UserEntity, }) export class UserService extends ApiServiceBase<UserEntity> { constructor( @InjectRepository(UserEntity) public repository: Repository<UserEntity>, ) { super(); } async getUsersWithPostCount(): Promise<Array<{ user: UserEntity; postCount: number }>> { return this.repository .createQueryBuilder("user") .leftJoin("user.posts", "post") .select("user") .addSelect("COUNT(post.id)", "postCount") .groupBy("user.id") .getRawAndEntities() .then(result => result.entities.map((user, index) => ({ user, postCount: parseInt(result.raw[index].postCount || "0", 10), })) ); } }

Error Handling

Throw meaningful errors from service methods:

import { NotFoundException, BadRequestException } from "@nestjs/common"; async activateUser(id: string): Promise<UserEntity> { const user = await this.repository.findOne({ where: { id } }); if (!user) { throw new NotFoundException(`User with ID ${id} not found`); } if (user.isActive) { throw new BadRequestException("User is already active"); } return this.update( { id }, { body: { isActive: true, activatedAt: new Date(), }, } ); }

Next Steps

Last updated on