Skip to content

Node.js e Express

Node.js executa JavaScript fora do navegador (motor V8) e Express é o framework web mais popular sobre ele. Juntos com MongoDB (banco de documentos, em NoSQL) e um front-end (Angular, na sigla original) formam a MEAN Stack: MongoDB + Express + Angular + Node.js. A grande vantagem: uma só linguagem (JavaScript/JSON) em todas as camadas, do banco ao navegador, o que acelera protótipos e simplifica a equipe.

flowchart LR
    B[Navegador<br/>Angular SPA] -- "HTTP / JSON (REST)" --> E[Express<br/>Node.js]
    E -- "Mongoose (ODM)" --> M[(MongoDB)]
    E --> A[Passport<br/>autenticação]

Definição: Node.js, npm e package.json

Node.js é um ambiente de execução de JavaScript orientado a eventos e a I/O assíncrono não bloqueante: uma só thread atende muitas requisições porque, ao esperar o banco ou a rede, registra uma função de retorno e segue adiante. npm é o gerenciador de pacotes; o package.json descreve o projeto e suas dependências (com versão), e a pasta node_modules (não versionada) é recriada com npm install. npm init cria o arquivo; npm install pacote --save (hoje, por padrão) registra a dependência.

Express: servidor, middlewares e rotas

Um servidor "puro" do Node responde a tudo na mesma função. O Express acrescenta middlewares, rotas, visões e utilidades.

Definição: middleware

Função que recebe a requisição (req), a resposta (res) e a próxima etapa (next) e pode ler/alterar a requisição, responder ou passar adiante. Uma pilha de middlewares é aplicada a cada requisição (segurança, log, parsing do corpo, arquivos estáticos, autenticação, tratamento de erro). A ordem de registro importa. A partir do Express 4 cada middleware é um módulo à parte.

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

app.use(express.json());                 // lê o corpo JSON
app.use(express.static("public"));       // arquivos estáticos

app.get("/contatos/:id", (req, res) => { // rota GET com parâmetro
  const contato = contatos.find(c => c._id === req.params.id);
  if (!contato) return res.status(404).json({ erro: "Contato não encontrado" });
  res.json(contato);
});

app.listen(3000);

Elementos do Express:

  • Variáveis de ambiente/configuração (app.set, process.env): porta, view engine, conexão com o banco. Não coloque segredos no código.
  • Rotas: app.get/post/put/delete(caminho, ...handlers) ligadas aos verbos HTTP; req.params (caminho), req.query (query string), req.body.
  • Template engines (EJS, Pug, Handlebars): geram HTML no servidor com res.render("view", dados). Em uma SPA, o servidor entrega só JSON.
  • Organização em MVC: routes (mapeiam URLs), controllers (funções que tratam a requisição, a ponte entre a view e os dados), models (acesso ao banco). Módulos como express-load carregavam essas pastas automaticamente; hoje se usam require/import explícitos.
  • Desenvolvimento: nodemon reinicia o servidor a cada alteração.

API REST

Com o Express é simples expor recursos como endpoints REST (REST): GET /contatos (lista), GET /contatos/:id, POST /contatos (cria), PUT /contatos/:id (atualiza), DELETE /contatos/:id, retornando JSON e códigos HTTP corretos (200, 201, 204, 400, 401, 404, 500).

Programação assíncrona: de callbacks a async/await

Operações de rede e banco são assíncronas. A evolução dos estilos:

Estilo Problema/vantagem
Callbacks Funções aninhadas formam a pirâmide da perdição (callback hell), e try/catch não captura erros assíncronos
Promises (.then().catch()) Valor futuro encadeável, erros propagados pela cadeia
async/await Código assíncrono com aparência sequencial e try/catch normal — o padrão atual
app.get("/contatos", async (req, res, next) => {
  try {
    const contatos = await Contato.find().exec();
    res.json(contatos);
  } catch (e) { next(e); }              // middleware de erro trata
});

Mongoose: esquemas para um banco sem esquema

