A CRUD Service

Combine a repository with a service to implement create, read, update, and delete.

Syntaxrepo.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 or findOneBy() 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

Example · typescript
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.

Example · ts
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.

Example · ts
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.

Example · ts
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

  • Be the first to comment on this lesson.