Entendendo o que é uma API RESTful
Uma API RESTful é uma interface de comunicação entre sistemas construída segundo os princípios arquiteturais do estilo REST, sigla para o termo em inglês Representational State Transfer. Diferente do que muitos pensam, o REST não é um protocolo ou um padrão de codificação, mas sim um estilo arquitetural. O termo foi cunhado por Roy Thomas Fielding em sua tese de doutorado no ano 2000, intitulada “Architectural Styles and the Design of Network-based Software Architectures”.
Segundo Fielding (2000), o REST surgiu da necessidade de entender como a Web (ou Internet) deveria se comportar para manter sua escalabilidade e longevidade. Em termos práticos, uma API RESTful permite que um cliente, como um navegador, aplicativo mobile ou sistema externo, interaja com recursos de um servidor por meio de representações desses recursos. Em uma livraria, por exemplo, os recursos podem ser livros, autores, pedidos, clientes e categorias. Esses recursos são acessados normalmente por meio de URLs, como /livros, /livros/1 ou /pedidos.
A ideia central de REST é que a comunicação seja simples, padronizada, escalável e baseada nos mecanismos já existentes da Web, principalmente o protocolo HTTP. Por isso, quando trabalhamos com APIs RESTful, costumamos usar métodos como GET, POST, PUT, PATCH e DELETE, além de códigos de status HTTP como 200, 201, 404 e 500.
Origem do REST e a contribuição de Roy Fielding
Roy Fielding (Figura 1) é um cientista da computação norte-americano e participou da especificação do protocolo HTTP/1.1, além de outras contribuições para padrões fundamentais da Web. Em sua tese, Fielding não apresentou REST como uma biblioteca, framework ou protocolo novo, mas como um conjunto de restrições arquiteturais. Para ele, a arquitetura da Web precisava favorecer propriedades como escalabilidade, simplicidade, visibilidade da comunicação, portabilidade e independência entre componentes (Fielding, 2000).

Fonte: https://www.red-gate.com/simple-talk/opinion/geek-of-the-week/roy-fielding-geek-of-the-week/
Estamos destacando esta questão, porque muita gente confunde REST com “qualquer API que usa JSON e HTTP” (o que é um erro, diga-se). Uma API pode usar HTTP e JSON e ainda assim não seguir bem os princípios REST. Para ser RESTful, a API deve respeitar certas restrições arquiteturais, como separação entre cliente e servidor, ausência de estado no servidor entre requisições, uso de cache, interface uniforme e possibilidade de organização em camadas.
Para conhecer mais sobre APIs em geral, leia nosso outro artigo sobre o assunto: clique aqui
Restrições arquiteturais
Um sistema é considerado RESTful quando ele adere estritamente ao conjunto de restrições (constraints) definidas por Fielding (2000). São elas:
- Client-Server (Cliente-Servidor): separação de preocupações. O cliente não cuida do armazenamento de dados, e o servidor não cuida da interface do usuário. Isso permite que ambos evoluam de forma independente.
- Stateless (Sem Estado): cada requisição do cliente para o servidor deve conter todas as informações necessárias para entender e processar o pedido. O servidor não deve armazenar nenhum contexto de sessão.
- Cacheable (Cacheável): as respostas devem se autodefinir como cacheáveis ou não, evitando que os clientes enviem dados irrelevantes ou obsoletos para o servidor.
- Uniform Interface (Interface Uniforme): este é o ponto central. Exige identificação dos recursos (URIs), manipulação de recursos através de representações (JSON, XML etc.), mensagens autodescritivas e o uso de HATEOAS (Hypermedia as the Engine of Application State).
- Layered System (Sistema em Camadas): o cliente não consegue saber se está conectado diretamente ao servidor final ou a um intermediário (como um balanceador de carga ou proxy).
- Code on Demand (Opcional): o servidor pode estender temporariamente a funcionalidade do cliente enviando código executável (como scripts de JavaScript).
A Figura 2 traz uma representação visual do funcionamento de uma aplicação baseada em API REST.

