惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

博客园 - 叶小钗
C
Check Point Blog
宝玉的分享
宝玉的分享
Attack and Defense Labs
Attack and Defense Labs
www.infosecurity-magazine.com
www.infosecurity-magazine.com
cs.AI updates on arXiv.org
cs.AI updates on arXiv.org
Application and Cybersecurity Blog
Application and Cybersecurity Blog
博客园 - Franky
V
V2EX
Hugging Face - Blog
Hugging Face - Blog
Google DeepMind News
Google DeepMind News
罗磊的独立博客
S
SegmentFault 最新的问题
S
Secure Thoughts
T
Troy Hunt's Blog
J
Java Code Geeks
Last Week in AI
Last Week in AI
酷 壳 – CoolShell
酷 壳 – CoolShell
W
WeLiveSecurity
Help Net Security
Help Net Security
S
Security @ Cisco Blogs
T
Threatpost
Apple Machine Learning Research
Apple Machine Learning Research
D
Darknet – Hacking Tools, Hacker News & Cyber Security
V2EX - 技术
V2EX - 技术
T
Tor Project blog
S
Security Affairs
T
Tailwind CSS Blog
钛媒体:引领未来商业与生活新知
钛媒体:引领未来商业与生活新知
H
Hacker News: Front Page
腾讯CDC
博客园 - 司徒正美
The Last Watchdog
The Last Watchdog
N
News | PayPal Newsroom
博客园 - 聂微东
小众软件
小众软件
WordPress大学
WordPress大学
博客园 - 三生石上(FineUI控件)
爱范儿
爱范儿
C
CERT Recently Published Vulnerability Notes
AI
AI
N
News and Events Feed by Topic
C
Cybersecurity and Infrastructure Security Agency CISA
O
OpenAI News
T
The Exploit Database - CXSecurity.com
L
LINUX DO - 最新话题
T
Threat Research - Cisco Blogs
雷峰网
雷峰网
NISL@THU
NISL@THU
V
Visual Studio Blog

DEV Community

