Ao terminar esta aula, você vai conseguir
- Organizar servidor, serviço e persistência por responsabilidade
- Ler corpo JSON com limite e validar uma tarefa
- Verificar os fluxos de listar, criar e falhar
Chegou a hora de conectar as peças numa API pequena, mas verificável. O projeto terá três responsabilidades: o servidor traduz HTTP, o serviço aplica regras de tarefas e o repositório lê e grava JSON. Essa divisão não existe para aumentar o número de arquivos; ela deixa claro onde cada mudança deve acontecer.
Pense no fluxo de um pedido: atendimento registra, cozinha aplica o processo e estoque mantém os dados dos ingredientes. Na API, transporte, regra e persistência também colaboram por contratos. O limite é que software permite outras arquiteturas; três camadas são uma escolha didática, não uma lei universal.
Defina o contrato antes das rotas
Nossa tarefa terá id, titulo e concluida. A criação aceita apenas um título
não vazio. O serviço pode ser pequeno:
export function validarTitulo(valor) {
if (typeof valor !== 'string' || valor.trim().length < 3) {
const erro = new Error('Título precisa ter pelo menos 3 caracteres');
erro.statusCode = 400;
throw erro;
}
return valor.trim();
}
export function criarTarefa(titulo) {
return { id: crypto.randomUUID(), titulo: validarTitulo(titulo), concluida: false };
}O erro controlado carrega um status para a borda HTTP traduzir. O serviço não
recebe req nem chama res.end; assim, a regra pode ser testada sem rede.
Leia o corpo com um limite
No módulo HTTP nativo, o corpo chega em partes chamadas chunks. Reúna as partes, imponha um limite e só então interprete JSON:
export async function lerJson(req, limite = 10_000) {
const partes = [];
let tamanho = 0;
for await (const parte of req) {
tamanho += parte.length;
if (tamanho > limite) {
const erro = new Error('Corpo excede o limite');
erro.statusCode = 413;
throw erro;
}
partes.push(parte);
}
try {
return JSON.parse(Buffer.concat(partes).toString('utf8'));
} catch {
const erro = new Error('JSON inválido');
erro.statusCode = 400;
throw erro;
}
}O limite evita manter um corpo arbitrariamente grande em memória. Em produção, outros limites, autenticação, autorização, TLS, logs e proteção de abuso ainda são necessários; este projeto cobre o fluxo essencial, não toda a segurança.
Ligue rotas ao repositório
Supondo que listarTarefas e salvarTarefas usem as funções da aula anterior,
o roteador pode tratar consulta e criação:
async function rotear(req, res) {
if (req.method === 'GET' && req.url === '/tarefas') {
return responder(res, 200, await listarTarefas());
}
if (req.method === 'POST' && req.url === '/tarefas') {
const entrada = await lerJson(req);
const tarefas = await listarTarefas();
const nova = criarTarefa(entrada.titulo);
await salvarTarefas([...tarefas, nova]);
return responder(res, 201, nova);
}
return responder(res, 404, { erro: 'Rota não encontrada' });
}A função responder define status, content-type e serializa uma única vez. O
servidor envolve rotear com try/catch: erros controlados usam
erro.statusCode; falhas inesperadas são registradas no servidor e devolvem
uma mensagem genérica com status 500, sem expor detalhes internos.
Siga uma requisição completa com o dedo no código: createServer recebe a
mensagem, rotear combina método e caminho, lerJson transforma bytes em dados,
criarTarefa aplica a regra, o repositório persiste e responder traduz o
resultado para HTTP. Essa ordem é o modelo mental do projeto. Quando algo falha,
identifique em qual fronteira o dado deixou de ter a forma esperada, em vez de
adicionar console.log aleatoriamente por todos os arquivos.
Uma função pura como criarTarefa pode receber entradas diretas em um teste. Já
o teste de rota precisa observar a borda HTTP. Os dois níveis respondem perguntas
diferentes e juntos tornam o diagnóstico mais rápido.
import { createServer } from 'node:http';
createServer(async (req, res) => {
try {
await rotear(req, res);
} catch (erro) {
const status = erro.statusCode ?? 500;
if (status === 500) console.error(erro);
responder(res, status, { erro: status === 500 ? 'Erro interno' : erro.message });
}
}).listen(3000, () => console.log('API em http://localhost:3000'));Teste o percurso completo
Com o servidor em execução, use outro terminal:
curl -i http://localhost:3000/tarefas
curl -i -X POST http://localhost:3000/tarefas \
-H 'content-type: application/json' \
-d '{"titulo":"Revisar módulos"}'
curl -i -X POST http://localhost:3000/tarefas \
-H 'content-type: application/json' \
-d '{"titulo":""}'Espere 200, 201 e 400, nessa ordem. Reinicie o processo e consulte de
novo: a tarefa precisa continuar no arquivo. O laboratório pratica somente o
serviço em memória no navegador; não afirma abrir HTTP nem persistir no disco.
Erro comum: confiar só no caminho feliz
Uma API não está pronta porque uma criação funcionou. Teste JSON quebrado, título vazio, rota ausente e reinicialização. Também não devolva a stack trace ao cliente; ela pode revelar caminhos e detalhes internos. Registre o necessário no servidor e mantenha a resposta pública estável.
Na missão final, o projeto está concluído quando os quatro resultados são
demonstráveis:
listar gera 200, criar gera 201, entrada inválida gera 400 e dados sobrevivem
ao reinício. Depois, uma evolução natural é trocar o arquivo por banco de dados
e o roteamento manual por um framework, preservando as mesmas responsabilidades.
Laboratório ao vivo
Serviço de tarefas em memória
O sandbox executa a regra do serviço, sem HTTP ou disco. Adicione, conclua e liste tarefas; depois provoque um título vazio e confirme o erro controlado.
Pare e pense
Qual responsabilidade deve ficar fora do módulo de regras de tarefas?
HTTP pertence à camada de transporte. A regra de tarefas deve receber dados simples, validar o domínio e devolver um resultado sem conhecer req ou res.
Faça sem copiar
Construa e teste GET e POST /tarefas. O projeto passa quando POST válido gera 201, título vazio gera 400 e uma nova execução ainda lista os dados gravados.
Fontes para consultar
Terminou a missão?
Marque apenas quando você conseguir explicar o conceito e concluir o desafio. O progresso fica salvo somente neste navegador.
Voltar ao curso e ver seu progresso →