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:
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:
@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:
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:
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:
@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
- Controllers - Configure controller endpoints
- DTOs - Understand DTO generation
- Subscriber System - Add hooks at service level
- Error Handling - Advanced error patterns