Authentication Security Deep Dive: From Brute Force to Salted Hashing (With Java Examples) Why AI Systems Don’t Fail — They Drift Spilling beans for how i learn for exam😁"Reinforcement Learning Cheat Sheet" I Replaced Chrome with Safari for AI Browser Automation. Here's What Broke (and What Finally Worked) How Python Borrows Other People's Work The $40 Architecture: Processing 1 Billion API Requests with 99.99% Uptime Vibe Coding: A Workflow Guide (From Zero to SaaS) Most webhook security guides protect the wrong side. The scary part is delivery. Headless CMS for TanStack Start: Build a Blog with Cosmic EU Age Verification App "Hacked in 2 Minutes" — What Actually Happened Comfy Cloud’s delete function does not actually remove files Running AI Models on GPU Cloud Servers: A Beginner Guide Event-driven media intelligence with AWS Step Functions and Bedrock I scored 500 AI prompts across 8 quality dimensions — here's what broke How to Call Google Gemini API from Next.js (Free Tier, No Backend Needed) The Portal Protocol: Reclaiming Human Connection in the Age of AI How to Fix Your Team's Scattered Knowledge Problem With a Self-Hosted Forum Intro to tc Cloud Functors: A Graph-First Mental Model for the Modern Cloud Designing Multi-Tenant Backends With Both Ownership and Team Access I Built a Neumorphic CSS Library with 77+ Components — Here's What I Learned PostgreSQL Performance Optimization: Why Connection Pooling Is Critical at Scale Cómo construí un SaaS multi-rubro para gestionar expensas en Argentina con FastAPI + Vue 3 🚀 I Built an Ethical Hacking Scanner Tool – Open Source Project I Replaced /usage and /context in Claude Code With a Single Statusline A Pythonic Way to Handle Emails (IMAP/SMTP) with Auto-Discovery and AI-Ready Design I Collected 8.9 Million Polymarket Price Points — Here's What I Found About How Markets Really Move EcoTrack AI — Carbon Footprint Tracker & Dashboard Everyone's Using AI. No One Agrees How. 5 self-hosted ebook managers worth trying in 2026 Building Your First AI Agent with LangChain: From Chatbot to Autonomous Assistant Common SOC 2 Failures (Real World) Stop Vibe-Checking Your AI App: A Practical Guide to Evals How to Use SonarQube and SonarScanner Locally to Level Up Your Code Quality Your Next To-Do App Is Dead — I Replaced Mine with an OpenClaw AI Sign a Nostr event in 60 lines of Python using coincurve — no nostr-sdk, no nbxplorer, no rust toolchain ITGC Audit Explained Like You’re in Big 4 Patch Tuesday abril 2026: Microsoft parcha 163 vulnerabilidades y un zero-day en SharePoint Stop scraping everything: a better way to track competitor price changes Listing on MCPize + the Official MCP Registry while routing payments OUTSIDE the marketplace — how I kept 100% of my x402 revenue Building an AI-Powered Risk Intelligence System Using Serverless Architecture Why We Ripped Function Overloading Out of Our AI Toolchain Testing AI-Generated Code: How to Actually Know If It Works SaaS Churn Is Killing Your Business. Here Is What to Do About It (Without a Support Team) The Speed of AI Is No Longer Linear - And Self-Improving Models Are Why How to Implement RBAC for MCP Tools: A Practical Guide for Engineering Teams From Standard Quote to Persuasive Proposal: AI Automation for Arborists I built a CLI that scaffolds complete multi-tenant SaaS apps Axios CVE-2025–62718: The Silent SSRF Bug That Could Be Hiding in Your Node.js App Right Now The dashboard that ended our friendship Data Pipelines Explained Simply (and How to Build Them with Python) The Hidden Cost of AI Systems Nobody Talks About. undefined vs undeclared, and how typeof behaves Switching from file-based jobs to NATS/Kafka in Rust without changing code io_uring Adventures: Rust Servers That Love Syscalls Why Agentic AI is Killing the Traditional Database The POUR principles of web accessibility for developers and designers Quantum Neural Network 3D — A Deep Dive into Interactive WebGL Visualization How To Install Caveman In Codex On macOS And Windows Automation Pipeline Reliability: Why Your Workflow Breaks When Nobody Is Watching I Built an 'Open World' AI Coding Agent — It Works From ANY Folder From Freelancing to Product: A Tech Service Company's SaaS Transformation China's AI Giants: Adding Tencent Hunyuan & ByteDance Doubao to AI University (74 Providers) On the Vibe Coders and Their Lies clerk: Auto-Summarize Your Claude Code Sessions AI Weekly — 2026/04/10–04/17 | The Model Lockdown Is Here, but the Toolchain Is the Real Battleground AI 週報 — 2026/04/10–2026/04/17 模型封鎖潮來了,但工具鏈才是真戰場 Maybe this is how Open-Source apps are born... 🚀 Fine-Tune LLMs with LoRA and QLoRA: 2026 Guide tRPC v11 + Next.js App Router: End-to-End Type Safety Without the Boilerplate ShadCN UI in 2026: Why I Stopped Installing Component Libraries and Started Owning My Components SaaS Billing in React Server Components: Stripe + Supabase Without a Single `useEffect` Join our DEV Weekend Challenge — $1,000 in Prizes Across TEN winners! Submissions Due April 20 at 6:59 AM UTC. Implementing FSRS Spaced Repetition in Flutter + Supabase — Adding Memory Science to an AI Learning App "I Texted My Localhost From the Train — Claude Code Fixed the Bug Before I Got Home" I Built a Sales Prep AI and It Went Deeper Than Expected Design to Code #2: One JSON, Eleven Outputs Solving the 100M-Row Problem: A Summary Table Pattern for High-Volume Push Notification Logs Flutter Web With Wasm: What Actually Changes For Developers I Built 50 Royalty-Free Soundtracks for My Side Project in a Weekend Using AI Music Generation The Vibe Coding Security Checklist: 7 Things to Check Before You Ship Stop Letting Googlebot Guess Fix Your React App's SEO Right Desconstruindo o Streaming do LinkedIn: Como Criar um Engine de Extração de Vídeo de Alta Performance com HLS e FFmpeg (EDA Part-1) EDA (Exploratory Data Analysis) Explained With Real Life — Why Looking at Your Data Is the Most Important Step in Machine Learning Brand Relationship Management at Scale: Our 4-Touch Outreach System for 200+ Brands Why String.fromEnvironment() Might Return an Empty String in Dart JGuardrails 1.0.0 — Hardening Java LLM Apps Against Jailbreaks, Toxicity, and Prompt Injection Plan and Schedule a Full Week of Threads Content From One Claude Conversation Coding Cat Oran Ep3, Five Tables Changed Everything Updated: BFF Pattern I'm done watching freelancers get buried by 200 proposals. So I'm building the alternative. This is my first post BFS Algorithm in Java Step by Step Tutorial with Examples Tracking LLM Pricing Monthly: An Open Dataset for 22 AI Models How We Measure Content ROI on a Comparison Site: Revenue Attribution Without Perfect Data Introducing Nova AI Ops: The AI-Native Operating System for SRE Teams I built a free desktop video downloader for Windows — Grabbit How Talkie OCR Helps Vision-Impaired & Dyslexic Users Read the World Around Them VRCFaceTracking安装和iPhone面捕配置教程,有bug Even CrowdStrike Can't See Your Agents The Automation Gold Rush: What n8n Workflows and Claude Are Opening Up for Developers Right Now
Dominando o Express: partials, sessões, CRUD REST, filtros e páginas de erro
Moprius · 2026-05-15 · via DEV Community

