← Stack web // stack web · back end · api

REST: os verbos e os códigos de status

Dois programas precisam conversar pela rede. REST é o combinado que faz um entender o outro sem manual próprio: o endereço diz de qual coisa você está falando, o verbo diz o que fazer com ela, e um número de três dígitos diz o que aconteceu.

O que REST resolve

Sem um combinado, cada API inventa o próprio vocabulário. Uma expõe /buscarUsuario, outra /usuario/get, uma terceira manda tudo por POST e devolve sempre 200 com um campo "erro" escondido no corpo da resposta. Quem consome três APIs assim lê três manuais e decora três jeitos diferentes de descobrir se a chamada deu certo.

REST corta esse trabalho reaproveitando o que o HTTP já tinha desde os anos 90: um endereço para cada coisa, um verbo para cada operação e um código numérico para o resultado. Com o combinado no lugar, você lê DELETE /tarefas/7 e já sabe o que aquilo faz, sem abrir documentação nenhuma.

Os conceitos que você precisa antes da primeira linha

  • Recurso e endereço. Recurso é uma coisa do seu sistema: uma tarefa, um usuário, um pedido. O endereço nomeia essa coisa com substantivo. /tarefas é a coleção inteira, /tarefas/1 é a tarefa de número 1. Ação na URL, como /buscarTarefa, joga fora metade do combinado.
  • Verbo, ou método HTTP. A ação mora aqui. Pela documentação da MDN: GET pede a representação de um recurso, POST submete dados a um recurso e costuma mudar o estado do servidor, PUT substitui todas as representações atuais do recurso, PATCH aplica modificações parciais, DELETE remove o recurso.
  • Código de status. Três dígitos na primeira linha da resposta, em cinco faixas: 1xx informativo, 2xx deu certo, 3xx redirecionamento, 4xx erro de quem chamou, 5xx erro do servidor. O código responde sobre o resultado antes de você olhar o corpo, e é justamente essa a parte que a API do exemplo anterior jogava fora ao responder 200 para tudo.
  • Seguro e idempotente. A RFC 9110 chama de seguro o método que só lê, sem alterar o estado do recurso (GET e HEAD), e de idempotente aquele em que várias requisições idênticas têm o mesmo efeito de uma só (GET, HEAD, PUT, DELETE, OPTIONS, TRACE). POST fica fora das duas listas. É por isso que recarregar a página depois de enviar um formulário cria o registro duas vezes.
  • Sem estado. Cada requisição chega completa. O servidor não guarda o que você pediu na chamada anterior para entender a chamada de agora. Quem precisa de identidade manda a credencial em toda requisição, e é essa restrição que permite colocar dez cópias da mesma API atrás de um balanceador sem ninguém perceber.

O contrato por dentro: recursos, verbos e códigos de status

REST organiza a API em torno de recursos: cada coisa do sistema tem um endereço. /clientes é a coleção, /clientes/42 é um cliente. O que você quer fazer com o recurso não vai na URL, vai no verbo HTTP, e cada verbo tem um contrato que o cliente da API pode assumir.

  • Os cinco verbos do dia a dia: GET lê e não altera nada (pode repetir à vontade). POST cria. PUT substitui o recurso inteiro. PATCH altera parte. DELETE remove. Repetir um DELETE dá o mesmo resultado que fazer uma vez. Repetir um POST cria dois. Essa diferença tem nome, idempotência, e aparece em entrevista.
  • O status é a resposta em uma palavra, por família: 2xx deu certo (200 ok, 201 criado, 204 sem corpo), 4xx o erro é do cliente (400 pedido malformado, 404 não existe, 401/403 autenticação e permissão), 5xx o erro é do servidor. O hello world desta página devolve 200 e 404 de verdade.
  • O corpo é JSON e a conversa não tem memória: cada requisição carrega tudo que o servidor precisa (inclusive o token de autenticação, num header), porque REST é stateless: o servidor não guarda o "onde paramos" entre uma chamada e outra.
POST /clientes HTTP/1.1
Content-Type: application/json

{ "nome": "Ana" }

