Programação II REST + Express

Esta página apresenta os conceitos essenciais de REST, mostra como funcionam os verbos HTTP e os códigos de status, e constrói passo a passo uma API REST simples com Express usando dados em memória.

O que você vai aprender

Ao estudar esta página, o foco é entender recurso, rota, requisição, resposta, JSON (JavaScript Object Notation), CRUD e a relação entre URL + método HTTP + status code.

  • REST
  • HTTP
  • GET
  • POST
  • PUT
  • DELETE
  • Express
  • JSON
Ideia central: uma API REST organiza o acesso a recursos por meio de URLs claras, métodos HTTP adequados e respostas com códigos de status que informam o resultado da operação.

1. Conceito de REST

REST é um estilo de organização para APIs no qual trabalhamos com recursos, como /estudantes, /produtos ou /tarefas, e usamos os métodos do HTTP para dizer o que queremos fazer com esses recursos.

1.1 Pensando em recursos

Em vez de criar URLs como:

/buscarEstudantes
/criarEstudante
/removerEstudante

em REST preferimos URLs que representam o recurso:

/estudantes
/estudantes/1
Recurso
É a entidade principal da API, como estudante, livro, tarefa ou produto.
Coleção
É o conjunto do recurso, por exemplo /tarefas.
Item específico
É um elemento individual, por exemplo /tarefas/3.

1.2 O que acontece em uma requisição

  1. O cliente envia uma requisição HTTP para uma URL.
  2. A requisição usa um método, como GET ou POST.
  3. O servidor processa a rota correspondente.
  4. O servidor devolve uma resposta, normalmente em JSON.
  5. A resposta traz um status code, como 200, 201, 404 ou 500.
📦
Pense na API como um balcão de atendimento. A URL identifica o assunto, o método HTTP informa a ação desejada, e o status code responde se deu certo, se faltou informação, se o item não existe ou se ocorreu um erro interno.

2. Verbos HTTP

Os métodos HTTP indicam a intenção da requisição. Em APIs REST, os verbos mais usados são GET, POST, PUT e DELETE.

2.1 Associação com CRUD

Operação Método Exemplo de rota Ideia principal
Create POST /tarefas Cria um novo item.
Read GET /tarefas ou /tarefas/1 Lê todos os itens ou um item específico.
Update PUT /tarefas/1 Atualiza um item existente.
Delete DELETE /tarefas/1 Remove um item.

2.2 Leituras rápidas sobre cada verbo

GET

GET é usado para consultar dados. Ele não deve alterar o estado do recurso.

GET /tarefas
GET /tarefas/2
POST

POST é usado para criar um novo recurso.

POST /tarefas
PUT

PUT é usado para atualizar um recurso existente.

PUT /tarefas/2
DELETE

DELETE é usado para remover um recurso.

DELETE /tarefas/2

3. Status codes mais importantes

Os códigos de status mostram o resultado da requisição. Em APIs REST, é importante usar um código coerente com a operação realizada.

2xx · Sucesso

A requisição foi processada com sucesso.

  • 200 OK – consulta ou atualização bem-sucedida.
  • 201 Created – recurso criado com sucesso.
  • 204 No Content – remoção sem conteúdo de retorno.

4xx · Erro do cliente

Há problema na requisição enviada.

  • 400 Bad Request – dados inválidos.
  • 404 Not Found – recurso não encontrado.

5xx · Erro do servidor

O servidor falhou ao processar a operação.

  • 500 Internal Server Error – erro interno inesperado.

Resposta ruim

Devolver sempre 200, mesmo quando falta campo, quando o item não existe ou quando houve falha.

Resposta melhor

Usar 201 para criar, 400 para entrada inválida, 404 para item ausente e 204 para remoção sem corpo.

Uma API bem organizada não depende apenas do código funcionar; ela também precisa responder de forma clara e consistente.

4. Criando a base da API com Express

Agora vamos montar uma API REST simples com Node.js e Express. O exemplo usará uma lista de tarefas armazenada em memória, isto é, os dados existem apenas enquanto o servidor está ligado.

4.1 Instalação

npm init -y
npm install express

4.2 Arquivo inicial

Crie um arquivo chamado server.js com a estrutura abaixo:

const express = require('express');
const app = express();

app.use(express.json());

const tarefas = [
  { id: 1, titulo: 'Estudar REST', concluida: false },
  { id: 2, titulo: 'Praticar Express', concluida: true }
];

app.get('/', (req, res) => {
  res.send('API funcionando!');
});

app.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});
express()
Cria a aplicação web.
app.use(express.json())
Permite ler JSON enviado no corpo da requisição.
app.listen(3000)
Coloca o servidor para escutar na porta 3000.

4.3 Executando

node server.js

Se tudo estiver certo, ao acessar http://localhost:3000 você verá a mensagem API funcionando!.

5. CRUD completo em memória

Agora vamos implementar as quatro operações principais sobre o recurso tarefas. A coleção será /tarefas e o item individual será /tarefas/:id.

5.1 Código completo

const express = require('express');
const app = express();

app.use(express.json());