Javascript

Você já tem um projeto Express organizado em MVC, com sua rota inicial funcionando e uma view de login renderizando direitinho. Mas uma aplicação web de verdade exige muito mais: reaproveitamento de pedaços de HTML, controle de sessão para login e logout, um CRUD completo seguindo o padrão REST, filtros para impedir acesso indevido a áreas restritas e, claro, páginas de erro amigáveis em vez daqueles stack traces feios que assustam o usuário.

Neste tutorial, vamos juntar todas essas peças. Ao final você terá uma aplicação Express completa: usuários se autenticando, criando contatos, editando, excluindo, sendo barrados quando não logam, e vendo páginas customizadas quando algo dá errado. O projeto continua sendo o Ntalk, uma agenda de contatos que vai virar também um chat em tempo real lá na frente.

Bora dominar o Express?

Estruturando views

Antes de mergulhar em sessões e rotas REST, vale uma melhoria simples mas que faz diferença gigante na manutenção do código: a separação das views em partials.

O template engine EJS tem várias funcionalidades para programar conteúdo dinâmico dentro do HTML. Não vamos esgotar o assunto, mas vamos usar os recursos principais para renderizar dados dinâmicos e, principalmente, evitar repetição. A ideia dos partials é simples: pedaços de HTML que se repetem em várias telas (cabeçalhos, rodapés, menus laterais) ficam em arquivos próprios e são incluídos sob demanda.

Vamos criar dois partials que serão reaproveitados em quase todas as páginas. O primeiro é o cabeçalho. Dentro de views, crie o arquivo header.ejs:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8">
    <title>Ntalk - Agenda de contatos</title>
</head>
<body>

Enter fullscreen mode Exit fullscreen mode

E agora o rodapé. Crie o arquivo footer.ejs:

<footer>
    <small>Ntalk - Agenda de contatos</small>
</footer>
</body>
</html>

Enter fullscreen mode Exit fullscreen mode

Agora podemos enxugar a homepage. Modifique a views/home/index.ejs para usar os partials através da diretiva include do EJS:

<% include ../header %>
<header>
    <h1>Ntalk</h1>
    <h4>Bem-vindo!</h4>
</header>
<section>
    <form action="/entrar" method="post">
        <input type="text" name="usuario[nome]" placeholder="Digite o nome">
        <br>
        <input type="text" name="usuario[email]" placeholder="Digite o e-mail">
        <br>
        <button type="submit">Entrar</button>
    </form>
</section>
<% include ../footer %>

Enter fullscreen mode Exit fullscreen mode

A homepage agora está muito mais enxuta. O cabeçalho e o rodapé não estão mais duplicados em todas as views: estão em um único lugar. Se você precisar mudar o título do site ou adicionar uma tag <meta> nova, edita um arquivo só, e a mudança se reflete em toda a aplicação. Esse tipo de organização vai te poupar muito tempo no futuro.

Outro detalhe importante: repare que os campos do formulário usam name="usuario[nome]" e name="usuario[email]". Essa sintaxe de colchetes faz com que o body parser do Express transforme automaticamente os dados em um objeto aninhado, ou seja, no servidor você vai receber req.body.usuario.nome e req.body.usuario.email. É uma forma elegante de manter dados relacionados agrupados.

Controlando as sessões de usuários

Para o sistema fazer login e logout, precisamos de controle de sessão. A sessão é uma área de memória no servidor que mantém dados específicos de cada usuário entre uma requisição e outra, identificada por um cookie no navegador. Trabalhar com sessão em Express é muito simples: os dados são manipulados através de um objeto JSON acessível em req.session.