HTTP/1.1 201 Created
Location: /clientes/42

{ "id": 42, "nome": "Ana" }

Requisição em cima, resposta embaixo: verbo + recurso, status + corpo. Toda API REST que você consumir na carreira é variação desse quadro.

Hello world: uma API que devolve 200 e 404

Você precisa do Node.js e do curl, sem instalar pacote nenhum. O servidor inteiro usa só o módulo node:http, que já vem junto.

  1. Confira que o Node.js está na máquina. Qualquer versão 18 ou mais nova serve.

    node --version
  2. Crie a pasta do exercício e entre nela.

    mkdir -p hello-rest && cd hello-rest
  3. Crie um arquivo chamado servidor.js e cole isto dentro:

    const http = require('node:http');
    
    const tarefas = new Map([
      [1, { id: 1, titulo: 'Estudar os verbos do HTTP', feita: false }]
    ]);
    let proximoId = 2;
    
    const server = http.createServer((req, res) => {
      res.setHeader('Content-Type', 'application/json; charset=utf-8');
      const url = new URL(req.url, `http://${req.headers.host}`);
      const partes = url.pathname.split('/').filter(Boolean);
    
      if (partes[0] !== 'tarefas') {
        res.writeHead(404);
        res.end(JSON.stringify({ erro: 'rota inexistente' }));
        return;
      }
    
      // coleção: /tarefas
      if (partes.length === 1) {
        if (req.method === 'GET') {
          res.writeHead(200);
          res.end(JSON.stringify([...tarefas.values()]));
          return;
        }
        if (req.method === 'POST') {
          let corpo = '';
          req.on('data', (p) => { corpo += p; });
          req.on('end', () => {
            let dados;
            try {
              dados = JSON.parse(corpo || '{}');
            } catch {
              res.writeHead(400);
              res.end(JSON.stringify({ erro: 'corpo nao e JSON valido' }));
              return;
            }
            if (typeof dados.titulo !== 'string' || dados.titulo.trim() === '') {
              res.writeHead(400);
              res.end(JSON.stringify({ erro: 'campo titulo e obrigatorio' }));
              return;
            }
            const nova = { id: proximoId++, titulo: dados.titulo, feita: false };
            tarefas.set(nova.id, nova);
            res.writeHead(201, { Location: `/tarefas/${nova.id}` });
            res.end(JSON.stringify(nova));
          });
          return;
        }
        res.writeHead(405, { Allow: 'GET, POST' });
        res.end(JSON.stringify({ erro: 'metodo nao permitido nesta rota' }));
        return;
      }
    
      // item: /tarefas/:id
      const id = Number(partes[1]);
      if (req.method === 'GET') {
        if (!tarefas.has(id)) {
          res.writeHead(404);
          res.end(JSON.stringify({ erro: 'tarefa nao encontrada', id }));
          return;
        }
        res.writeHead(200);
        res.end(JSON.stringify(tarefas.get(id)));
        return;
      }
      if (req.method === 'DELETE') {
        if (!tarefas.has(id)) {
          res.writeHead(404);
          res.end(JSON.stringify({ erro: 'tarefa nao encontrada', id }));
          return;
        }
        tarefas.delete(id);
        res.removeHeader('Content-Type');
        res.writeHead(204);
        res.end();
        return;
      }
      res.writeHead(405, { Allow: 'GET, DELETE' });
      res.end(JSON.stringify({ erro: 'metodo nao permitido nesta rota' }));
    });
    
    server.listen(3000, () => {
      console.log('API no ar em http://localhost:3000');
    });
  4. Suba o servidor. O terminal imprime uma linha e fica ocupado, o que é o comportamento certo.

    node servidor.js
  5. Abra um segundo terminal, deixando o primeiro rodando, e faça a chamada de um recurso que existe. A opção -i manda o curl mostrar os cabeçalhos da resposta, que é onde mora o código de status. A opção -s tira a barra de progresso.

    curl -i -s http://localhost:3000/tarefas/1
  6. Agora peça uma tarefa que não existe, no mesmo servidor e no mesmo formato de rota. Só o número muda.

    curl -i -s http://localhost:3000/tarefas/999
  7. Exercite os outros verbos, ainda no segundo terminal. O POST cria e responde 201 com o cabeçalho Location. O DELETE apaga e responde 204, sem corpo.

    curl -i -s -X POST http://localhost:3000/tarefas \
      -H "Content-Type: application/json" \
      -d '{"titulo":"Testar o 201"}'
    
    curl -i -s -X DELETE http://localhost:3000/tarefas/2
  8. Volte ao primeiro terminal e pare o servidor com Ctrl + C. As tarefas moram na memória do processo, então tudo que você criou some junto.

