Como criar uma API REST com Node.js e TypeScript do zero
Um guia prático e completo para construir uma API REST robusta utilizando Node.js, Express e TypeScript, com boas práticas de arquitetura, validação e tratamento de erros.
Construir uma API REST de qualidade vai muito além de usar express() e sair definindo rotas. Neste artigo, vamos do zero até uma estrutura sólida, com TypeScript, validação, tratamento de erros e organização de pastas que vai te salvar quando o projeto crescer.
Pré-requisitos
- Node.js 20 LTS ou superior instalado
- npm ou yarn
- Conhecimento básico de TypeScript
- Um editor de código (recomendo VS Code)
Inicializando o projeto
Comece criando a pasta do projeto e inicializando o package.json:
mkdir minha-api && cd minha-api
npm init -yAgora instale as dependências principais e de desenvolvimento:
npm install express
npm install -D typescript ts-node @types/node @types/express nodemonSempre separe as dependências de desenvolvimento das de produção usando -D. Isso mantém seu bundle de produção leve e organizado.
Configurando o TypeScript
Gere o arquivo tsconfig.json e configure as opções essenciais:
npx tsc --init{
"compilerOptions": {
"target": "ES2022",
"module": "commonjs",
"rootDir": "./src",
"outDir": "./dist",
"strict": true,
"esModuleInterop": true,
"resolveJsonModule": true,
"skipLibCheck": true
},
"include": ["src"]
}Estrutura de pastas
Uma boa organização desde o início evita uma enorme dor de cabeça no futuro. Adote esta estrutura:
src/
├── controllers/ # Lógica de cada rota
├── middlewares/ # Auth, erros, validação
├── models/ # Interfaces e tipos
├── routes/ # Definição das rotas
├── services/ # Regras de negócio
└── server.ts # Entry pointCriando o servidor
Entry point da aplicação
O arquivo server.ts é o ponto de entrada da aplicação. Ele deve ser enxuto: apenas inicializa o Express, registra os middlewares globais, monta as rotas e sobe o servidor.
Nota sobre a ordem dos middlewares
O Express processa middlewares na ordem em que são registrados. Por isso, express.json() sempre deve vir antes das rotas, e o middleware de erro sempre deve ser o último.
import express from 'express';
import { userRoutes } from './routes/userRoutes';
import { errorHandler } from './middlewares/errorHandler';
const app = express();
const PORT = process.env.PORT ?? 3000;
app.use(express.json());
// Rotas
app.use('/api/users', userRoutes);
// Middleware de erros (sempre por último)
app.use(errorHandler);
app.listen(PORT, () => {
console.log(`🚀 Servidor rodando na porta ${PORT}`);
});Tratamento de erros centralizado
Uma das melhores práticas em APIs Express é centralizar o tratamento de erros em um middleware dedicado. Isso evita try/catch duplicado em cada controller:
import type { Request, Response, NextFunction } from 'express';
export class AppError extends Error {
constructor(
public message: string,
public statusCode: number = 500
) {
super(message);
this.name = 'AppError';
}
}
export function errorHandler(
err: Error,
_req: Request,
res: Response,
_next: NextFunction
): void {
if (err instanceof AppError) {
res.status(err.statusCode).json({ error: err.message });
return;
}
console.error(err);
res.status(500).json({ error: 'Erro interno do servidor' });
}O middleware de erro precisa ter exatamente 4 parâmetros (err, req, res, next) para o Express reconhecê-lo como handler de erro, mesmo que você não use todos eles. Use um prefixo _ nos não usados para evitar warnings do TypeScript.
Definindo as rotas
Organize os verbos HTTP na ordem: GET → POST → PUT → PATCH → DELETE. Isso facilita a leitura e a revisão de código em equipe.
GET /— lista todos os recursosGET /:id— retorna um recurso específicoPOST /— cria um novo recursoPUT /:id— substitui um recurso completoDELETE /:id— remove um recurso
import { Router } from 'express';
import { UserController } from '../controllers/UserController';
export const userRoutes = Router();
const controller = new UserController();
userRoutes.get('/', controller.index);
userRoutes.get('/:id', controller.show);
userRoutes.post('/', controller.create);
userRoutes.put('/:id', controller.update);
userRoutes.delete('/:id', controller.destroy);Conclusão
Com essa estrutura você tem uma base sólida que escala bem: adicionar uma nova feature é só criar um controller, um service e uma rota, sem mexer no que já funciona. Os próximos passos naturais seriam adicionar um ORM (como TypeORM ou Prisma), autenticação JWT e uma camada de validação com Zod ou class-validator.
Quer ver esses próximos passos na prática? Os artigos sobre autenticação JWT e validação com Zod estão a caminho!