Primeiro, vamos adicionar duas novas rotas em routes/home.js, uma POST /entrar para receber o formulário de login e uma GET /sair para destruir a sessão:

module.exports = function(app) {
    var home = app.controllers.home;
    app.get('/', home.index);
    app.post('/entrar', home.login);
    app.get('/sair', home.logout);
};

Enter fullscreen mode Exit fullscreen mode

Agora implemente as actions correspondentes no controllers/home.js. Na action login, vamos validar de forma simples se os campos nome e email foram preenchidos. Se sim, gravamos os dados na sessão e criamos um array vazio de contatos (que será usado mais tarde). Em seguida, redirecionamos para /contatos. Na action logout, chamamos req.session.destroy() para limpar tudo:

module.exports = function(app) {
    var HomeController = {
        index: function(req, res) {
            res.render('home/index');
        },
        login: function(req, res) {
            var email = req.body.usuario.email,
                nome = req.body.usuario.nome;
            if (email && nome) {
                var usuario = req.body.usuario;
                usuario['contatos'] = [];
                req.session.usuario = usuario;
                res.redirect('/contatos');
            } else {
                res.redirect('/');
            }
        },
        logout: function(req, res) {
            req.session.destroy();
            res.redirect('/');
        }
    };
    return HomeController;
};

Enter fullscreen mode Exit fullscreen mode

Reinicie o servidor (CTRL+C para parar e node app.js para subir de novo) e tente fazer login no sistema. Surpresa: vai dar erro. E é justamente nesse erro que vamos aprender uma das peças mais importantes do Express.

Faltou habilitar o body parser e a sessão na stack

O erro acontece porque o Express, por padrão, não decodifica automaticamente os corpos de requisição vindos de formulários HTML. Ele também não tem sessão habilitada por padrão. Precisamos adicionar esses itens à stack de middlewares.

Atualize a configuração da stack no app.js para ficar assim, na ordem correta:

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.cookieParser('ntalk'));
app.use(express.session());
app.use(express.json());
app.use(express.urlencoded());
app.use(express.static(__dirname + '/public'));

Enter fullscreen mode Exit fullscreen mode

Cada peça nova tem um papel específico:

  • express.cookieParser('ntalk') — precisa vir antes do middleware de sessão, porque o session() usa o cookie parser para codificar e decodificar o SessionID, que é justamente o identificador persistido no cookie do navegador. A string 'ntalk' é a chave secreta usada para assinar os cookies, dificultando que alguém os forje.
  • express.session() — habilita a sessão. A partir daí, todo req ganha o atributo req.session que você manipula livremente.
  • express.json() e express.urlencoded() — são os parsers de corpo. Eles transformam o conteúdo bruto das requisições POST em objetos JavaScript prontos para uso em req.body. O primeiro lida com requisições com Content-Type: application/json (típico de APIs), e o segundo com formulários HTML padrão. É graças a eles que aquela sintaxe name="usuario[nome]" vira req.body.usuario.nome.

Cuidados ao trabalhar com sessões

Tudo que você atribuir a req.session vira um atributo persistido no objeto da sessão daquele usuário. Por exemplo, req.session.mensagem = "Olá" cria a propriedade mensagem e ela vai estar disponível em requisições futuras do mesmo usuário.

Mas atenção: cuidado para não sobrescrever as funções nativas da sessão. Nomes como req.session.destroy ou req.session.regenerate são funções do próprio framework. Se você atribuir um valor a req.session.destroy, vai apagar a função e perder a capacidade de destruir aquela sessão. Isso é fonte de bugs inesperados difíceis de rastrear. Escolha nomes que não colidam com a API.

Criando a rota /contatos

Antes de testar tudo, precisamos criar a rota /contatos para onde o login está redirecionando. Vamos criar um controller, uma rota e uma view para essa área.

Crie o diretório views/contatos e dentro dele o arquivo index.ejs:

<% include ../header %>
<header>
    <h2>Ntalk - Agenda de contatos</h2>
</header>
<section>
    <p>Bem-vindo <%- usuario.nome %></p>
</section>
<% include ../exit %>
<% include ../footer %>

Enter fullscreen mode Exit fullscreen mode

Note duas coisas: estamos exibindo o nome do usuário logado com <%- usuario.nome %>, e estamos incluindo um terceiro partial chamado exit. Esse partial vai conter o link de logout, reaproveitado por toda a área autenticada. Crie views/exit.ejs:

