Carregando trilhass

Carregando trilhass

Esta trilha mostra passo-a-passo um CRUD simples usando Express e Prisma. A proposta é usar uma separação clara entre controllers, services e repositories (camada de persistência) para facilitar testes e manutenção.
src/
server.ts (inicia o Express)routes/ - define rotascontrollers/ - recebe Request/Responseservices/ - regras de negóciorepositories/ - acesso ao banco via Prismaprisma/ - schema.prisma e migrationsVamos usar um modelo Post simples:
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "sqlite"
url = "file:./dev.db"
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
}
Observação: neste guia usamos sqlite como banco local de desenvolvimento (arquivo dev.db). Em produção prefira um SGBD como PostgreSQL e ajuste provider/url conforme necessário.
Comandos úteis (SQLite):
# inicializa a pasta prisma (gera schema.prisma se não existir)
npx prisma init
# cria/migra o banco SQLite (gerará o arquivo `prisma/dev.db` por padrão)
npx prisma migrate dev --name init
# gera o client do Prisma
npx prisma generate
Observação sobre alternativas:
npx prisma db push para sincronizar o schema com o banco sem gerar arquivos de migration.SQLite é um banco de dados relacional leve que armazena os dados em um único arquivo (ex.: dev.db). Vantagens para desenvolvimento local:
Limitações:
Use SQLite para desenvolvimento, demos e testes locais. Para produção, migre para PostgreSQL, MySQL ou outro SGBD conforme a necessidade.
Local do arquivo:
schema.prisma aponta url = "file:./dev.db", o arquivo será gerado em prisma/dev.db quando executar prisma migrate dev.// package.json (trecho)
{
"dependencies": {
"express": "^4.18.2",
"@prisma/client": "^5.0.0"
},
"devDependencies": {
"prisma": "^5.0.0",
"ts-node-dev": "^2.0.0",
"typescript": "^4.0.0"
}
}
Comando para rodar em desenvolvimento:
yarn add express @prisma/client
yarn add -D prisma ts-node-dev typescript @types/express
src/prisma/client.tsimport { PrismaClient } from '@prisma/client';
const prisma = new PrismaClient();
export default prisma;
src/repositories/post.repository.tsResponsável por toda interação com o banco (Prisma). Retorna dados brutos para o service.
import prisma from '../prisma/client';
import type { Post } from '@prisma/client';
export const PostRepository = {
async findAll(): Promise<Post[]> {
return prisma.post.findMany({ orderBy: { createdAt: 'desc' } });
},
async findById(id: number): Promise<Post | null> {
return prisma.post.findUnique({ where: { id } });
},
async create(data: { title: string; content?: string; published?: boolean }): Promise<Post> {
return prisma.post.create({ data });
},
async update(id: number, data: Partial<Post>): Promise<Post> {
return prisma.post.update({ where: { id }, data });
},
async delete(id: number): Promise<Post> {
return prisma.post.delete({ where: { id } });
},
};
src/services/post.service.tsRegras de negócio. Validações simples e adaptação de erros.
import { PostRepository } from '../repositories/post.repository';
export const PostService = {
async list() {
return PostRepository.findAll();
},
async getById(id: number) {
const post = await PostRepository.findById(id);
if (!post) throw new Error('Post not found');
return post;
},
async create(payload: { title: string; content?: string; published?: boolean }) {
if (!payload.title) throw new Error('Title is required');
return PostRepository.create(payload);
},
async update(id: number, payload: Partial<{ title: string; content: string; published: boolean }>) {
// opcional: verificar existência antes
await this.getById(id);
return PostRepository.update(id, payload as any);
},
async remove(id: number) {
await this.getById(id);
return PostRepository.delete(id);
},
};
src/controllers/post.controller.tsController lida com Request/Response, converte parâmetros e trata erros básicos.
import { Request, Response } from 'express';
import { PostService } from '../services/post.service';
export const PostController = {
async list(req: Request, res: Response) {
const posts = await PostService.list();
return res.json(posts);
},
async getById(req: Request, res: Response) {
const id = Number(req.params.id);
try {
const post = await PostService.getById(id);
return res.json(post);
} catch (err: any) {
return res.status(404).json({ message: err.message });
}
},
async create(req: Request, res: Response) {
try {
const created = await PostService.create(req.body);
return res.status(201).json(created);
} catch (err: any) {
return res.status(400).json({ message: err.message });
}
},
async update(req: Request, res: Response) {
const id = Number(req.params.id);
try {
const updated = await PostService.update(id, req.body);
return res.json(updated);
} catch (err: any) {
return res.status(400).json({ message: err.message });
}
},
async remove(req: Request, res: Response) {
const id = Number(req.params.id);
try {
await PostService.remove(id);
return res.status(204).send();
} catch (err: any) {
return res.status(400).json({ message: err.message });
}
},
};
src/routes/post.routes.tsimport { Router } from 'express';
import { PostController } from '../controllers/post.controller';
const router = Router();
router.get('/', PostController.list);
router.get('/:id', PostController.getById);
router.post('/', PostController.create);
router.put('/:id', PostController.update);
router.delete('/:id', PostController.remove);
export default router;
src/server.tsimport express from 'express';
import postRoutes from './routes/post.routes';
const app = express();
app.use(express.json());
app.use('/posts', postRoutes);
const PORT = process.env.PORT ?? 3333;
app.listen(PORT, () => console.log(`Server running on http://localhost:${PORT}`));
/posts -> lista todos os posts/posts/:id -> obtém post por id/posts -> cria um novo post (body: { title, content?, published? })/posts/:id -> atualiza todo o recurso (ou use PATCH para parcial)/posts/:id -> remove o postExemplo curl:
# Create
curl -X POST http://localhost:3333/posts -H "Content-Type: application/json" -d '{"title":"Meu primeiro post","content":"Conteúdo"}'
# List
curl http://localhost:3333/posts
# Get by id
curl http://localhost:3333/posts/1
# Update
curl -X PUT http://localhost:3333/posts/1 -H "Content-Type: application/json" -d '{"title":"Novo título"}'
# Delete
curl -X DELETE http://localhost:3333/posts/1
Nesta seção mostramos como adicionar validação de request bodies com zod. A ideia é definir schemas para os payloads e aplicar um middleware que valida req.body antes de chegar ao controller.
Instalação:
yarn add zod
# ou npm install zod
Crie src/validations/post.schema.ts com os schemas para criação e atualização:
import { z } from 'zod';
export const createPostSchema = z.object({
title: z.string().min(1, 'Title is required'),
content: z.string().optional(),
published: z.boolean().optional(),
});
export const updatePostSchema = z.object({
title: z.string().min(1).optional(),
content: z.string().nullable().optional(),
published: z.boolean().optional(),
});
export type CreatePostInput = z.infer<typeof createPostSchema>;
export type UpdatePostInput = z.infer<typeof updatePostSchema>;
Crie um middleware genérico src/middlewares/validate.ts que recebe um schema e valida req.body:
import { NextFunction, Request, Response } from 'express';
import { ZodSchema } from 'zod';
export const validate = (schema: ZodSchema<any>) => (req: Request, res: Response, next: NextFunction) => {
const result = schema.safeParse(req.body);
if (!result.success) {
const errors = result.error.format();
return res.status(400).json({ message: 'Validation failed', errors });
}
// sobrescreve o body com os dados validados/parseados
req.body = result.data;
return next();
};
Importe o middleware e os schemas nas rotas para validar as requisições:
// src/routes/post.routes.ts
import { Router } from 'express';
import { PostController } from '../controllers/post.controller';
import { validate } from '../middlewares/validate';
import { createPostSchema, updatePostSchema } from '../validations/post.schema';
const router = Router();
router.get('/', PostController.list);
router.get('/:id', PostController.getById);
router.post('/', validate(createPostSchema), PostController.create);
router.put('/:id', validate(updatePostSchema), PostController.update);
router.delete('/:id', PostController.remove);
export default router;
O middleware já retorna 400 quando a validação falha. Nos serviços/controllers, você pode assumir que req.body está validado. Se preferir, capture ZodError explicitamente quando validar dentro do serviço.
Ao usar TypeScript, importe os tipos inferidos pelo Zod (CreatePostInput, UpdatePostInput) nos services para garantir tipagem forte:
// src/services/post.service.ts (trecho)
import type { CreatePostInput, UpdatePostInput } from '../validations/post.schema';
async create(payload: CreatePostInput) { /* ... */ }
async update(id: number, payload: UpdatePostInput) { /* ... */ }
Descrição: Node.js Express MongoDB backend tutorial 2025 Prisma ORM API REST CRUD JavaScript desenvolvimento web fullstack banco de dados NoSQL.
Como CRIAR um BACKEND com Node.js, Express e MongoDB (Prisma ORM - FÁCIL!)