typescript
57 lines · 7 steps
Building a CRUD service in NestJS
A NestJS provider wraps a TypeORM repository to create, read, update, and delete users while guarding against duplicates and missing rows.
Explained by
highlit
1import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
2import { InjectRepository } from '@nestjs/typeorm';
3import { Repository, DeepPartial } from 'typeorm';
4import { User } from './entities/user.entity';
5import { CreateUserDto } from './dto/create-user.dto';
6import { UpdateUserDto } from './dto/update-user.dto';
7
8@Injectable()
9export class UsersService {
10 constructor(
11 @InjectRepository(User)
12 private readonly usersRepository: Repository<User>,
13 ) {}
14
15 async create(dto: CreateUserDto): Promise<User> {
16 const existing = await this.usersRepository.findOne({
17 where: { email: dto.email },
18 });
19
20 if (existing) {
21 throw new ConflictException(`Email ${dto.email} is already registered`);
22 }
23
24 const user = this.usersRepository.create(dto as DeepPartial<User>);
25 return this.usersRepository.save(user);
26 }
27
28 findAll(): Promise<User[]> {
29 return this.usersRepository.find({
30 order: { createdAt: 'DESC' },
31 });
32 }
33
34 async findOne(id: string): Promise<User> {
35 const user = await this.usersRepository.findOne({ where: { id } });
36
37 if (!user) {
38 throw new NotFoundException(`User ${id} not found`);
39 }
40
41 return user;
42 }
43
44 async update(id: string, dto: UpdateUserDto): Promise<User> {
45 const user = await this.findOne(id);
46 Object.assign(user, dto);
47 return this.usersRepository.save(user);
48 }
49
50 async remove(id: string): Promise<void> {
51 const result = await this.usersRepository.delete(id);
52
53 if (result.affected === 0) {
54 throw new NotFoundException(`User ${id} not found`);
55 }
56 }
57}
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Injecting a repository keeps data access declarative and testable while the service focuses on business rules.
- 2Translating data conditions into NestJS HTTP exceptions gives callers meaningful status codes for free.
- 3Reusing findOne inside update centralizes the not-found check so every read path enforces the same guarantee.
Related explainers
go
package handlers import ( "context"
Liveness vs readiness health checks in Gin
health-checks
dependency-injection
context-timeout
Intermediate
7 steps
typescript
type AsyncMethod = (...args: any[]) => Promise<any>; function LogExecutionTime(thresholdMs = 0) { return function <T extends AsyncMethod>(
A method decorator that times async calls
decorators
higher-order-functions
async
Advanced
9 steps
php
<?php namespace App\Queue;
A stable priority job queue in PHP
priority-queue
data-structures
closures
Intermediate
8 steps
typescript
import { Controller, Get, Param, Query, Redirect, NotFoundException } from '@nestjs/common'; import { LinksService } from './links.service'; @Controller('links')
Handling HTTP redirects in NestJS
redirects
routing
decorators
Intermediate
7 steps
python
import secrets from datetime import datetime, timedelta, timezone from fastapi import APIRouter, Cookie, Depends, HTTPException, Response, status
Cookie session auth in FastAPI
session-authentication
cookies
dependency-injection
Intermediate
8 steps
go
package storage import ( "context"
A SQLite-backed order store in Go
database/sql
sqlite
error-wrapping
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/building-a-crud-service-in-nestjs-explained-typescript-b4a3/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.