<section>
    <a href='/sair'>Sair</a>
</section>

Enter fullscreen mode Exit fullscreen mode

Agora crie o controller controllers/contatos.js, com uma única action por enquanto:

module.exports = function(app) {
    var ContatoController = {
        index: function(req, res) {
            var usuario = req.session.usuario,
                params = {usuario: usuario};
            res.render('contatos/index', params);
        }
    };
    return ContatoController;
};

Enter fullscreen mode Exit fullscreen mode

E o arquivo de rotas routes/contatos.js (por convenção, o nome bate com o controller):

module.exports = function(app) {
    var contatos = app.controllers.contatos;
    app.get('/contatos', contatos.index);
};

Enter fullscreen mode Exit fullscreen mode

Reinicie o servidor e tente novamente o login. Agora deve funcionar: você é redirecionado para a página de contatos, vê seu nome no topo e tem um link para sair. Parabéns, você acabou de implementar autenticação e sessão.

Criando rotas no padrão REST

A agenda de contatos precisa do CRUD clássico: criar, listar, atualizar e excluir contatos. A forma profissional de expor esse CRUD é via REST, que mapeia cada operação para um verbo HTTP específico:

  • GET /contatos — lista todos os contatos
  • GET /contato/:id — mostra os detalhes de um contato
  • POST /contato — cria um novo contato
  • GET /contato/:id/editar — exibe o formulário de edição
  • PUT /contato/:id — atualiza um contato existente
  • DELETE /contato/:id — exclui um contato

Para que tudo isso funcione, precisamos adicionar dois itens novos na stack:

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.cookieParser('ntalk'));
app.use(express.session());
app.use(express.json());
app.use(express.urlencoded());
app.use(express.methodOverride());
app.use(app.router);
app.use(express.static(__dirname + '/public'));

Enter fullscreen mode Exit fullscreen mode

O express.methodOverride() permite simular os verbos PUT e DELETE em formulários HTML (já já explico o porquê disso). O app.router é o middleware que gerencia o roteamento da aplicação. Adicioná-lo explicitamente à stack nos dá controle sobre quando o roteamento acontece em relação aos outros middlewares, o que será crucial para a parte de páginas de erro mais à frente.

Implementando as rotas REST

Atualize routes/contatos.js para contemplar todas as rotas do CRUD:

module.exports = function(app) {
    var contatos = app.controllers.contatos;
    app.get('/contatos', contatos.index);
    app.get('/contato/:id', contatos.show);
    app.post('/contato', contatos.create);
    app.get('/contato/:id/editar', contatos.edit);
    app.put('/contato/:id', contatos.update);
    app.del('/contato/:id', contatos.destroy);
};

Enter fullscreen mode Exit fullscreen mode

Repare em :id — esses são parâmetros de rota. Qualquer valor que aparecer naquela posição da URL será capturado e disponibilizado em req.params.id. Por exemplo, ao acessar /contato/3, dentro da action o req.params.id vai valer "3".

Implementando as actions

Por enquanto, em vez de um banco de dados, vamos persistir os contatos na própria sessão do usuário. Isso não é uma boa ideia para produção (a sessão é volátil e tem tamanho limitado), mas é perfeito para entender o fluxo do framework sem se preocupar com infraestrutura. Substitua o conteúdo de controllers/contatos.js pelo seguinte:

module.exports = function(app) {
    var ContatoController = {
        index: function(req, res) {
            var usuario = req.session.usuario,
                contatos = usuario.contatos,
                params = {usuario: usuario, contatos: contatos};
            res.render('contatos/index', params);
        },
        create: function(req, res) {
            var contato = req.body.contato,
                usuario = req.session.usuario;
            usuario.contatos.push(contato);
            res.redirect('/contatos');
        },
        show: function(req, res) {
            var id = req.params.id,
                contato = req.session.usuario.contatos[id],
                params = {contato: contato, id: id};
            res.render('contatos/show', params);
        },
        edit: function(req, res) {
            var id = req.params.id,
                usuario = req.session.usuario,
                contato = usuario.contatos[id],
                params = {usuario: usuario, contato: contato, id: id};
            res.render('contatos/edit', params);
        },
        update: function(req, res) {
            var contato = req.body.contato,
                usuario = req.session.usuario;
            usuario.contatos[req.params.id] = contato;
            res.redirect('/contatos');
        },
        destroy: function(req, res) {
            var usuario = req.session.usuario,
                id = req.params.id;
            usuario.contatos.splice(id, 1);
            res.redirect('/contatos');
        }
    };
    return ContatoController;
};