let tarefas = [
  { id: 1, titulo: 'Estudar REST', concluida: false },
  { id: 2, titulo: 'Praticar Express', concluida: true }
];

let proximoId = 3;

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

app.get('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const tarefa = tarefas.find(t => t.id === id);

  if (!tarefa) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  res.status(200).json(tarefa);
});

app.post('/tarefas', (req, res) => {
  const { titulo, concluida } = req.body;

  if (!titulo || typeof titulo !== 'string') {
    return res.status(400).json({ erro: 'O campo titulo é obrigatório' });
  }

  const novaTarefa = {
    id: proximoId++,
    titulo,
    concluida: concluida ?? false
  };

  tarefas.push(novaTarefa);
  res.status(201).json(novaTarefa);
});

app.put('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const { titulo, concluida } = req.body;

  const indice = tarefas.findIndex(t => t.id === id);

  if (indice === -1) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  if (!titulo || typeof titulo !== 'string') {
    return res.status(400).json({ erro: 'O campo titulo é obrigatório' });
  }

  tarefas[indice] = {
    id,
    titulo,
    concluida: Boolean(concluida)
  };

  res.status(200).json(tarefas[indice]);
});

app.delete('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const indice = tarefas.findIndex(t => t.id === id);

  if (indice === -1) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  tarefas.splice(indice, 1);
  res.status(204).send();
});

app.listen(3000, () => {
  console.log('Servidor rodando em http://localhost:3000');
});

5.2 Entendendo rota por rota

GET /tarefas

Retorna a lista completa de tarefas.

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

Palavras-chave: listar, coleção, leitura, 200 OK.

GET /tarefas/:id

Busca uma tarefa específica pelo identificador enviado na URL.

app.get('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const tarefa = tarefas.find(t => t.id === id);

  if (!tarefa) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  res.status(200).json(tarefa);
});

Palavras-chave: parâmetro de rota, busca, item único, 404.

POST /tarefas

Recebe dados no corpo da requisição, valida o título e cria uma nova tarefa.

app.post('/tarefas', (req, res) => {
  const { titulo, concluida } = req.body;

  if (!titulo || typeof titulo !== 'string') {
    return res.status(400).json({ erro: 'O campo titulo é obrigatório' });
  }

  const novaTarefa = {
    id: proximoId++,
    titulo,
    concluida: concluida ?? false
  };

  tarefas.push(novaTarefa);
  res.status(201).json(novaTarefa);
});

Palavras-chave: body, validação, criação, 201 Created.

PUT /tarefas/:id

Atualiza os dados de uma tarefa que já existe.

app.put('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const { titulo, concluida } = req.body;

  const indice = tarefas.findIndex(t => t.id === id);

  if (indice === -1) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  if (!titulo || typeof titulo !== 'string') {
    return res.status(400).json({ erro: 'O campo titulo é obrigatório' });
  }

  tarefas[indice] = {
    id,
    titulo,
    concluida: Boolean(concluida)
  };

  res.status(200).json(tarefas[indice]);
});

Palavras-chave: atualização, id, validação, substituição, 200 OK.

DELETE /tarefas/:id

Remove uma tarefa pelo id.

app.delete('/tarefas/:id', (req, res) => {
  const id = Number(req.params.id);
  const indice = tarefas.findIndex(t => t.id === id);

  if (indice === -1) {
    return res.status(404).json({ erro: 'Tarefa não encontrada' });
  }

  tarefas.splice(indice, 1);
  res.status(204).send();
});

Palavras-chave: remoção, 204 No Content, item inexistente, 404.

6. Como testar a API

Você pode testar com navegador, Postman, Insomnia ou VS Code REST Client. O importante é observar método, URL, body e status code.

6.1 Fluxo de teste

  1. Execute node server.js.
  2. Teste GET /tarefas para verificar a lista inicial.
  3. Teste POST /tarefas para criar uma nova tarefa.
  4. Teste GET /tarefas/:id para consultar o item criado.
  5. Teste PUT /tarefas/:id para alterar os dados.
  6. Teste DELETE /tarefas/:id para remover o item.
  7. Repita uma busca no item removido para observar o 404.

6.2 Exemplos de requisição

GET http://localhost:3000/tarefas
POST http://localhost:3000/tarefas
Content-Type: application/json

{
  "titulo": "Montar API REST",
  "concluida": false
}
PUT http://localhost:3000/tarefas/1
Content-Type: application/json

{
  "titulo": "Montar API REST com Express",
  "concluida": true
}
DELETE http://localhost:3000/tarefas/1
Observe durante os testes: qual recurso foi acessado, qual método foi enviado, qual código foi retornado e qual JSON apareceu na resposta.

7. Limitações do exemplo em memória

Esse modelo é excelente para aprender o funcionamento das rotas REST, mas possui uma limitação importante: os dados ficam apenas na variável do programa.

  • Ao reiniciar o servidor, os dados são perdidos.
  • Não há banco de dados.
  • Não existe autenticação ou controle de acesso.
  • As validações ainda são simples.

Mesmo assim, esse é o melhor ponto de partida para compreender o comportamento de uma API antes de integrar banco, autenticação, camadas de serviço e tratamento de erros mais robusto.