A prova de que funcionou. Duas chamadas ao mesmo servidor, na mesma rota, e a primeira linha de cada resposta traz um código diferente. O passo 5 tem que imprimir na tela:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8

{"id":1,"titulo":"Estudar os verbos do HTTP","feita":false}
E o passo 6, com a mesma rota e outro número:
HTTP/1.1 404 Not Found
Content-Type: application/json; charset=utf-8

{"erro":"tarefa nao encontrada","id":999}
Esses dois blocos na tela são o combinado inteiro funcionando: quem chamou não precisou ler o corpo para saber que a segunda deu errado, o 404 já disse. Se aparecer Connection refused, o servidor do passo 4 não está rodando ou você fechou o primeiro terminal. Se aparecer o JSON sem nenhuma linha HTTP/1.1 em cima, faltou o -i no comando.

O que REST não é

A confusão mais comum é tratar "REST" e "API que devolve JSON por HTTP" como sinônimos. REST é um estilo de arquitetura descrito por Roy Fielding no capítulo 5 da tese de doutorado dele, em 2000, e vem com seis restrições nomeadas: cliente-servidor, sem estado, cache, interface uniforme, sistema em camadas e código sob demanda, essa última opcional. JSON não aparece na lista. Uma API REST pode responder XML, HTML ou texto puro sem deixar de ser REST.

O caminho inverso também engana. Trocar o formato para JSON e usar os verbos certos ainda deixa de fora boa parte da interface uniforme. Em outubro de 2008, o próprio Fielding publicou um texto chamado "REST APIs must be hypertext-driven" que abre com a reclamação de que ele estava ficando frustrado com a quantidade de gente chamando qualquer interface sobre HTTP de API REST. No mercado, "API REST" virou o nome de HTTP com verbo e status, e é isso que vai estar escrito na vaga que você quer. Saber de onde vem a palavra evita discussão vazia em revisão de código.

Fontes

O hello world desta página foi executado em 15/08/2026, no Node.js v22.23.0 com curl 8.18.0. As oito etapas rodaram na ordem descrita. GET /tarefas/1 respondeu HTTP/1.1 200 OK com o JSON da tarefa, GET /tarefas/999 respondeu HTTP/1.1 404 Not Found, o POST respondeu HTTP/1.1 201 Created com Location: /tarefas/2, o DELETE respondeu HTTP/1.1 204 No Content sem corpo, o mesmo DELETE repetido respondeu 404, o POST sem título respondeu 400 Bad Request e o DELETE /tarefas na coleção respondeu 405 Method Not Allowed com Allow: GET, POST. Os dois blocos mostrados no quadro da prova são a saída literal do terminal.

Para continuar

O contraponto direto é GraphQL, que resolve o mesmo problema por outro caminho: em vez de vários endereços com formato fixo, um endereço só onde quem chama descreve os campos que quer. Do lado de quem constrói, Node.js é a plataforma que rodou o servidor desta página, e vale conhecer o resto dela antes de trocar o node:http cru por um framework. O caminho completo, com as vinte tecnologias e a ordem sugerida, está no mapa da stack web.

O curso desta trilha está em preparação. A lista paga menos.

Os tutoriais daqui continuam gratuitos. O que está a caminho é um curso pago que transforma esse mapa em formação guiada, na ordem certa e com projeto. Deixe seu e-mail: você recebe os estudos novos gratuitos e, no lançamento, o desconto de quem entrou na lista primeiro.