API REST Profissional com Fastify e Prisma
A maioria dos tutoriais de API REST para com o CRUD funcionando. Você tem quatro rotas, um prisma.user.findMany() direto no handler, zero validação, zero tratamento de erro. Funciona no Insomnia. Quebra no primeiro request malformado.
Este post monta uma API REST com Fastify e Prisma que trata os problemas que aparecem quando o código vai para staging: validação de payload, error handling centralizado, separação entre handler e lógica de negócio, e logging estruturado. O resultado é uma base que você consegue estender sem reescrever.
Por que Fastify e não Express
A escolha entre Fastify e Express não é sobre "qual é mais rápido" em benchmark sintético. A diferença prática está em três pontos que afetam o dia a dia de desenvolvimento:
| Critério | Express | Fastify |
|---|---|---|
| Validação nativa | Não tem. Precisa de middleware externo (express-validator, celebrate) | JSON Schema nativo no core, com suporte a Zod via plugin |
| Serialização de resposta | Não tem. res.json() faz JSON.stringify sem schema | Serialização com schema via fast-json-stringify, que pré-compila o serializer |
| Logging | Não tem. Você instala morgan ou winston por fora | Pino integrado no core, com request id automático |
| Sistema de plugins | Middleware global com app.use(), sem encapsulamento | Encapsulamento por plugin com escopo isolado |
| TypeScript DX | Tipagem via @types/express, frequentemente defasada | Tipagem first-class, generics nos handlers para schema tipado |
Se a sua API tem menos de cinco rotas e você já conhece Express, não vale a migração. Se você está começando um projeto novo com mais de dez endpoints, Fastify entrega mais infraestrutura pronta e menos dependências externas.
Setup inicial: projeto, Prisma e estrutura de pastas
mkdir api-produtos && cd api-produtos
npm init -y
npm install fastify @fastify/cors @prisma/client zod
npm install -D typescript tsx prisma @types/node
npx tsc --init --target ES2022 --module NodeNext --moduleResolution NodeNext --outDir dist --rootDir src --strict
npx prisma init --datasource-provider postgresql
A estrutura que funciona sem over-engineering para uma API de tamanho médio (10-30 endpoints):
src/
server.ts
app.ts
modules/
product/
product.routes.ts
product.service.ts
product.schema.ts
lib/
prisma.ts
errors/
app-error.ts
prisma/
schema.prisma
Essa organização por módulo (feature) escala melhor que a separação por tipo (controllers/, services/, routes/) porque mantém tudo que pertence a um domínio no mesmo diretório. Se você quer ir além nessa direção, o post sobre Clean Architecture com TypeScript e Node.js detalha a separação em camadas com inversão de dependência.
Schema do Prisma e cliente singleton
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model Product {
id String @id @default(cuid())
name String @db.VarChar(255)
description String? @db.Text
priceInCents Int // armazenar preço em centavos evita problemas de ponto flutuante
stock Int @default(0)
active Boolean @default(true)
createdAt DateTime @default(now()) @map("created_at")
updatedAt DateTime @updatedAt @map("updated_at")
@@map("products")
}
O cliente do Prisma precisa ser singleton. Se você instancia new PrismaClient() em cada arquivo, cada instância abre seu próprio connection pool. Em desenvolvimento com hot reload (tsx watch, nodemon), isso esgota conexões do PostgreSQL em minutos.
// src/lib/prisma.ts
import { PrismaClient } from "@prisma/client";
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined;
};
// Reutiliza a instância entre hot reloads em desenvolvimento.
// Em produção, o processo inicia uma vez e isso é irrelevant
---
Leia o artigo completo em [https://www.vivodecodigo.com.br/backend/api-rest-profissional-fastify-prisma-nodejs](https://www.vivodecodigo.com.br/backend/api-rest-profissional-fastify-prisma-nodejs)