A CRUD Service
Combine a repository with a service to implement create, read, update, and delete.
Syntax
repo.create(dto) / repo.save(entity)With an entity and its repository in place, a CRUD service wires up the four basic operations. The controller calls these methods; the service does the data work.
The four operations
- Create - build an entity from a DTO and
save()it. - Read -
find()all orfindOneBy()a single record. - Update - load, merge changes, then save.
- Delete -
delete()by id.
Throw NotFoundException when a lookup returns nothing so clients get a clean 404.
Example
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(
@InjectRepository(User) private readonly repo: Repository<User>,
) {}
create(dto: CreateUserDto) {
const user = this.repo.create(dto);
return this.repo.save(user);
}
async findOne(id: number) {
const user = await this.repo.findOneBy({ id });
if (!user) throw new NotFoundException(`User ${id} not found`);
return user;
}
remove(id: number) {
return this.repo.delete(id);
}
}When to use it
- A developer writes a ProductsService with create, findAll, findOne, update, and remove methods so the controller stays thin and testable.
- A team wraps the repository calls in a service so they can add business logic (e.g., sending a welcome email on user creation) in one place.
- An engineer returns NotFoundException from the service when a record is not found so the controller does not need to handle that error case.
More examples
Full CRUD service
Implements all five CRUD operations in one service, throwing NotFoundException when the requested entity does not exist.
import { Injectable, NotFoundException } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { Product } from './product.entity';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
@Injectable()
export class ProductsService {
constructor(
@InjectRepository(Product) private repo: Repository<Product>,
) {}
create(dto: CreateProductDto) { return this.repo.save(dto); }
findAll() { return this.repo.find(); }
async findOne(id: number) {
const p = await this.repo.findOneBy({ id });
if (!p) throw new NotFoundException(`Product ${id} not found`);
return p;
}
async update(id: number, dto: UpdateProductDto) {
await this.findOne(id);
return this.repo.save({ id, ...dto });
}
async remove(id: number) {
const p = await this.findOne(id);
return this.repo.remove(p);
}
}CRUD controller wiring
Wires all five CRUD service methods to HTTP verbs in the controller, keeping each handler a single delegating line.
import { Controller, Get, Post, Patch, Delete, Param, Body, ParseIntPipe } from '@nestjs/common';
import { ProductsService } from './products.service';
import { CreateProductDto } from './dto/create-product.dto';
import { UpdateProductDto } from './dto/update-product.dto';
@Controller('products')
export class ProductsController {
constructor(private readonly svc: ProductsService) {}
@Post() create(@Body() dto: CreateProductDto) { return this.svc.create(dto); }
@Get() findAll() { return this.svc.findAll(); }
@Get(':id') findOne(@Param('id', ParseIntPipe) id: number) { return this.svc.findOne(id); }
@Patch(':id') update(@Param('id', ParseIntPipe) id: number, @Body() dto: UpdateProductDto) { return this.svc.update(id, dto); }
@Delete(':id') remove(@Param('id', ParseIntPipe) id: number) { return this.svc.remove(id); }
}Soft delete pattern
Adds a @DeleteDateColumn to enable TypeORM soft deletes, so removed records are hidden from queries but still in the database.
import { Entity, Column, DeleteDateColumn, PrimaryGeneratedColumn } from 'typeorm';
@Entity()
export class Product {
@PrimaryGeneratedColumn() id: number;
@Column() name: string;
@DeleteDateColumn() deletedAt?: Date; // null = active
}
// In service:
async softRemove(id: number) {
await this.repo.softDelete(id); // sets deletedAt, hides from find()
}
Discussion