Cookbook: API REST Completa com NestJS e Prisma
Receita passo a passo para construir uma API REST robusta com NestJS, Prisma ORM, autenticação JWT, validação e tratamento de erros.
Setup do Projeto
# Criar projeto NestJS
npm i -g @nestjs/cli
nest new minha-api
cd minha-api
# Instalar dependências
npm i @prisma/client @nestjs/config @nestjs/jwt @nestjs/passport passport passport-jwt bcrypt class-validator class-transformer
npm i -D prisma @types/passport-jwt @types/bcrypt
Configurar Prisma
npx prisma init
Defina o schema em prisma/schema.prisma:
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String
password String
role Role @default(USER)
posts Post[]
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
model Post {
id String @id @default(cuid())
title String
content String
published Boolean @default(false)
author User @relation(fields: [authorId], references: [id])
authorId String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
enum Role {
USER
ADMIN
}
Prisma Service
@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
async onModuleInit() {
await this.$connect();
}
}
Autenticação JWT
@Injectable()
export class AuthService {
constructor(
private prisma: PrismaService,
private jwt: JwtService,
) {}
async register(dto: RegisterDto) {
const hash = await bcrypt.hash(dto.password, 12);
const user = await this.prisma.user.create({
data: { email: dto.email, name: dto.name, password: hash },
});
return this.generateToken(user.id, user.email);
}
async login(dto: LoginDto) {
const user = await this.prisma.user.findUnique({ where: { email: dto.email } });
if (!user || !(await bcrypt.compare(dto.password, user.password))) {
throw new UnauthorizedException('Credenciais inválidas');
}
return this.generateToken(user.id, user.email);
}
private generateToken(userId: string, email: string) {
const payload = { sub: userId, email };
return { access_token: this.jwt.sign(payload) };
}
}
CRUD com Validação
@Controller('posts')
@UseGuards(JwtAuthGuard)
export class PostsController {
constructor(private prisma: PrismaService) {}
@Post()
create(@Body() dto: CreatePostDto, @Req() req) {
return this.prisma.post.create({
data: { ...dto, authorId: req.user.id },
});
}
@Get()
findAll(@Query() query: PaginationDto) {
return this.prisma.post.findMany({
skip: query.skip ?? 0,
take: query.take ?? 20,
where: { published: true },
include: { author: { select: { name: true } } },
orderBy: { createdAt: 'desc' },
});
}
}
Tratamento Global de Erros
@Catch()
export class AllExceptionsFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
const status = exception instanceof HttpException
? exception.getStatus()
: HttpStatus.INTERNAL_SERVER_ERROR;
response.status(status).json({
statusCode: status,
message: exception instanceof HttpException
? exception.message
: 'Erro interno do servidor',
timestamp: new Date().toISOString(),
});
}
}



