Criando uma aplicação JS com framework Express.js
O Express.js é um framework web para Node.js usado para construir servidores HTTP, APIs e aplicações web com menos código repetitivo. A própria documentação o define como um framework “rápido (…) e minimalista” para Node.js, isto é, uma ferramenta que fornece os mecanismos essenciais para lidar com requisições, respostas, rotas e middlewares, sem impor uma arquitetura rígida ao projeto (Express, 2026).
Essa característica torna o Express.js adequado para projetos didáticos, porque permite compreender com clareza o caminho percorrido por uma requisição HTTP. Em termos de engenharia de software, esse processo ajuda a perceber que uma aplicação web não é apenas “um código que roda”, mas um sistema organizado por responsabilidades, contratos de comunicação e pontos de extensão (Pressman; Maxim, 2016).
Neste artigo, será desenvolvido um pequeno projeto de backlog de obras — filmes, séries ou animações — usando CRUD em memória. A ideia é que o sistema funcione como uma primeira versão de API, ainda sem banco de dados e sem MVC, mas já preparada conceitualmente para uma futura refatoração arquitetural.
O que é o Express.js?
Express.js é uma biblioteca/framework construída sobre o ambiente Node.js para facilitar o desenvolvimento de aplicações HTTP. Sem um framework, o programador precisa lidar diretamente com detalhes de criação do servidor, análise da URL, tratamento dos métodos HTTP, leitura do corpo da requisição e montagem da resposta. Com o Express, essas tarefas são organizadas em uma API mais simples, baseada em rotas, middlewares e objetos como req e res (Express, 2026).
A documentação oficial mostra que uma aplicação Express nasce a partir da função express(), que cria uma instância da aplicação. Essa instância permite configurar middlewares, definir rotas e iniciar o servidor para escutar requisições HTTP em uma porta específica (Express, 2026).
Na prática, o Express.js atua como uma camada intermediária entre o protocolo HTTP e a lógica da aplicação. Isso é importante porque o protocolo HTTP define métodos como GET, POST, PUT e DELETE, cada um com semântica própria para transferência, criação, substituição ou remoção de representações de recursos (Fielding; Nottingham; Reschke, 2022). O método PATCH, por sua vez, foi padronizado para aplicar modificações parciais em um recurso existente, diferenciando-se do PUT, que representa substituição mais completa do recurso (Dusseault; Snell, 2010).
Para que serve o Express.js?
O Express.js serve principalmente para construir aplicações web e APIs. Em uma API, o servidor normalmente recebe requisições de um frontend, aplicativo móvel, cliente HTTP ou outro sistema, processa essas requisições e devolve respostas em formatos estruturados, como JSON. Esse formato é especialmente útil em sistemas distribuídos, pois separa a interface visual, que pode estar no navegador, da lógica de negócio, que fica no servidor.
Em termos arquiteturais, essa separação aproxima o projeto do modelo cliente-servidor. Segundo Sommerville (2011), sistemas distribuídos podem organizar componentes em clientes e servidores, em que os clientes solicitam serviços e os servidores os fornecem. No caso deste artigo, o frontend será entendido como cliente e a API Express como servidor responsável por gerenciar o backlog de obras.
O domínio escolhido é simples: um backlog de filmes, séries ou animações. O usuário do sistema poderá escolher o tipo de obra a ser trabalhada. Para filmes, serão armazenados titulo, ano e nota. Para séries, serão armazenados titulo, temporadas e nota. Para animações, serão armazenados titulo, estudio e nota. Como os dados ficarão em memória, eles serão apagados sempre que o servidor for reiniciado.
Como iniciar um projeto JavaScript com Express.js?
Para criar o projeto, é necessário ter o Node.js instalado. Na versão atual do Express 5.x, a documentação informa a exigência de Node.js 18 ou superior (Express, 2026). Para usar módulos modernos do JavaScript, o Node.js permite configurar o projeto com "type": "module" no arquivo package.json, fazendo com que arquivos .js sejam interpretados como ECMAScript Modules (Node.js, 2026).
Antes de continuar, um aviso: estamos usando o sistema operacional Linux OpenSuse Tumbleweed. Portanto, todos os comandos apresentados foram executados no terminal Bash nativo. Caso você esteja em outro sistema, verifique a melhor opção que você tem à disposição.
A criação inicial pode ser feita assim (estou trabalhando a partir da pasta Documentos):
mkdir backlog-obras-api
cd backlog-obras-api
npm init -y
npm install expressDepois, ajuste o arquivo package.json:
{
"name": "backlog-obras-api",
"version": "1.0.0",
"description": "API simples com Express.js para backlog de obras",
"type": "module",
"main": "servidor.js",
"scripts": {
"dev": "node --watch servidor.js",
"start": "node servidor.js"
},
"dependencies": {
"express": "^5.2.1"
}
}O campo "type": "module" permite escrever importações com import, em vez de require. Essa escolha aproxima o projeto do padrão moderno do JavaScript, usado no ecossistema atual de desenvolvimento web.
Ciclo de requisição e resposta
O ciclo de requisição e resposta começa quando um cliente envia uma requisição HTTP ao servidor. Essa requisição contém, entre outros elementos, um método HTTP, uma URL, cabeçalhos e, em alguns casos, um corpo em JSON. O servidor Express recebe essa requisição e a representa pelo objeto req. A resposta que será enviada de volta ao cliente é representada pelo objeto res.
No Express, uma rota liga um método HTTP e um caminho a uma função manipuladora. A documentação oficial mostra que o roteamento é definido por métodos do objeto app correspondentes aos métodos HTTP, como app.get() para GET e app.post() para POST (Express, 2026).
O ciclo geral pode ser representado assim:
Frontend
↓ envia requisição HTTP
Middleware global
↓ trata JSON, log ou validações gerais
Rota
↓ executa a regra da operação
Middleware de erro, se necessário
↓
Resposta JSON ao frontend
Esse ciclo é importante para entendermos a comunicação entre as diversas partes de uma API.
Rotas e endpoints
Uma rota é a associação entre método HTTP, caminho e função de tratamento. Um endpoint é um ponto de acesso da API. No projeto deste artigo, a API terá os seguintes endpoints:
| Método | Endpoint | Função |
|---|---|---|
GET | /obras | Lista todas as obras |
GET | /obras/:id | Busca uma obra pelo código |
POST | /obras | Cadastra uma nova obra |
PUT | /obras | Substitui uma obra inteira |
PATCH | /obras | Atualiza parcialmente uma obra |
DELETE | /obras | Remove uma obra |
Neste projeto, POST, PUT, PATCH e DELETE receberão JSON no corpo da requisição. Embora seja comum ver DELETE /obras/:id, aqui será usado DELETE /obras com corpo { "id": 1 } para atender ao requisito didático de receber JSON também no método DELETE.
Middlewares
Middleware é uma função executada entre a chegada da requisição e a resposta final. Em Express.js, middlewares podem ler a requisição, alterar objetos, encerrar a resposta ou chamar next() para passar a requisição ao próximo estágio do ciclo. A documentação oficial explica que middlewares podem ser usados em nível de aplicação, em nível de roteador e também para tratamento de erros (Express, 2026).
No projeto, serão usados três tipos principais de middleware. O primeiro é express.json(), que interpreta o corpo da requisição como JSON. O segundo é um middleware de log, que registra no terminal o método e o caminho acessado. O terceiro é um middleware de erro, usado para padronizar respostas quando algo falha.
Essa separação é útil para evitar mistura de responsabilidades dentro do código. A rota deve se concentrar na regra da operação, enquanto o middleware deve cuidar de tarefas transversais, como leitura de JSON, autenticação, validação, log ou tratamento de erro. Essa ideia se aproxima do princípio de separação de responsabilidades, frequente em engenharia de software, pois reduz acoplamento e facilita manutenção (Sommerville, 2011).
Projeto: API de backlog de obras em memória
O código a seguir implementa uma API simples. Por padrão, o sistema está configurado para trabalhar com filmes. Para usar séries ou animações, basta alterar a constante TIPO_OBRA.
Crie o arquivo servidor.js e digite o código abaixo:
import express from "express";
const app = express();
const PORTA = 3000;
/*
Tipos possíveis:
- "filmes"
- "series"
- "animacoes"
Para trocar o tipo da obra, altere apenas esta constante.
*/
const TIPO_OBRA = "filmes";
let proximoId = 1;
const obras = [];
app.use(express.json());
app.use((req, res, next) => {
const dataHora = new Date().toISOString();
console.log(`[${dataHora}] ${req.method} ${req.url}`);
next();
});A primeira parte do código importa o Express, cria a aplicação e define a porta do servidor. Em seguida, a constante TIPO_OBRA determina qual estrutura de dados será validada. Essa decisão deixa o projeto flexível sem exigir herança, classes ou padrões complexos. Para um primeiro contato com APIs, essa simplicidade é adequada.
O trecho app.use(express.json()) é um middleware essencial. Ele permite que o Express interprete o corpo da requisição como JSON e disponibilize os dados em req.body. Sem esse middleware, os dados enviados pelo frontend em POST, PUT, PATCH e DELETE não seriam lidos corretamente.
O middleware de log é executado em todas as requisições. Ele não finaliza a resposta do servidor, apenas registra as informações de data e hora, método e URL, para depois chamar next(). Esse detalhe é importante em Express.js, pois quando um middleware não envia uma resposta, ele deve encaminhar o fluxo para o próximo middleware ou rota.
Cotinuando o código no mesmo arquivo servidor.js, criaremos agora as funções validarCamposObrigatorios, validarNota, montarObra e encontrarIndicePorId. Os códigos para estas funções está descrito abaixo:
function obterCamposObrigatorios() {
if (TIPO_OBRA === "filmes") {
return ["titulo", "ano", "nota"];
}
if (TIPO_OBRA === "series") {
return ["titulo", "temporadas", "nota"];
}
if (TIPO_OBRA === "animacoes") {
return ["titulo", "estudio", "nota"];
}
throw new Error("Tipo de obra inválido.");
}
function validarCamposObrigatorios(dados) {
const camposObrigatorios = obterCamposObrigatorios();
for (const campo of camposObrigatorios) {
if (dados[campo] === undefined || dados[campo] === null || dados[campo] === "") {
return `O campo '${campo}' é obrigatório para ${TIPO_OBRA}.`;
}
}
return null;
}
function validarNota(nota) {
if (typeof nota !== "number" || nota < 0 || nota > 10) {
return "A nota deve ser um número entre 0 e 10.";
}
return null;
}
function montarObra(dados) {
if (TIPO_OBRA === "filmes") {
return {
id: proximoId++,
titulo: dados.titulo,
ano: dados.ano,
nota: dados.nota
};
}
if (TIPO_OBRA === "series") {
return {
id: proximoId++,
titulo: dados.titulo,
temporadas: dados.temporadas,
nota: dados.nota
};
}
return {
id: proximoId++,
titulo: dados.titulo,
estudio: dados.estudio,
nota: dados.nota
};
}
function encontrarIndicePorId(id) {
return obras.findIndex((obra) => obra.id === Number(id));
}As funções validarCamposObrigatorios, validarNota, montarObra e encontrarIndicePorId foram separadas para manter as rotas mais legíveis. Essa organização antecipa uma futura refatoração para MVC. Em uma versão posterior, a lista obras poderia ser substituída por um banco de dados, as funções de validação poderiam ir para uma camada de serviço e as rotas poderiam ser movidas para arquivos específicos.
Chegando na reta final do servidor, vamos elaborar as rotas previstas para a aplicação Express. No script JS abaixo, a rota GET /obras retorna JSON com a quantidade de obras e os dados armazenados. Isso simula a resposta que um frontend receberia para montar uma tabela, lista ou conjunto de cards. Por outro lado, a rota POST /obras recebe um JSON, valida os campos, cria um identificador automático e armazena o objeto em memória.
app.get("/", (req, res) => {
res.status(200).json({
mensagem: "API de backlog de obras com Express.js",
tipoObra: TIPO_OBRA,
endpoints: ["/obras", "/obras/:id"]
});
});
app.get("/obras", (req, res) => {
res.status(200).json({
quantidade: obras.length,
dados: obras
});
});
app.get("/obras/:id", (req, res) => {
const indice = encontrarIndicePorId(req.params.id);
if (indice === -1) {
return res.status(404).json({
erro: "Obra não encontrada."
});
}
res.status(200).json(obras[indice]);
});
app.post("/obras", (req, res) => {
const erroCampos = validarCamposObrigatorios(req.body);
if (erroCampos) {
return res.status(400).json({ erro: erroCampos });
}
const erroNota = validarNota(req.body.nota);
if (erroNota) {
return res.status(400).json({ erro: erroNota });
}
const novaObra = montarObra(req.body);
obras.push(novaObra);
res.status(201).json({
mensagem: "Obra cadastrada com sucesso.",
dados: novaObra
});
});
app.put("/obras", (req, res) => {
const { id } = req.body;
if (!id) {
return res.status(400).json({
erro: "O campo 'id' é obrigatório."
});
}
const indice = encontrarIndicePorId(id);
if (indice === -1) {
return res.status(404).json({
erro: "Obra não encontrada."
});
}
const erroCampos = validarCamposObrigatorios(req.body);
if (erroCampos) {
return res.status(400).json({ erro: erroCampos });
}
const erroNota = validarNota(req.body.nota);
if (erroNota) {
return res.status(400).json({ erro: erroNota });
}
const obraAtualizada = {
...montarObra(req.body),
id: Number(id)
};
proximoId--;
obras[indice] = obraAtualizada;
res.status(200).json({
mensagem: "Obra substituída com sucesso.",
dados: obraAtualizada
});
});
app.patch("/obras", (req, res) => {
const { id, ...camposParaAtualizar } = req.body;
if (!id) {
return res.status(400).json({
erro: "O campo 'id' é obrigatório."
});
}
const indice = encontrarIndicePorId(id);
if (indice === -1) {
return res.status(404).json({
erro: "Obra não encontrada."
});
}
if (camposParaAtualizar.nota !== undefined) {
const erroNota = validarNota(camposParaAtualizar.nota);
if (erroNota) {
return res.status(400).json({ erro: erroNota });
}
}
obras[indice] = {
...obras[indice],
...camposParaAtualizar,
id: Number(id)
};
res.status(200).json({
mensagem: "Obra atualizada parcialmente com sucesso.",
dados: obras[indice]
});
});
app.delete("/obras", (req, res) => {
const { id } = req.body;
if (!id) {
return res.status(400).json({
erro: "O campo 'id' é obrigatório."
});
}
const indice = encontrarIndicePorId(id);
if (indice === -1) {
return res.status(404).json({
erro: "Obra não encontrada."
});
}
const obraRemovida = obras.splice(indice, 1)[0];
res.status(200).json({
mensagem: "Obra removida com sucesso.",
dados: obraRemovida
});
});
app.use((err, req, res, next) => {
console.error(err);
res.status(500).json({
erro: "Erro interno no servidor."
});
});
app.listen(PORTA, () => {
console.log(`Servidor iniciado em http://localhost:${PORTA}`);
});O PUT /obras exige a substituição de todos os campos obrigatoriamente, porque o método representa a substituição completa do elemento manipulado. Já o PATCH /obras permite enviar apenas os campos que devem ser alterados. Essa distinção é coerente com a semântica HTTP.
Finalmente, o DELETE /obras remove uma obra pelo id recebido dentro do JSON e não no endpoint. Embora muitas APIs usem o padrão DELETE /obras/:id, esta versão favorece o objetivo didático do exercício que é mostrar que o servidor pode receber JSON também em uma operação de remoção, desde que o cliente envie o cabeçalho Content-Type: application/json e o servidor esteja preparado para interpretar o corpo da requisição.
Servidor construído, vamos testá-lo. Para executar o servidor, execute no terminal:
npm run devDisclaimer: conseguiremos rodar a aplicação com esse comando porque no arquivo package.json inicial do Express.js nós definimos um script assim na linha 8:
"dev": "node --watch servidor.js"
Se não fosse isso, o comando `npm run dev` não funcionaria. Seria necessário digitar a versão básica de execução de código javascript:
node servidor.js
Testando a API com cURL
Para cadastrar um filme, considerando a constante TIPO_OBRA = "filmes", abra o seu terminal e digite:
curl -i -X POST http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"titulo":"Matrix","ano":1999,"nota":9.5}'Para listar todas as obras:
curl -i http://localhost:3000/obrasPara buscar uma obra por ID:
curl -i http://localhost:3000/obras/1Para substituir uma obra inteira com PUT:
curl -i -X PUT http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"id":1,"titulo":"Matrix Reloaded","ano":2003,"nota":8.0}'Para atualizar parcialmente uma obra com PATCH:
curl -i -X PATCH http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"id":1,"nota":8.5}'Para remover uma obra com DELETE:
curl -i -X DELETE http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"id":1}'Caso o projeto esteja configurado para séries, o POST deve seguir este formato:
curl -i -X POST http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"titulo":"Arquivo X","temporadas":11,"nota":9.0}'Caso esteja configurado para animações:
curl -i -X POST http://localhost:3000/obras \
-H "Content-Type: application/json" \
-d '{"titulo":"A Viagem de Chihiro","estudio":"Studio Ghibli","nota":9.6}'Boas práticas adotadas
A API usa nomes claros, funções pequenas e respostas padronizadas em JSON. Além disso, os códigos de status também seguem uma convenção comum: 200 para sucesso geral, 201 para criação, 400 para erro de entrada, 404 para recurso não encontrado e 500 para erro interno.
Outra boa prática é a separação das validações em funções auxiliares. Ainda que o projeto esteja em um único arquivo, essa separação reduz repetição e facilita a futura migração para uma estrutura MVC. Em uma etapa seguinte, o projeto poderia ser dividido em routes, controllers, services e models, além de substituir o armazenamento em memória por MySQL, PostgreSQL, SQLite ou outro banco de dados.
Conclusão
Dominar Express.js é importante para quem deseja se aventurar pelo mundo da programação web, especificamente em JavaScript. O framework explicita conceitos fundamentais do desenvolvimento web como requisição, resposta, rota, endpoint, middleware, JSON e métodos HTTP. Mesmo em um projeto simples, como um backlog de filmes, séries ou animações, já aparecem decisões típicas de uma API real, tais como nomear recursos, como validar entradas, como responder erros e como organizar o fluxo entre cliente e servidor.
Obrigado pela leitura e bons estudos!
Referências
DUSSEAULT, Lisa; SNELL, James. RFC 5789: PATCH Method for HTTP. Internet Engineering Task Force, 2010. Disponível em: . Acesso em: 23 ago. 2026.
EXPRESS. Express.js: Node.js web application framework. OpenJS Foundation, 2026. Disponível em: <https://expressjs.com/>. Acesso em: 23 ago. 2026.
FIELDING, Roy T.; NOTTINGHAM, Mark; RESCHKE, Julian. RFC 9110: HTTP Semantics. Internet Engineering Task Force, 2022. Disponível em: . Acesso em: 23 ago. 2026.
NODE.JS. ECMAScript modules. OpenJS Foundation, 2026. Disponível em: . Acesso em: 23 ago. 2026.
PRESSMAN, Roger S.; MAXIM, Bruce R. Engenharia de software: uma abordagem profissional. 8. ed. Porto Alegre: AMGH, 2016.
SOMMERVILLE, Ian. Engenharia de software. 9. ed. São Paulo: Pearson Prentice Hall, 2011.


