Notebook exibindo código em um ambiente de desenvolvimento
Uma API transforma regras de negócio em endpoints que outros sistemas podem chamar. Crédito da foto.
Compartilhe este conteúdo

Neste tutorial você vai criar uma API HTTP simples para produtos usando Node.js e Express. A aplicação aceitará requisições GET, POST, PUT e DELETE, receberá e devolverá JSON e usará códigos de status adequados. O objetivo é enxergar, em um projeto pequeno, como o CRUD vira uma interface que pode ser consumida por um site, aplicativo móvel ou outro sistema.

Resultado final: uma API executada localmente em http://localhost:3000, com endpoints para listar, consultar, cadastrar, atualizar e excluir produtos.

1. Preparando o projeto

Você precisa ter o Node.js instalado. Em uma pasta vazia, abra o terminal e execute:

npm init -y
npm install express

O primeiro comando cria o arquivo package.json. O segundo instala o Express, framework que facilita a criação de rotas e respostas HTTP.

Crie um arquivo chamado server.js. Para este primeiro projeto, os dados ficarão em memória. Isso deixa o foco nas rotas. Ao reiniciar o servidor, os produtos voltam ao estado inicial; mais adiante você pode substituir o array por PostgreSQL ou outro banco.

2. Criando o servidor Express

const express = require('express');

const app = express();
const PORT = 3000;

app.use(express.json());

app.listen(PORT, () => {
  console.log(`API rodando em http://localhost:${PORT}`);
});

express() cria a aplicação. express.json() permite que o Express interprete corpos de requisições enviados em JSON. O listen coloca o servidor para escutar a porta 3000.

A documentação do Express define uma rota pela combinação de método HTTP + caminho + função manipuladora. Por isso, GET /produtos e POST /produtos são operações diferentes mesmo usando o mesmo caminho.

3. Criando alguns dados para trabalhar

Acrescente este array antes do app.listen:

let produtos = [
  { id: 1, nome: 'Teclado', preco: 120 },
  { id: 2, nome: 'Mouse', preco: 80 }
];

let proximoId = 3;

Em um sistema real, essa responsabilidade pertence ao banco de dados. Aqui, o array funciona como um armazenamento temporário para que você consiga visualizar o ciclo completo.

4. GET: listar todos os produtos

app.get('/produtos', (req, res) => {
  res.json(produtos);
});

Ao acessar GET /produtos, a API responde com o array em JSON e, por padrão, status 200.

5. GET por ID: localizar um produto

app.get('/produtos/:id', (req, res) => {
  const id = Number(req.params.id);
  const produto = produtos.find(p => p.id === id);

  if (!produto) {
    return res.status(404).json({ erro: 'Produto não encontrado' });
  }

  res.json(produto);
});

O trecho :id é um parâmetro de rota. Como parâmetros chegam como texto, usamos Number() antes da comparação. Se o recurso não existir, retornar 404 Not Found comunica melhor o resultado do que devolver um objeto vazio.

6. POST: cadastrar um produto

app.post('/produtos', (req, res) => {
  const { nome, preco } = req.body;

  if (!nome || typeof preco !== 'number' || preco < 0) {
    return res.status(400).json({ erro: 'Informe nome e preço válido' });
  }

  const produto = { id: proximoId++, nome, preco };
  produtos.push(produto);
  res.status(201).json(produto);
});

O cliente envia dados; o servidor valida antes de confiar neles. A própria documentação do Express alerta que req.body é controlado pelo usuário e deve ser validado. Quando o cadastro é criado, 201 Created é mais informativo do que um 200 genérico.

7. PUT: atualizar um produto

app.put('/produtos/:id', (req, res) => {
  const id = Number(req.params.id);
  const produto = produtos.find(p => p.id === id);
  if (!produto) return res.status(404).json({ erro: 'Produto não encontrado' });

  const { nome, preco } = req.body;
  if (!nome || typeof preco !== 'number' || preco < 0) return res.status(400).json({ erro: 'Dados inválidos' });

  produto.nome = nome;
  produto.preco = preco;
  res.json(produto);
});

Neste exemplo, o PUT substitui os campos editáveis do produto. Uma API maior também pode oferecer PATCH para alterações parciais.

8. DELETE: excluir um produto

app.delete('/produtos/:id', (req, res) => {
  const id = Number(req.params.id);
  const indice = produtos.findIndex(p => p.id === id);
  if (indice === -1) return res.status(404).json({ erro: 'Produto não encontrado' });
  produtos.splice(indice, 1);
  res.status(204).send();
});

Quando a exclusão ocorre e não há corpo para devolver, 204 No Content é uma resposta apropriada.

9. Código completo

const express = require('express');
const app = express();
const PORT = 3000;
app.use(express.json());

let produtos = [
  { id: 1, nome: 'Teclado', preco: 120 },
  { id: 2, nome: 'Mouse', preco: 80 }
];
let proximoId = 3;

