Skip to main content

Modules, Controllers & Services

Creating a NestJS Project

npm install -g @nestjs/cli
nest new my-api
cd my-api
npm run start:dev

Architecture Overview

src/
app.module.ts # root module
main.ts # bootstrap
users/
users.module.ts # wires everything together
users.controller.ts # handles HTTP
users.service.ts # business logic
dto/
create-user.dto.ts
update-user.dto.ts
entities/
user.entity.ts
auth/
auth.module.ts
auth.controller.ts
auth.service.ts

Module

src/users/users.module.ts
import { Module } from '@nestjs/common';
import { UsersController } from './users.controller';
import { UsersService } from './users.service';
import { PrismaModule } from '../prisma/prisma.module';

@Module({
imports: [PrismaModule], // modules this module needs
controllers: [UsersController], // handle HTTP requests
providers: [UsersService], // services, guards, etc.
exports: [UsersService], // share with other modules
})
export class UsersModule {}

Controller

src/users/users.controller.ts
import {
Controller, Get, Post, Body, Patch, Param, Delete,
ParseUUIDPipe, HttpCode, HttpStatus, UseGuards,
} from '@nestjs/common';
import { UsersService } from './users.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import { JwtAuthGuard } from '../auth/guards/jwt-auth.guard';
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';

@ApiTags('users')
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}

@Post()
@HttpCode(HttpStatus.CREATED)
create(@Body() dto: CreateUserDto) {
return this.usersService.create(dto);
}

@Get()
findAll() {
return this.usersService.findAll();
}

@Get(':id')
findOne(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.findOne(id);
}

@Patch(':id')
@UseGuards(JwtAuthGuard)
@ApiBearerAuth()
update(@Param('id', ParseUUIDPipe) id: string, @Body() dto: UpdateUserDto) {
return this.usersService.update(id, dto);
}

@Delete(':id')
@UseGuards(JwtAuthGuard)
@HttpCode(HttpStatus.NO_CONTENT)
remove(@Param('id', ParseUUIDPipe) id: string) {
return this.usersService.remove(id);
}
}

Service

src/users/users.service.ts
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { PrismaService } from '../prisma/prisma.service';
import { CreateUserDto } from './dto/create-user.dto';
import { UpdateUserDto } from './dto/update-user.dto';
import * as bcrypt from 'bcryptjs';

@Injectable()
export class UsersService {
constructor(private prisma: PrismaService) {}

async create(dto: CreateUserDto) {
const existing = await this.prisma.user.findUnique({
where: { email: dto.email },
});

if (existing) throw new ConflictException('Email already in use');

const hash = await bcrypt.hash(dto.password, 12);

return this.prisma.user.create({
data: { ...dto, password: hash },
select: { id: true, name: true, email: true, role: true, createdAt: true },
});
}

findAll() {
return this.prisma.user.findMany({
select: { id: true, name: true, email: true, role: true, createdAt: true },
});
}

async findOne(id: string) {
const user = await this.prisma.user.findUnique({ where: { id } });
if (!user) throw new NotFoundException(`User ${id} not found`);
return user;
}

async update(id: string, dto: UpdateUserDto) {
await this.findOne(id); // throws 404 if not found
return this.prisma.user.update({ where: { id }, data: dto });
}

async remove(id: string) {
await this.findOne(id);
await this.prisma.user.delete({ where: { id } });
}
}

DTOs with Class Validator

npm install class-validator class-transformer
src/users/dto/create-user.dto.ts
import { IsEmail, IsString, MinLength, MaxLength, IsEnum, IsOptional } from 'class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CreateUserDto {
@ApiProperty({ example: 'Alice Smith' })
@IsString()
@MinLength(2)
@MaxLength(100)
name: string;

@ApiProperty({ example: 'alice@example.com' })
@IsEmail()
email: string;

@ApiProperty({ minLength: 8 })
@IsString()
@MinLength(8)
password: string;
}
src/users/dto/update-user.dto.ts
import { PartialType } from '@nestjs/swagger';
import { CreateUserDto } from './create-user.dto';
import { OmitType } from '@nestjs/mapped-types';

// All fields optional, exclude password from updates via this DTO
export class UpdateUserDto extends PartialType(OmitType(CreateUserDto, ['password'])) {}