Enter fullscreen mode Exit fullscreen mode

Cada action faz algo bem específico: o index lista, o create adiciona ao array, o show exibe os detalhes de um item, o edit carrega o formulário com os dados atuais, o update aplica as alterações e o destroy remove pela posição no array usando splice.

Criando as views do CRUD

Atualize views/contatos/index.ejs para listar contatos e ter o formulário de cadastro:

<% include ../header %>
<header>
    <h2>Ntalk - Agenda de contatos</h2>
</header>
<section>
    <form action="/contato" method="post">
        <input type="text" name="contato[nome]" placeholder="Nome">
        <input type="text" name="contato[email]" placeholder="E-mail">
        <button type="submit">Cadastrar</button>
    </form>
    <table>
        <thead>
            <tr>
                <th>Nome</th>
                <th>E-mail</th>
                <th>Ação</th>
            </tr>
        </thead>
        <tbody>
            <% contatos.forEach(function(contato, index) { %>
                <tr>
                    <td><%- contato.nome %></td>
                    <td><%- contato.email %></td>
                    <td><a href="/contato/<%- index %>">Detalhes</a></td>
                </tr>
            <% }) %>
        </tbody>
    </table>
</section>
<% include ../exit %>
<% include ../footer %>

Enter fullscreen mode Exit fullscreen mode

Agora a view de edição, views/contatos/edit.ejs:

<% include ../header %>
<header>
    <h2>Ntalk - Editar contato</h2>
</header>
<section>
    <form action="/contato/<%- id %>" method="post">
        <input type="hidden" name="_method" value="put">
        <label>Nome:</label>
        <input type="text" name="contato[nome]" value="<%- contato.nome %>">
        <label>E-mail:</label>
        <input type="text" name="contato[email]" value="<%- contato.email %>">
        <button type="submit">Atualizar</button>
    </form>
</section>
<% include ../exit %>
<% include ../footer %>

Enter fullscreen mode Exit fullscreen mode

E por último a view de detalhes, views/contatos/show.ejs:

<% include ../header %>
<header>
    <h2>Ntalk - Dados do contato</h2>
</header>
<section>
    <form action="/contato/<%- id %>" method="post">
        <input type="hidden" name="_method" value="delete">
        <p><label>Nome:</label> <%- contato.nome %></p>
        <p><label>E-mail:</label> <%- contato.email %></p>
        <p>
            <button type="submit">Excluir</button>
            <a href="/contato/<%- id %>/editar">Editar</a>
        </p>
    </form>
</section>
<% include ../exit %>
<% include ../footer %>

Enter fullscreen mode Exit fullscreen mode

O truque dos verbos PUT e DELETE em HTML

Você deve ter notado essas linhas estranhas:

<input type="hidden" name="_method" value="put">
<input type="hidden" name="_method" value="delete">

Enter fullscreen mode Exit fullscreen mode

Esse é um detalhe importante e que pega muita gente de surpresa. A especificação atual do HTML não permite definir method="put" ou method="delete" na tag <form>. Os únicos métodos suportados nativamente são GET e POST. Para contornar isso, existe uma convenção que praticamente todos os frameworks web adotam: a tag <form> envia como POST, e um campo oculto chamado _method indica o verbo HTTP real que deveria ser usado.

O middleware express.methodOverride() é exatamente quem faz essa mágica. Ele intercepta a requisição, lê o valor de _method no corpo, e reescreve o verbo HTTP antes de chegar ao roteador. É por isso que tivemos que adicioná-lo à stack: sem ele, o servidor ignoraria o _method e trataria tudo como POST, jamais chegando nas actions update e destroy.

Reinicie o servidor e teste o CRUD completo. Cadastre alguns contatos, abra os detalhes, edite, exclua. Tudo deve funcionar, e os dados ficam persistidos enquanto a sessão durar (ou seja, até você fazer logout ou reiniciar o servidor).

Aplicando filtros antes de acessar as rotas

Já notou que se você acessar /contatos sem fazer login, a aplicação dá um erro feio? É fácil reproduzir: faça logout e tente abrir /contatos diretamente no navegador. Você vê algo como "Cannot read property 'usuario' of undefined".

A causa é simples. O controller tenta acessar req.session.usuario, mas, sem login, esse objeto não existe na sessão. JavaScript tenta ler a propriedade contatos de undefined e lança a exceção.