app.get('/produtos', (req, res) => res.json(produtos));
app.get('/produtos/:id', (req, res) => {
  const produto = produtos.find(p => p.id === Number(req.params.id));
  if (!produto) return res.status(404).json({ erro: 'Produto não encontrado' });
  res.json(produto);
});
app.post('/produtos', (req, res) => {
  const { nome, preco } = req.body;
  if (!nome || typeof preco !== 'number' || preco < 0) return res.status(400).json({ erro: 'Dados inválidos' });
  const produto = { id: proximoId++, nome, preco };
  produtos.push(produto);
  res.status(201).json(produto);
});
app.put('/produtos/:id', (req, res) => {
  const produto = produtos.find(p => p.id === Number(req.params.id));
  if (!produto) return res.status(404).json({ erro: 'Produto não encontrado' });
  const { nome, preco } = req.body;
  if (!nome || typeof preco !== 'number' || preco < 0) return res.status(400).json({ erro: 'Dados inválidos' });
  produto.nome = nome; produto.preco = preco; res.json(produto);
});
app.delete('/produtos/:id', (req, res) => {
  const indice = produtos.findIndex(p => p.id === Number(req.params.id));
  if (indice === -1) return res.status(404).json({ erro: 'Produto não encontrado' });
  produtos.splice(indice, 1); res.status(204).send();
});
app.listen(PORT, () => console.log(`API rodando em http://localhost:${PORT}`));

10. Como testar

Inicie o servidor:

node server.js

Você pode testar no terminal com curl, no Postman, Insomnia ou em outro cliente HTTP.

curl http://localhost:3000/produtos

curl -X POST http://localhost:3000/produtos \
  -H "Content-Type: application/json" \
  -d '{"nome":"Webcam","preco":250}'

curl -X PUT http://localhost:3000/produtos/1 \
  -H "Content-Type: application/json" \
  -d '{"nome":"Teclado mecânico","preco":220}'

curl -X DELETE http://localhost:3000/produtos/2

CRUD e métodos HTTP lado a lado

ObjetivoMétodoEndpointStatus comum
ListarGET/produtos200
Consultar umGET/produtos/:id200 / 404
CriarPOST/produtos201 / 400
AtualizarPUT/produtos/:id200 / 400 / 404
ExcluirDELETE/produtos/:id204 / 404

Esta API é realmente REST?

No uso cotidiano, muita gente chama qualquer API HTTP com recursos e métodos como GET, POST, PUT e DELETE de “API REST”. É uma aproximação útil para iniciantes, mas REST é mais amplo. Na formulação original de Roy Fielding, REST é um estilo arquitetural com um conjunto de restrições, incluindo interface uniforme, comunicação sem estado e possibilidade de uso de intermediários. Uma aplicação pode usar HTTP e JSON sem cumprir integralmente essas restrições.

Por isso, o projeto deste tutorial é melhor entendido como uma API HTTP orientada a recursos, no estilo REST. Essa precisão evita transformar REST em apenas uma tabela de verbos HTTP.

O que melhorar depois

Este exemplo é didático. Em produção, você deve separar rotas e regras de negócio, persistir dados em um banco, validar entradas de forma mais robusta, tratar erros de modo centralizado, configurar autenticação e autorização quando necessário, proteger segredos em variáveis de ambiente e criar testes automatizados.

Também vale conectar este tutorial aos conteúdos já publicados: revise CRUD em banco de dados, entenda a base lógica em algoritmos e use Git e GitHub para versionar o projeto.

Desafio prático

Adicione o campo estoque aos produtos. Faça a API rejeitar valores negativos. Depois crie a rota GET /produtos/baixo-estoque para devolver somente produtos com menos de 5 unidades. Pense na ordem das rotas para que baixo-estoque não seja interpretado como um ID.

Perguntas frequentes

Preciso de banco de dados para criar uma API?

Não para aprender o fluxo. Um array em memória é suficiente para experimentar rotas e HTTP. Para persistência real, use um banco.

Express é obrigatório no Node.js?

Não. O próprio Node.js possui um módulo HTTP. O Express adiciona uma camada mais conveniente para rotas, middleware e respostas.

POST e PUT são a mesma coisa?

Não. Neste tutorial, POST cria um novo recurso e PUT atualiza um recurso identificado pela URL.

Conclusão

Uma API deixa o CRUD visível como um contrato entre sistemas. Cada endpoint combina um recurso, um método HTTP, dados de entrada, regras de validação e uma resposta com status coerente. Depois de dominar esse ciclo em memória, o passo natural é ligar a mesma estrutura a um banco de dados e organizar o projeto em camadas.

Referências bibliográficas

  1. BROWN, Ethan. Web Development with Node and Express. 2. ed. O’Reilly Media, 2019.
  2. RICHARDSON, Leonard; AMUNDSEN, Mike; RUBY, Sam. RESTful Web APIs. 1. ed. O’Reilly Media, 2013.

Documentação técnica: Node.js HTTP API; Express Routing e Express API. Material acadêmico: Fielding, Roy T., Architectural Styles and the Design of Network-based Software Architectures, 2000.

Consulte a bibliografia completa do Código em Sala.