⚡ Cap 05: Stack PERN: Express com TypeScript, PostgreSQL, Prisma ORM e React com TanStack Query
Arquitetura de Aplicações Full Stack
🗺️ Mapa Conceitual
flowchart LR
REACT["⚛️ React<br/>TanStack Query (cache do servidor)"]
REACT -->|"fetch /api/... (proxy do Vite em dev)"| EXPRESS["🚂 Express + TypeScript<br/>router → Zod → handler de erros"]
EXPRESS --> PRISMA["🔷 Prisma Client<br/>(um por processo)"]
PRISMA --> PG[("🐘 PostgreSQL")]
style REACT fill:#e1f5fe,stroke:#03a9f4,stroke-width:2px
style EXPRESS fill:#fff3e0,stroke:#ff9800,stroke-width:2px
🧩 O problema
- TypeScript dos dois lados: o mesmo tipo do banco à tela
- Mas Express e React não opinam: pastas, validação, erros, cache… tudo é decisão sua
- Este capítulo: as decisões que fazem a API e a tela não quebrarem em produção
🚂 Express organizado
- Router por recurso:
app.use("/api/tarefas", tarefasRouter) - Validação na entrada com Zod:
NovaTarefa.parse(req.body) - Um handler de erros no fim, com 4 parâmetros:
ZodError→ 400,ErroHttp→ status, resto → 500 sem stack trace express.json()antes das rotas, senãoreq.bodyéundefined
💥 Rotas async no Express 4
export const assincrono = (fn) => (req, res, next) => { fn(req, res).catch(next); };
- Sem isso, um
thrownuma rotaasyncvira promessa rejeitada sem tratamento - No Node.js 22, isso derruba o processo inteiro, não só a requisição
- O Express 5 repassa a rejeição sozinho
🐘 Prisma na rota
- Um
PrismaClientpor processo (cada um abre um pool de conexões) selectsó com as colunas que a tela usa- Migrações e comparação com TypeORM/Drizzle: Cap. 07
⚛️ TanStack Query
- Dados da API = cache identificado por
queryKey useQuery: carregando, erro e dados prontosuseMutation+invalidateQueries: muda e recarrega- A chave inclui tudo de que o resultado depende:
["tarefas", { status }] fetchnão rejeita em 4xx/5xx: confiraresp.ok
🧭 Estado do servidor × estado da tela
| Onde mora | Exemplo | Ferramenta |
|---|---|---|
| Na API | Lista de tarefas, estadias | TanStack Query |
| Só na tela | Aba aberta, filtro, carrinho não enviado | useState / hook próprio |
🔌 Portas diferentes
- Dev: proxy do Vite,
server.proxy["/api"] = "http://localhost:3000", sem CORS - Produção:
cors({ origin: WEB_ORIGIN, credentials: true }) - Nunca
origin: "*"junto comcredentials
⚖️ Comparação
| Express | Fastify | NestJS | |
|---|---|---|---|
| Estrutura | Livre | Livre + plugins | Imposta |
Erros async |
Embrulho (v4) | Automático | Automático |
| Quando | APIs pequenas | Muito tráfego | Equipes grandes |
useEffect + useState para um dado isolado; TanStack Query quando várias telas usam a mesma API.
⚠️ Armadilhas
- Servidor cai em erro do banco → rota
asyncsemnext(err)no Express 4 - “Too many connections” →
new PrismaClient()dentro da rota - Lista não atualiza → mutação sem
invalidateQueries - Filtro mostra dado antigo → parâmetro fora da
queryKey - Segredo no navegador → variável
VITE_*é pública
🗂️ No Catálogo
fullstack_tarefas_07_spring_react:useEffect+useState, o “antes” do TanStack Queryfullstack_tecloja_03_nest_react:useCart.js, estado do clienteinfra_nginx_01_nodejs: Express mínimo atrás do Nginxfullstack_pi_01a_05: React sem API; o desafio dos exercícios é escrevê-la
Slide 1 de 1
Atalhos: ← → Navegar • Espaço Avançar • F Tela Cheia • Home Início • End Fim