Para resolver isso, precisamos de um filtro de autenticação que rode antes da action propriamente dita. Frameworks de outras linguagens normalmente oferecem hooks explícitos chamados before ou after. O Express não tem isso de forma tão direta. Mas, graças aos callbacks de JavaScript, o próprio mecanismo de roteamento do Express suporta callbacks encadeados em uma rota. Resumindo: depois do path, você pode passar quantos callbacks quiser, e eles são executados em ordem, um após o outro.

Por exemplo:

app.get('/', callback1, callback2, callback3);

Enter fullscreen mode Exit fullscreen mode

Esse é o mecanismo que vamos usar para criar nossos filtros.

Criando o middleware de autenticação

Na raiz do projeto, crie a pasta middleware e dentro dela o arquivo autenticador.js:

module.exports = function(req, res, next) {
    if (!req.session.usuario) {
        return res.redirect('/');
    }
    return next();
};

Enter fullscreen mode Exit fullscreen mode

A lógica é direta: se não existe usuário na sessão, redireciona para a página inicial. Se existe, chama next() para passar para o próximo callback da cadeia (que será a action propriamente dita).

A função next é a peça-chave de qualquer middleware Express. Chamá-la significa "terminei meu trabalho, passe a bola adiante". Se você esquecer de chamar next() e não enviar uma resposta, a requisição fica travada para sempre, esperando algo que nunca vem.

Encaixando o filtro nas rotas

Agora vamos injetar esse middleware em todas as rotas que exigem autenticação. Modifique routes/contatos.js:

module.exports = function(app) {
    var autenticar = require('./../middleware/autenticador'),
        contatos = app.controllers.contatos;

    app.get('/contatos', autenticar, contatos.index);
    app.get('/contato/:id', autenticar, contatos.show);
    app.post('/contato', autenticar, contatos.create);
    app.get('/contato/:id/editar', autenticar, contatos.edit);
    app.put('/contato/:id', autenticar, contatos.update);
    app.del('/contato/:id', autenticar, contatos.destroy);
};

Enter fullscreen mode Exit fullscreen mode

Pronto. Cada rota agora executa primeiro autenticar e só chega na action de contatos se o usuário estiver autenticado. Caso contrário, é redirecionado para a tela de login.

Esse é o padrão para emular um filtro before. Se você quiser um filtro after, basta colocar o callback do filtro depois do callback principal da rota. A ordem dos callbacks define a ordem de execução, simples assim.

Esse mesmo mecanismo pode ser usado para muito mais do que autenticação: logging por rota, verificação de permissões específicas, validação de dados, rate limiting. Cada filtro fica em seu próprio arquivo, com responsabilidade única, e você compõe-os onde precisar.

Indo além: criando páginas de erros amigáveis

Por padrão, quando uma rota não existe ou quando acontece um erro não tratado, o Express devolve uma página técnica feia, com stack trace, status numérico e nenhum carinho com o usuário. Isso é péssimo para qualquer aplicação em produção. A solução é interceptar esses erros e renderizar páginas customizadas.

O Express oferece dois mecanismos para isso: um para o famoso erro 404 (página não encontrada) e outro genérico para qualquer erro de servidor (geralmente o 500).

Criando as views de erro

Primeiro, crie duas novas views dentro de views. A primeira é a tela de "não encontrado", chame de not-found.ejs:

<% include header %>
<header>
    <h1>Ntalk</h1>
    <h4>Infelizmente essa página não existe :(</h4>
</header>
<hr>
<p>Vamos voltar <a href="/">home page?</a> :)</p>
<% include footer %>

Enter fullscreen mode Exit fullscreen mode

A segunda é a tela de erro genérico, chame de server-error.ejs:

<% include header %>
<header>
    <h1>Ntalk</h1>
    <h4>Aconteceu algo terrível! :(</h4>
</header>
<p>
    Veja os detalhes do erro:
    <br>
    <%- error.message %>
</p>
<hr>
<p>Que tal voltar <a href="/">home page?</a> :)</p>
<% include footer %>

Enter fullscreen mode Exit fullscreen mode

A view de 500 recebe um objeto error com a mensagem do erro. Em produção, normalmente você esconderia essa mensagem do usuário final (para não vazar detalhes da implementação), mas em desenvolvimento é útil ver o erro direto na tela.

Registrando os handlers de erro