Fonte: https://mannhowie.com/rest-api
Vantagens e desvantagens
APIs RESTful são populares porque aproveitam padrões amplamente conhecidos da Web. Isso reduz a curva de aprendizado, facilita integração entre sistemas e permite o uso de ferramentas comuns, como navegadores, proxies, caches HTTP e gateways. Isso traz simplicidade para o desenvolvedor.
Outra vantagem é a escalabilidade. A restrição stateless permite distribuir requisições entre múltiplos servidores. A separação cliente-servidor permite que frontend e backend evoluam separadamente. O uso de cache pode melhorar desempenho e reduzir custos operacionais.
Além disso, APIs RESTful tendem a ser tecnologicamente independentes. Um servidor escrito em JavaScript pode ser consumido por um cliente em Python, Java, Kotlin, Swift ou qualquer outra linguagem capaz de fazer requisições HTTP.
Por outro lado, em alguns cenários, usar uma API RESTful pode gerar excesso de requisições. Por exemplo, para montar uma tela complexa, o código cliente pode precisar de diversas rotas para compor as informações ao usuário. Esse problema é conhecido como over-fetching ou under-fetching, dependendo do caso.
Outro ponto é que muitas APIs chamadas RESTful não implementam todos os princípios REST. O HATEOAS, por exemplo, é frequentemente ignorado. Isso cria uma diferença entre o REST acadêmico proposto por Fielding e o “REST pragmático” usado na indústria.
Também existe o risco de projetar rotas com aparência REST, mas sem boa semântica. Usar uma rota definida como POST /fazerTudo viola a clareza esperada de uma API orientada a recursos. Como observa Fowler (2010), o uso disciplinado de recursos, métodos HTTP e hipermídia diferencia uma API madura de uma API meramente baseada em chamadas remotas.
Exemplo em Javascript
Vamos elaborar um exemplo básico para entender como funciona uma API que utiliza o estilo REST. Para isso, vamos usar o cenário de uma livraria.
Antes de criar a API, é necessário configurar o projeto em Node.js para usar o padrão moderno de módulos do JavaScript (o padrão ES Modules). Nesse padrão usamos import e export, em vez de require e module.exports.
Crie uma pasta para o projeto dentro do diretório Documentos (sugestão nossa, mas você pode criar onde preferir):
mkdir api-livraria-restful
cd api-livraria-restful
npm init -y
npm install expressDepois, edite o arquivo package.json para ficar similar ao código abaixo. Preste atenção à versão do Express (linha 12). Você pode manter a versão que foi instalada na linha npm install express anterior. Não precisa mudar para a versão que está no exemplo.
{
"name": "api-livraria-restful",
"version": "1.0.0",
"description": "Exemplo didático de API RESTful para uma livraria",
"type": "module",
"main": "servidor.js",
"scripts": {
"dev": "node servidor.js",
"cliente": "node cliente.js"
},
"dependencies": {
"express": "^4.18.3"
}
}A propriedade mais importante que adicionamos ao arquivo está na linha 5, que é:
"type": "module"Ela informa ao Node.js que os arquivos .js devem usar o padrão ES Modules. Assim, poderemos escrever as importações da seguinte maneira:
import express from "express";Criando o servidor
Depois da configuração, crie o arquivo servidor.js na pasta api-livraria-restful e digite o código abaixo:
import express from "express";
const app = express();
const porta = 3000;
app.use(express.json());
let livros = [
{
id: 1,
titulo: "Dom Casmurro",
autor: "Machado de Assis",
preco: 39.9,
estoque: 8
},
{
id: 2,
titulo: "Capitães da Areia",
autor: "Jorge Amado",
preco: 44.5,
estoque: 5
}
];
app.get("/livros", (requisicao, resposta) => {
console.log("Consulta realizada: lista de livros");
resposta.status(200).json({
mensagem: "Livros encontrados com sucesso.",
dados: livros
});
});
app.get("/livros/:id", (requisicao, resposta) => {
const id = Number(requisicao.params.id);
const livroEncontrado = livros.find((livro) => livro.id === id);
if (!livroEncontrado) {
console.log(`Livro com id ${id} não encontrado.`);
return resposta.status(404).json({
mensagem: "Livro não encontrado."
});
}
console.log(`Consulta realizada: livro ${livroEncontrado.titulo}`);
resposta.status(200).json({
mensagem: "Livro encontrado com sucesso.",
dados: livroEncontrado
});
});
app.post("/livros", (requisicao, resposta) => {
const novoLivro = {
id: livros.length + 1,
titulo: requisicao.body.titulo,
autor: requisicao.body.autor,
preco: requisicao.body.preco,
estoque: requisicao.body.estoque
};
livros.push(novoLivro);
console.log(`Livro cadastrado: ${novoLivro.titulo}`);
resposta.status(201).json({
mensagem: "Livro cadastrado com sucesso.",
dados: novoLivro
});
});
app.put("/livros/:id", (requisicao, resposta) => {
const id = Number(requisicao.params.id);
const indiceLivro = livros.findIndex((livro) => livro.id === id);
if (indiceLivro === -1) {
console.log(`Tentativa de atualização falhou. Livro ${id} não encontrado.`);
return resposta.status(404).json({
mensagem: "Livro não encontrado."
});
}
const livroAtualizado = {
id,
titulo: requisicao.body.titulo,
autor: requisicao.body.autor,
preco: requisicao.body.preco,
estoque: requisicao.body.estoque
};
livros[indiceLivro] = livroAtualizado;
console.log(`Livro atualizado: ${livroAtualizado.titulo}`);
resposta.status(200).json({
mensagem: "Livro atualizado com sucesso.",
dados: livroAtualizado
});
});
app.patch("/livros/:id", (requisicao, resposta) => {
const id = Number(requisicao.params.id);
const livroEncontrado = livros.find((livro) => livro.id === id);
if (!livroEncontrado) {
console.log(`Tentativa de alteração parcial falhou. Livro ${id} não encontrado.`);
return resposta.status(404).json({
mensagem: "Livro não encontrado."
});
}
Object.assign(livroEncontrado, requisicao.body);
console.log(`Livro alterado parcialmente: ${livroEncontrado.titulo}`);
resposta.status(200).json({
mensagem: "Livro alterado parcialmente com sucesso.",
dados: livroEncontrado
});
});
app.delete("/livros/:id", (requisicao, resposta) => {
const id = Number(requisicao.params.id);
const quantidadeAntes = livros.length;
livros = livros.filter((livro) => livro.id !== id);
if (livros.length === quantidadeAntes) {
console.log(`Tentativa de exclusão falhou. Livro ${id} não encontrado.`);
return resposta.status(404).json({
mensagem: "Livro não encontrado."
});
}
console.log(`Livro com id ${id} removido com sucesso.`);
resposta.status(200).json({
mensagem: "Livro removido com sucesso."
});
});
app.listen(porta, () => {
console.log(`API da livraria executando em http://localhost:${porta}`);
});Para executar o código, rode o seguinte comando no terminal do SO (como estou usando Linux, tenho o Bash):
npm run devCaso tudo funcione corretamente, o resultado que esperamos no terminal é:
API da livraria executando em http://localhost:3000Esse primeiro exemplo já demonstra uma ideia importante: a API organiza as operações em torno do recurso /livros. As ações não aparecem no nome da rota, mas nos métodos HTTP.
Criando o cliente
Agora precisamos de uma aplicação cliente para testar a nossa API RESTful. Vamos criar o arquivo cliente.js, mas antes verifique se o servidor ainda está rodando (caso não esteja, rode o comando de inicialização de novo: npm run dev).
Criado o arquivo cliente.js, digite o código abaixo:
const enderecoBase = "http://localhost:3000";
async function listarLivros() {
const resposta = await fetch(`${enderecoBase}/livros`);
const corpo = await resposta.json();
console.log("Resultado da listagem de livros:");
console.log(corpo);
}
async function cadastrarLivro() {
const resposta = await fetch(`${enderecoBase}/livros`, {
method: "POST",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
titulo: "O Cortiço",
autor: "Aluísio Azevedo",
preco: 35.9,
estoque: 12
})
});
const corpo = await resposta.json();
console.log("Resultado do cadastro de livro:");
console.log(corpo);
}
async function alterarPrecoLivro() {
const resposta = await fetch(`${enderecoBase}/livros/1`, {
method: "PATCH",
headers: {
"Content-Type": "application/json"
},
body: JSON.stringify({
preco: 42.9
})
});
const corpo = await resposta.json();
console.log("Resultado da alteração parcial:");
console.log(corpo);
}
async function executarFluxoDaLivraria() {
await listarLivros();
await cadastrarLivro();
await alterarPrecoLivro();
await listarLivros();
}
executarFluxoDaLivraria();Abra outra sessão do terminal e execute o comando de inicialização:
npm run clienteEsse cliente simula um pequeno fluxo da livraria: listar o catálogo, cadastrar um novo livro, alterar parcialmente o preço de um livro e listar novamente o catálogo. A saída aparecerá no console.log, permitindo observar o comportamento da API.
Analisando o exemplo conforme as restrições REST
Princípio 1: cliente-servidor
Em nosso exemplo, o arquivo servidor.js funciona como a provedora dos recursos da API, enquanto o arquivo cliente.js representa um consumidor da API (os nomes dos arquivos não poderiam ser mais autoexplicativos, não é?). Como esta é uma aplicação básica para demonstração dos princípios, essa sepração é suficiente para trabalharmos.
Cabe ressaltar que o arquivo cliente.js poderia ser substituído por um frontend em React, um aplicativo mobile ou outro sistema qualquer, que o servidor continuaria expondo os mesmos recursos.
Essa separação é vantajosa porque permite que equipes diferentes trabalhem em partes distintas do sistema. Contudo, há um problema aqui: a comunicação pela rede introduz latência, falhas de conexão e necessidade de contratos bem definidos entre cliente e servidor.
Princípio 2: stateless
Nossa API não tem código para autenticação, mas se futuramente tiver, cada requisição deveria enviar um token de acesso. Esse princípio determina que o servidor não deveria depender de uma sessão interna invisível para entender quem é o usuário.
A vantagem do modelo stateless é a escalabilidade. Como cada requisição é independente, diferentes servidores podem processar requisições diferentes. A desvantagem é que o cliente precisa reenviar informações contextuais, o que pode aumentar o tamanho das requisições e exigir mais cuidado com segurança.
Princípio 3: cache
O código original não contempla essa questão. Contudo, em uma livraria, a lista de livros em destaque poderia ser armazenada em cache por alguns minutos, pois não muda a cada segundo. Sendo assim, poderíamos complementar o arquivo servidor.js com o código abaixo:
app.get("/livros-em-destaque", (requisicao, resposta) => {
const livrosEmDestaque = livros.filter((livro) => livro.estoque > 0);
console.log("Consulta realizada: livros em destaque.");
resposta.set("Cache-Control", "public, max-age=60");
resposta.status(200).json({
mensagem: "Livros em destaque encontrados com sucesso.",
dados: livrosEmDestaque
});
});A linha 6 informa que a resposta pode ser armazenada em cache por 60 segundos:
resposta.set("Cache-Control", "public, max-age=60");A vantagem do cache é melhorar desempenho e reduzir carga no servidor. A desvantagem é o risco de entregar dados desatualizados. Por isso, nem todo recurso deve ser cacheado. Dados de estoque, pagamento e pedidos exigem mais cautela.
E, ah, observe que modificamos apenas o arquivo servidor.js, mas não adicionamos uma chamada à função no arquivo cliente.js. Ou seja, mesmo se você rodar a API novamente, não verá diferença nos resultados. Para ver alguma alteração, adicione o código abaixo no arquivo cliente.js:
async function listarLivrosDestaque() {
const resposta = await fetch(`${enderecoBase}/livros-em-destaque`);
const corpo = await resposta.json();
console.log("Resultado da listagem de livros em destaque:");
console.log(corpo);
}
// Adicione esta linha dentro do método 'executarFluxoDaLivraria' que começa na linha 48 do código original
await listarLivrosDestaque();Princípio 4: interface uniforme
Fielding (2000) descreve quatro subprincípios importantes da interface uniforme: 1) os recursos devem ser identificados por URIs; 2) os recursos devem ser manipulados por representações, como JSON ou XML; 3) as mensagens devem ser autodescritivas, isto é, devem conter informações suficientes para serem interpretadas; 4) a hipermídia deve orientar o estado da aplicação, princípio conhecido como HATEOAS.
No nosso exemplo, a rota /livros/1 identifica um recurso. O JSON enviado no corpo da requisição representa o estado desejado do recurso. O cabeçalho Content-Type: application/json ajuda a mensagem a ser autodescritiva.
Ficou faltando apenas a questão de orientar o estado da aplicação. Para implementar algo assim, poderíamos modificar o servidor.js para retornar uma resposta com links, aproximando a API do princípio HATEOAS:
app.get("/livros/:id/detalhes", (requisicao, resposta) => {
const id = Number(requisicao.params.id);
const livroEncontrado = livros.find((livro) => livro.id === id);
if (!livroEncontrado) {
console.log(`Livro ${id} não encontrado na rota de detalhes.`);
return resposta.status(404).json({
mensagem: "Livro não encontrado."
});
}
console.log(`Detalhes consultados: ${livroEncontrado.titulo}`);
resposta.status(200).json({
mensagem: "Detalhes do livro encontrados com sucesso.",
dados: livroEncontrado,
links: {
self: `/livros/${livroEncontrado.id}/detalhes`,
catalogo: "/livros",
atualizar: `/livros/${livroEncontrado.id}`,
remover: `/livros/${livroEncontrado.id}`
}
});
});O campo links informa ao cliente quais ações ou navegações são possíveis a partir daquele recurso. Essa ideia é central no estilo REST, embora muitas APIs ditas “RESTful” no mercado não implementem HATEOAS de forma completa.
Princípio 5: sistema em camadas
REST também prevê a possibilidade de organizar o sistema em camadas. No código didático que criamos, ainda temos uma estrutura simples e pouco separada. Porém, poderíamos separar responsabilidades em arquivos diferentes, caso quiséssemos ou precisássemos.
Essa restrição favorece escalabilidade, segurança e manutenibilidade. Em sistemas reais, uma requisição pode passar por várias camadas.
Princípio 6: código sob demanda
Em APIs JSON tradicionais, esse princípio raramente aparece de forma direta. Um exemplo clássico seria um servidor entregar uma página HTML junto com arquivos JavaScript que serão executados no navegador. Porém, como esse princípio é opcional, uma API pode ser considerada RESTful mesmo sem utilizá-lo.
Conclusão
Uma API RESTful é mais do que uma API HTTP que retorna JSON. Ela é uma forma de projetar sistemas distribuídos usando recursos identificáveis, representações, métodos HTTP, mensagens autodescritivas e separação clara entre cliente e servidor.
Vamos lembrar que Roy Fielding definiu REST como um estilo arquitetural, não como uma tecnologia específica. Por isso, aprender REST exige compreender suas restrições (cliente-servidor, stateless, cache, interface uniforme, sistema em camadas e código sob demanda opcional). Essa base é suficiente para que estudantes iniciem a construção de APIs mais organizadas, previsíveis e compatíveis com os fundamentos da Web.
Obrigado pela leitura e bons estudos!
Referências
FIELDING, Roy Thomas. Architectural Styles and the Design of Network-based Software Architectures. 2000. 162 f. Tese (Doutorado em Informação e Ciência da Computação) – University of California, Irvine, 2000.
FOWLER, Martin. Richardson Maturity Model: steps toward the glory of REST. 2010. Disponível em: <https://martinfowler.com/articles/richardsonMaturityModel.html>. Acesso em: 24 abr. 2026.