O MongoDB não força esquema — a integridade dos dados passa a ser responsabilidade da aplicação. O Mongoose é um ODM (Object-Document Mapper): você define esquemas (campos, tipos, obrigatoriedade, valores padrão, validações), cria modelos a partir deles e usa métodos como find, findById, create, findByIdAndUpdate, deleteOne. Gerencia também a conexão (eventos connected, error, disconnected; uma conexão reaproveitada por toda a aplicação) e referências entre documentos (ref + populate, como um JOIN na aplicação). Aproxima-se do padrão Active Record.

const esquema = new mongoose.Schema({
  nome:  { type: String, required: true },
  email: { type: String, required: true, unique: true, index: true },
  emergencia: { type: mongoose.Schema.Types.ObjectId, ref: "Contato" }
});
const Contato = mongoose.model("Contato", esquema);

Atualização por substituição de documento

Aceitar o objeto inteiro enviado pelo cliente e substituir o documento permite que ele altere campos que não deveria (o equivalente do mass assignment; veja Segurança). Atualize só os campos permitidos ($set).

Autenticação com Passport e OAuth 2.0

Passport é um middleware de autenticação com estratégias plugáveis (usuário e senha, OAuth 2.0 com GitHub/Google, JWT). Fluxo típico: registrar a aplicação no provedor, configurar a estratégia (ID e segredo do cliente em variáveis de ambiente), serializar o usuário na sessão e desserializar a cada requisição, e então proteger as rotas com um middleware que exige usuário autenticado (resposta 401 para APIs). No front-end, um interceptador de requisições redireciona ao login quando recebe 401. Teoria do protocolo em Segurança.

Segurança em aplicações Express

Veja o panorama em Segurança em aplicações web. Específico do ecossistema:

  • Helmet: conjunto de middlewares que ajustam cabeçalhos HTTP: escondem o X-Powered-By (que revela a tecnologia), X-Frame-Options (contra clickjacking), X-Content-Type-Options: nosniff, Strict-Transport-Security, Content-Security-Policy (o antigo X-XSS-Protection foi abandonado). app.use(helmet()).
  • Injeção de operadores no MongoDB (NoSQL injection): o MongoDB é imune à injeção de SQL, mas não a objetos maliciosos. Se a API espera um texto (id) e recebe um objeto como { "$ne": null }, uma consulta ou remoção pode atingir todos os documentos. Valide e converta os tipos, e sanitize chaves que comecem com $ (express-mongo-sanitize, validação com Joi/Zod).
  • Mensagens de erro genéricas, limite de tamanho do corpo, limitação de taxa (express-rate-limit), CORS configurado e HTTPS.

Ferramentas de build, testes e entrega

Tema No livro (2015) Hoje
Dependências do front-end Bower npm/pnpm + bundlers (Vite, esbuild, webpack); Bower foi abandonado
Automação (minificar e concatenar) Grunt (usemin) Vite, webpack, npm scripts; Gulp/Grunt são legados
Testes de unidade Karma + Jasmine Jest ou Vitest; testes de API com Supertest
Testes de ponta a ponta Protractor (descontinuado) + Page Objects Playwright ou Cypress, mantendo o padrão Page Object (Qualidade)
Integração contínua Travis CI GitHub Actions, GitLab CI (CI/CD)
Deploy PaaS (OpenShift) a cada commit bem-sucedido PaaS (Render, Fly.io, Heroku), contêineres/Kubernetes, funções serverless (Nuvem)

A ideia que continua válida: tudo que é manual está sujeito a erro, então cada commit dispara build, testes e (se passar) implantação automática. Os mocks do back-end no front-end ($httpBackend no Angular) correspondem hoje a ferramentas como MSW ou HttpClientTestingModule.

Quando usar (e quando não)

Bom para: APIs e BFFs I/O-bound (muitas conexões, pouco processamento), tempo real (WebSocket), protótipos e times de JavaScript/TypeScript. Cuidado com tarefas intensivas de CPU (bloqueiam o event loop; use worker threads ou serviços à parte). Hoje prefira TypeScript e, para estruturar projetos grandes, frameworks como NestJS (inspirado no Angular) ou Fastify. O mesmo conceito de stack com outras tecnologias: Spring.