O Express tem uma regra interessante: qualquer middleware registrado depois das rotas e que receba uma requisição que nenhuma rota tratou vira automaticamente um handler de 404. Já um middleware com quatro parâmetros (error, req, res, next) é interpretado como handler de erro genérico, recebendo a exceção que foi lançada em qualquer ponto anterior.

Para organizar isso, vamos criar um arquivo dedicado em middleware/error.js:

exports.notFound = function(req, res, next) {
    res.status(404);
    res.render('not-found');
};

exports.serverError = function(error, req, res, next) {
    res.status(500);
    res.render('server-error', {error: error});
};

Enter fullscreen mode Exit fullscreen mode

E modificar a stack no app.js para incluir esses handlers no final:

var express = require('express'),
    app = express(),
    load = require('express-load'),
    error = require('./middleware/error');

app.set('views', __dirname + '/views');
app.set('view engine', 'ejs');
app.use(express.cookieParser('ntalk'));
app.use(express.session());
app.use(express.json());
app.use(express.urlencoded());
app.use(app.router);
app.use(express.static(__dirname + '/public'));
app.use(error.notFound);
app.use(error.serverError);

Enter fullscreen mode Exit fullscreen mode

Repare na ordem: os handlers de erro vêm depois do app.router e do express.static. Isso é fundamental. Se viessem antes, eles interceptariam todas as requisições e nenhuma rota ou arquivo estático seria atendido. A regra é "trate o erro quando todo o resto já falhou".

Testando

Reinicie o servidor. Para testar o erro 404, acesse uma URL que não existe, como http://localhost:3000/url-errada. Em vez do erro técnico, você vai ver sua tela amigável.

Para testar o erro 500, force um bug deliberadamente. Remova o filtro autenticar de uma das rotas em routes/contatos.js e tente acessar /contatos sem estar logado. O req.session.usuario é undefined, a tentativa de ler .contatos lança uma exceção e o handler de erro 500 entra em ação, renderizando a tela customizada. Depois do teste, lembre-se de recolocar o filtro.

Existem códigos HTTP para os mais diversos tipos de erro (401 para não autenticado, 403 para acesso negado, 422 para dados inválidos, e por aí vai). À medida que sua aplicação cresce, faz sentido criar handlers e views específicas para cada um. O padrão é sempre o mesmo: registrar o middleware na stack após as rotas, definir o status com res.status(...) e renderizar a view apropriada.

Conclusão

Você acabou de transformar um esqueleto de aplicação em algo realmente funcional. Recapitulando:

  • Estruturando views com partials: extraímos header.ejs, footer.ejs e exit.ejs para eliminar duplicação, usando a diretiva <% include %> do EJS. Cada view agora tem só o conteúdo específico daquela tela.
  • Controlando sessões de usuários: adicionamos cookieParser, session, json e urlencoded na stack, criamos as rotas /entrar e /sair, implementamos login e logout manipulando req.session. Aprendemos a evitar sobrescrever funções nativas da sessão.
  • CRUD REST: implementamos as seis actions (index, show, create, edit, update, destroy) mapeadas para os verbos GET, POST, PUT e DELETE, com parâmetros de rota via :id. Descobrimos o truque do campo oculto _method combinado com o middleware methodOverride para simular PUT e DELETE em formulários HTML.
  • Filtros antes das rotas: criamos nosso primeiro middleware customizado em middleware/autenticador.js, aproveitando o suporte do Express a callbacks encadeados em rotas. Bloqueamos acesso à área autenticada sem precisar repetir verificação em cada action.
  • Páginas de erro amigáveis: criamos not-found.ejs e server-error.ejs, dois handlers em middleware/error.js, e os registramos no fim da stack. Entendemos a convenção de quatro parâmetros que define um middleware de erro no Express.

A aplicação agora tem todos os elementos básicos de um sistema web profissional: autenticação, CRUD, filtros, tratamento de erros, e tudo isso organizado de forma modular, sem código repetido, com responsabilidades bem separadas.

O próximo passo natural é tirar os dados da sessão e colocá-los em um banco de dados de verdade, persistindo os contatos entre sessões. Daí vamos abrir caminho para o chat em tempo real, validação robusta dos modelos, testes automatizados e tudo o mais que transforma uma aplicação de estudo em algo digno de ir para produção.

Mas isso já é assunto para o próximo tutorial. Por enquanto, aproveite o que você acabou de construir: uma aplicação Express completa, do zero, que faz tudo o que toda aplicação web precisa fazer. Boa codificação, e até lá.