Trabalhando com Express

Trabalhando com Express

Olá leitor, seja bem vindo a uma das lições mais importantes da jornada do desenvolvedor NodeJS 🥳

Aqui você vai aprender a utilizar um dos frameworks mais usados no NodeJS, o EXPRESS! 😎

Para que você possa entender o quão importante é esta lição, além do express ser um dos maiores frameworks web utilizados no NodeJS, ele é responsável por dar a vida a sua aplicação.

Mas de que forma?

Simples, com ele nós podemos construir rotas que definem como a aplicação deve responder a diferentes solicitações realizadas por parte do cliente.

Será por meio do express que nós iremos criar um servidor online (e também APIs) de forma fácil e performática 🤩

Pronto para começarmos?

O que é o Express?

O Express é um framework para NodeJS que simplifica o desenvolvimento de aplicações web e APIs.

Criado para ser minimalista e flexível, o Express fornece uma estrutura básica para criar servidores web e manipular requisições HTTP (retornar códigos HTML), sem impor muitas restrições ou padrões rígidos.

Por de baixo dos panos, o express faz o uso do módulo HTTP do NodeJS para criar um servidor online. A diferença é que o express criou métodos e propriedades muito mais simples de serem usadas se comparado ao módulo HTTP do NodeJS 🙃

Com o express, nós podemos criar rotas para diferentes URLs, além de métodos HTTP (como GET, POST, PUT, DELETE) de forma simples e completamente intuitiva.

Não só isso, como esse framework também possui suporte ao uso de middlewares, que são funções que podem executar códigos, modificar requisições e respostas, além de terminar o ciclo de requisição-resposta. 

Será por meio dos middlewares que faremos a autenticação, validação, e manipulação de erros que acontecem em nosso projeto.

Além disso, o express é um framework que pode ser facilmente estendido com pacotes adicionais, como bibliotecas para integração com bancos de dados, autenticação, e outras funcionalidades.

E pelo simples fato dele ser um dos frameworks mais populares e amplamente utilizados no ecossistema do NodeJS, já é de se esperar que ele tenha uma grande quantidade de recursos, diversas documentações disponíves, além do suporte da própria comunidade de desenvolvedores.

Portanto, podemos dizer que esse framework é a base sólida para criar aplicações web com o NodeJS.

Criando nosso projeto de testes

Antes de colocarmos a mão na massa, é deveras importante que você configure o seu projeto inicial. Para isso, eu criei uma pasta chamada Express dentro da minha área de trabalho (desktop):

Com o seu terminal (Prompt de Comando) aberto na pasta raiz que acabamos de criar (Express), precisamos inicializar o nosso projeto por meio do NPM, sendo assim, execute o seguinte comando abaixo:

npm init -y

A flag -y, como você já deve saber, cria um novo projeto de forma enxuta, respondendo SIM para tudo 😅

Feito isso, você está pronto para instalar o express 😉

Instalando o Express

Ainda dentro da pasta raiz do seu projeto, com o terminal (Prompt de Comando) aberto, execute o seguinte comando abaixo:

npm install express

O comando acima, irá instalar na pasta do seu projeto o pacote express 🥳

Ao analisarmos a estrutura de pastas do nosso projeto, vemos que nada de muito diferente mudou:

O framework express, serve para ser usado diretamente dentro dos arquivos da sua aplicação, sendo assim, ele não cria uma estrutura logo de cara, fazendo com que nós desenvolvedores tenhamos que fazer isso mais tarde 🙃

Configurações iniciais do projeto

Com seu projeto configurado e o express instalado, chegou a hora de colocarmos a mão na massa, de modo a fazer a configuração inicial deste framework 😉

Primeiro de tudo, vamos criar um novo arquivo chamado de index.js dentro da pasta raiz da sua aplicação (Express), com as seguintes configurações:

const express = require('express');

const app = express();

const port = 3000;

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

A primeira vista, o funcionamento do Express é bastante similar ao módulo HTTP que nós aprendemos em lições passadas.

A diferença, é que nós começamos importando o módulo express para dentro do index.js, e o instânciamos por meio da constante app (express()).

Em seguida, fizemos o uso do método get do framework express, para criar uma rota simpels do tipo GET.

O primeiro argumento é uma string e representa a URL que nós iremos interceptar, que no caso é a página principal (/). 

E assim como módulo HTTP, nós é retornando duas variáveis dentro de um callback:

req: representa a requisição HTTP feita pelo cliente, e contém informações sobre a solicitação.

res: representa a resposta HTTP que o servidor envia de volta ao cliente, e também fornece métodos e propriedades para configurar essa resposta.

Incrível como o funcionamento é igualzinho ao módulo HTTP, não é verdade? 😋

Em seguida, utilizamos o método send da variável res, para enviar uma resposta de volta ao cliente, que naquele caso, foi uma mensagem de boas vindas!

Observação: diferente do módulo HTTP, o comando res.send (e outros métodos de resposta como res.json, res.end, etc), não pode ser chamado mais de uma vez para uma única resposta.

Uma vez que a resposta é enviada para o cliente, o fluxo de resposta é finalizado e não pode ser alterado ou chamado novamente.

app.get('/', (req, res) => {
    res.send('Primeira resposta'); // Envia a primeira resposta
    res.send('Segunda resposta'); // Isso causará um erro
});

Por fim, executamos o método listen para definirmos a porta em que o nosso servidor estará rodando.

Para testar esse código, basta executar o comando abaixo no seu terminal (Prompt de Comando):

node ./index.js

Uma nova aba no seu navegador se abrirá, lhe informando que o servidor foi inicializado com sucesso:

Observação: Caso uma nova aba não se abra, basta seguir o link: http://localhost:3000/.

E pronto, você já tem seu primeiro servidor local configurado, só que dessa vez, usando o framework EXPRESS 😍

Retornando um arquivo HTML com o Express

Assim como o módulo HTTP, com o express nós também conseguimos renderizar um código HTML, e para isso podemos utilizar o método sendFile da seguinte forma (em conjunto com o módulo path):

index_arquivo_html.js:

const express = require('express');
const app = express();
const port = 3000;

const path = require('path');
const basePath = path.join(__dirname, 'templates_html');//Importa a pasta que contém todos os templates em HTML.

app.get('/', (req, res) => {
    res.sendFile(`${basePath}/index.html`);//Envia o arquivo index.html para o cliente.
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que no comando acima, criamos uma variável basePath que contém o caminho base que leva para a pasta templates_html, aonde conterá todos os templates em HTML que serão usados pelo nosso servidor.

Note também que fizemos o uso do comando sendFile da variável res para enviar o arquivo index.html.

E falando nesse arquivo, não se esqueça de criar uma nova pasta chamada templates_html dentro da pasta raiz do seu projeto, junto com o seu index.html:

templates_html > index.html:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Meu Primeiro Servidor com a Micilini.com</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 20px;
            padding: 0;
            background-color: #f4f4f4;
            color: #333;
        }
        h1 {
            color: #0056b3;
        }
        p {
            font-size: 18px;
        }
    </style>
</head>
<body>
    <h1>Meu Primeiro Servidor com a Micilini.com</h1>
    <p>Estamos aprendendo a criar servidores com o EXPRESS!</p>
</body>
</html>

Para rodar o projeto não se esqueça de executar o comando:

node ./index_arquivo_html.js

Veja como ficou o resultado final:

Incrível, não acha? 😋

Retornando um código HTML (inline) com Express

Caso você não queira trabalhar com arquivos .html, o express também suporta que você envie códigos HTML de forma inline, observe:

index_inline_html.js:

const express = require('express');
const app = express();
const port = 3000;

// Defina o código HTML como uma string
const htmlContent = `
<!DOCTYPE html>
<html lang="pt-br">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Página Inicial</title>
    <style>
        body {
            font-family: Arial, sans-serif;
            margin: 0;
            padding: 0;
            background-color: #f4f4f4;
            color: #333;
        }
        .container {
            width: 80%;
            margin: 0 auto;
            overflow: hidden;
        }
        header {
            background: #333;
            color: #fff;
            padding-top: 30px;
            min-height: 70px;
            border-bottom: #ccc 1px solid;
            text-align: center;
        }
        header h1 {
            margin: 0;
            font-size: 24px;
        }
        .main {
            padding: 20px;
        }
    </style>
</head>
<body>
    <header>
        <div class="container">
            <h1>Bem-vindo ao Meu Site</h1>
        </div>
    </header>
    <div class="main container">
        <h2>Conteúdo da Página</h2>
        <p>Este é um exemplo de conteúdo HTML retornado diretamente pelo servidor.</p>
    </div>
</body>
</html>
`;

app.get('/', (req, res) => {
    res.send(htmlContent); // Envia o HTML diretamente na resposta
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Veja como ficou o resultado final:

Criando novas rotas na nossa aplicação

Nos tópicos anteriores, você aprendeu que para criar uma rota, usamos o comando app.get, onde informamos no primeiro parâmetro a URL que nós queremos interceptar.

Seguindo a mesma linha de raciocínio, para criarmos novas rotas do tipo GET, basta duplicarmos o comando app.get, alterando o primeiro parâmetro, veja como é fácil:

index_rotas_simples.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Tela Principal!');
});

app.get('/contato', (req, res) => {
    res.send('Tela de Contato');
});

app.get('/sobre', (req, res) => {
    res.send('Tela de Sobre');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que só precisamos repetir o comando app.get, em vez de criar uma estrutura do tipo SwitchCase, como acontecia com o módulo HTTP.

Agora ficou mais simplificado o processo de criação de rotas, não é verdade?

Veja como ficou o funcionamento:

Configurando cabeçalhos com o Express

Supondo que você precise configurar um cabeçalho personalizado durante as solicitações HTTP que o seu servidor recebe, você pode fazer isso da seguinte forma:

index_cabecalho.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    // Definindo um cabeçalho personalizado
    res.setHeader('X-Custom-Header', 'Este é um cabeçalho personalizado');

    // Definindo um cabeçalho padrão
    res.setHeader('Content-Type', 'text/plain');

    // Enviando a resposta
    res.send('Olá, mundo! Esta é uma resposta simples com cabeçalhos personalizados.');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

No caso do comando acima, estamos usando o método setHeader para setar nossos Headers do HTTP.

Eu preciso chamar o setHeader toda vez que eu precisar configurar uma chave de cabeçalho? Não existe uma forma mais automatizada de se fazer isso?

Infelizmente, o Express junto com seu método res.setHeader, é utilizado para definir cabeçalhos HTTP individualmente, e isso significa que ele não aceita um objeto diretamente 😒

O que podemos fazer para contornar isso, é criando uma função externa, que recebará a variável res junto com um objeto que represente nossos cabeçalhos, afim de executar um loop, por exemplo:

index_cabecalho_multiplo.js:

const express = require('express');
const app = express();
const port = 3000;

// Função para configurar múltiplos cabeçalhos a partir de um objeto
const setHeaders = (res, headers) => {
    for (const [key, value] of Object.entries(headers)) {
        res.setHeader(key, value);
    }
};

app.get('/', (req, res) => {
    // Definindo múltiplos cabeçalhos usando um objeto
    const headers = {
        'X-Custom-Header': 'Este é um cabeçalho personalizado',
        'Content-Type': 'text/plain',
        'Cache-Control': 'no-cache',
    };

    setHeaders(res, headers); // Configura os cabeçalhos

    // Enviando a resposta
    res.send('Olá, mundo! Esta é uma resposta com múltiplos cabeçalhos.');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Dessa forma, chamamos a função setHeaders, passando a variável res e o nosso objeto que representa o cabeçalho.

Utilizando status code com Express

Com o Express, podemos retornar alguns códigos de status, veja um pequeno exemplo de como isso pode ser feito no código abaixo:

index_status_code.js:

const express = require('express');

const app = express();

const port = 3000;

app.get('/', (req, res) => {
    res.status(200).send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

No comando acima, note que chamamos dois métodos dentro de uma mesma linha, o status() que envia o código de status ao navegador do cliente, e o send() que envia a mensagem em sí.

Criando uma rota 404 com o Express

Atualmente, se você digitar uma rota que não existe na aplicação, você receberá a seguinte mensagem:

Muito feio, não acha? Que tal criar uma rota personalizada capaz de atender rotas que não existem?

index_404.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

//Sempre use este middleware por último:
app.use(function(req, res, next) {
    res.status(404).send('Desculpe, não conseguimos encontrar essa página.');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que no código acima, criamos um middleware para atender solicitações que não existem.

O que já é uma grande deixa para começarmos a falar sobre ele 😎

O que é um middleware?

Traduzindo do inglês para o bom portguês, um middleware significa "intermediário", e no mundo do express (e também da área de TI), um middleware nada mais são do que códigos que podem ser executados entre uma determinada ação específica.

Por exemplo, vamos imaginar que você quer ir de um ponto A até um ponto B, só que para você chegar no ponto B, primeiro vai ter que passar por uma vistória, que vai dizer se você está apto ou não para ir ao ponto B.

Essa vistoria, geralmente acontece por de baixo dos panos, e você muita vezes nem precebe que está passando por ela, só quando você realmente não está apto a ir ao ponto B, pois nesse caso, você acaba recebendo uma notificação de impedimento 😅

Quer um outro exemplo mais real? 

Quando você chega a um hotel, o processo de check-in inclui várias etapas: verificação da reserva, confirmação da identidade, e talvez até a coleta de um depósito de segurança.

O check-in funciona como um middleware, uma vez que ele processa e valida suas informações, antes que você possa ter acesso ao seu quarto. Sem essa etapa, você não poderia usufruir dos serviços do hotel.

Outro exemplo?

Quando você faz um pedido em um restaurante, o garçom toma o seu pedido e o envia para a cozinha. A cozinha então prepara o prato e o garçom o entrega a você.

O garçom funciona como um middleware porque atua como intermediário entre você (o cliente) e a cozinha (o serviço). Ele processa seu pedido e garante que a comunicação entre você e a cozinha ocorra sem problemas.

Já no mundo da TI, um middleware pode ser representado por:

  • Validação de Dados de Entrada
  • Autenticação e Autorização em Aplicações Web
  • Tratamento de Erros
  • Logging e Monitoramento
  • Gerenciamento de Sessões
  • Controle de Acesso (CORS)
  • Processamento de Arquivos
  • E entre outras...

Consegue perceber que um middleware funciona como uma função (ou classe) que sempre é chamada quando nosso código está prestes a fazer alguma outra coisa?

No express, um middleware pode ser usado por meio do comando use, vejamos como ele funciona na prática.

index_middleware.js:

const express = require('express');
const app = express();
const port = 3000;

const authUser = function(req, res, next) {
    req.authUser = true;

    if(req.authUser){
        console.log('Usuário Autenticado');
        next();
    }else{
        console.log('Usuário não Autenticado');
        res.send('Usuário não Autenticado');
    }
}

app.use(authUser);

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Vamos às explicações 🙃

No comando acima, nós criamos uma costante chamada de authUser, que representa uma função anônima que recebe três variáveis temporárias:

req: variável que representa a requisição, e já sabemos como ela funciona.

res: variável que representa a resposta, também já sabemos como ela funciona.

next: é uma função fundamental dentro do ciclo de vida de um middleware. Sua principal função é passar o controle para o próximo middleware na cadeia de execução (e caso não achar, executa a lógica do callback de uma rota).

Basicamente o next() é uma forma de dizer ao sistema para ele seguir a diante, e no caso do comando acima, como não temos mais nenhum outro middleware, ele abre o callback da rota principal normalmente (app.get('/'...).

Já dentro da função anônima, nós criamos uma nova variável dentro de req chamada de authUser, que representa um boolean que armazena inicialmente o valor de true.

O authUser serve para simularmos se o usuário está autenticado ou não na nossa aplicação.

Em seguida, fazemos uma verificação dentro de uma condicional, e se o authUser for true (e ele realmente é), uma mensagem aparece no terminal e o express executa tudo o que está dentro do app.get, tudo isso graças ao comando next(), que diz a lógica do express que ele pode seguir a diante:

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

Caso o authUser estivesse setado como false, ele também mostraria uma mensagem no terminal, mas em vez de seguir a diante, ele retornaria a mensagem 'Usuário não Autenticado'.

Observação: é importante que você envie uma resposta de volta ao usuário, mesmo que a ele não passe pela validação do seu middleware e execute o next(), pois se não tivéssemos executado um res.send() no else do nosso middleware, nossa aplicaria entraria em loop eterno.

Por fim, temos o comando app.use(authUser) que ativa em toda a nossa aplicação o middleware que existe dentro da função authUser.

Isso significa, que sempre quando o usuário tentar acessar uma de nossas rotas, o middleware authUser sempre seja chamado antes de executar a lógica pertencente à aquela rota 🙂

Veja um outro exemplo mais atualizado que comprova o que eu disse acima:

const express = require('express');
const app = express();
const port = 3000;

const authUser = function(req, res, next) {
    req.authUser = false;

    if(req.authUser){
        console.log('Usuário Autenticado');
        next();
    }else{
        console.log('Usuário não Autenticado');
        res.send('Usuário não Autenticado');
    }
}

app.use(authUser);

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.get('/home', (req, res) => {//Essa rota também está sobre a protação do middleware...
    res.send('Home');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Só que aí nos temos um problema... nem toda rota precisa de um middleware, não é verdade?

Seguindo a lógica do middleware que criamos acima, ele representa a validação do processo de autenticação de um determinado usuário.

Mas concorda comigo que a página principal, página de contatos, página de sobre... não precisa executar esse middleware?

Sendo assim, o que será que nós podemos fazer para que o middleware seja aplicado somente nas rotas em que desejamos que ele seja aplicado?

Simples, observe o exemplo abaixo:

index_middleware_rotas.js:

const express = require('express');
const app = express();
const port = 3000;

// Middleware de autenticação
const authUser = function(req, res, next) {
    req.authUser = false; // Simula que o usuário não está autenticado

    if (req.authUser) {
        console.log('Usuário Autenticado');
        next(); // Usuário autenticado, continua para a próxima função
    } else {
        console.log('Usuário não Autenticado');
        res.send('Usuário não Autenticado'); // Responde ao cliente
    }
}

// Rota para a página inicial
app.get('/home', (req, res) => {
    res.send('Página Inicial');
});

// Rota para a página de sobre
app.get('/sobre', (req, res) => {
    res.send('Página de Sobre');
});

// Rota para a página de contato
app.get('/contato', (req, res) => {
    res.send('Página de Contato');
});

// Rota para a página de dashboard com middleware de autenticação
app.get('/dashboard', authUser, (req, res) => {
    res.send('Página de Dashboard');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que no comando acima, nós excluímos o uso do app.use(), que fazia com que todas as nossas rotas executássem nosso middleware de autenticação.

Note também que a rota /dashboard, foi criada de modo que inserimos mais um parâmetro entre a URL e o callback:

app.get('/dashboard', authUser, (req, res) => {
    res.send('Página de Dashboard');
});

Quando criamos um parâmetro entre a URL e o callback, estamos criando um middleware que está relacionado exclusivamente com aquela rota.

Se você testar a sua aplicação, verá que a rota /dashboard é a única que executa o middleware authUser que nós criamos.

Entre a URL e a sua função de callback, você pode inserir quantos middlewares você quiser, não tem um limite específico. (Veremos o funcionamento disso nos próximos tópicos).

Observação: Você pode usar tanto o app.use() quanto o segundo parâmetro de forma simultânea, mas tenha em mente, que se você fizer isso, o seu sistema vai executar dois middlewares antes de executar o callback da sua rota.

Criando rotas que recebem parâmetros

Durante a sua jornada como desenvolvedor, você vai precisar configurar algumas rotas que recebem algum tipo de parâmetro adicional.

Esses parâmetros podem ser resgatados pela variável req, que contém todos os dados de uma determinada requisição HTTP, e isso geralmente é feito por meio da seguinte sintaxe:

req.params.nome-do-parametro

Já para configurar uma rota que recebe parâmetros, costumamos setar isso da seguinte forma:

'/usuario/:id'

'/ola/:mensagem'

Ou seja, para receber parâmetros em uma rota, basta definir na URL um valor dinâmico, como por exemplo: :nome-do-parâmetro.

Lembrando que este parâmetro pode receber qualquer tipo de dado (string, boolean, number...), apesar de que na captura desses dados, todos eles sejam tratados como uma string.

Vejamos como isso pode ser feito:

index_parametros_url.js:

const express = require('express');
const app = express();
const port = 3000;

app.get('/usuario/:id', (req, res) => {
    const id = req.params.id;
    res.send(`ID do Usuário: ${id}`);
});

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

No comando acima, criamos uma URL dinâmica que pode receber um :id, mas poderia ser qualquer outro nome, como :email, :nome, :micilini ...

Em seguida, estamos recuperando esse id de dentro de req.params, e salvando dentro de uma constante (id).

Por fim, estamos informando ao nosso usuário, o id que ele escolheu:

Como dito anteriormente, você pode inserir qualquer tipo de valor dentro da sua URL dinâmica, que ele vai funcionar perfeitamente, por exemplo:

Além disso, você pode receber mais de um único parâmetro via URL, para isso, basta fazer da seguinte forma:

app.get('/usuario/:id/:nome/:email', (req, res) => {
    const id = req.params.id;
    const nome = req.params.nome;
    const email = req.params.email;
    res.send(`ID do Usuário: ${id}, Nome: ${nome}, Email: ${email}`);
});

Veja como ficou o resultado:

Rotas com parâmetros opcionais

Por vezes, você vai querer que um determinado parâmetro relacionado a uma rota seja opcional, para atingir este objetivo, basta declará-lo com um ponto de interrogação, por exemplo:

'/usuario/:id?'

Vejamos um exemplo prático:

index_parametros_opcionais.js:

const express = require('express');
const app = express();
const port = 3000;

// Rota com parâmetro opcional
app.get('/usuario/:id?', (req, res) => {
    const id = req.params.id;

    if (id) {
        res.send(`ID do Usuário: ${id}`);
    } else {
        res.send('ID do Usuário não fornecido');
    }
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

No comando acima, estamos usando o :id? com um ponto de interrogação no final, o que diz ao express que esse parâmetro é opcional.

Além disso, estamos validando se o parâmetro id veio na URL ou não, e dependendo, mostramos mensagens diferentes aos nossos usuários 😋

Observação: a mesma lógica pode ser aplicada em URLs que recebem mais de um único parâmetro.

Realizando validações em parâmetros de rotas

Vamos supor que você tenha uma determinada rota que receba um parâmetro dinâmico chamado de :id, só que esse parâmetro, precisa ser exclusivamente numérico, o que você faz?

Pois bem, existem diversas formas de se resolver este problema, e a forma mais fácil, é fazendo uma verificação condicional dentro do próprio callback da rota, por exemplo:

index_parametros_validos_1.js:

const express = require('express');
const app = express();
const port = 3000;

// Rota com validação direta no manipulador
app.get('/usuario/:id', (req, res) => {
    const id = req.params.id;

    // Verifica se o id é um número
    if (!/^\d+$/.test(id)) {
        return res.status(400).send('ID deve ser um valor numérico.');
    }

    res.send(`ID do Usuário: ${id}`);
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Observe que recebemos o id da rota, e antes de retornar uma mensagem ao usuário, fizemos uma validação dentro de uma condicional com Regex, onde verificamos se o id é numérico.

E se ele for numérico, o sistema deixa ele passar, e executa o res.send(), se não, ele retorna um status 400 junto com uma mensagem de erro.

A mesma validação pode ser feita por meio de um middleware, observe:

index_parametros_validos_2.js:

const express = require('express');
const app = express();
const port = 3000;

// Middleware para validar se o parâmetro id é numérico
const validateNumericId = (req, res, next) => {
    const id = req.params.id;

    if (/^\d+$/.test(id)) {
        // ID é numérico, continue para o próximo middleware ou manipulador
        next();
    } else {
        // ID não é numérico, retorna um erro 400 (Bad Request)
        res.status(400).send('ID deve ser um valor numérico.');
    }
};

// Rota com parâmetro obrigatório e validação numérica
app.get('/usuario/:id', validateNumericId, (req, res) => {
    const id = req.params.id;
    res.send(`ID do Usuário: ${id}`);
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

A única diferença que nós temos para o código anterior, é que neste caso em específico, transportamos a lógica de validação para dentro de um middleware 😉

Existem alguns projetos que fazem o uso da biblioteca express-validator, para fazer esses tipos de validações nas rotas, observe um exemplo:

index_parametros_validos_3.js:

const express = require('express');
const { param, validationResult } = require('express-validator');
const app = express();
const port = 3000;

// Rota com validação usando express-validator
app.get('/usuario/:id',
    param('id').isInt().withMessage('ID deve ser um número inteiro'),
    (req, res) => {
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        const id = req.params.id;
        res.send(`ID do Usuário: ${id}`);
    }
);

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

O express-validator, é uma biblioteca popular para validação e sanitização de dados em aplicações feitas com o express. Para utilizá-lo certifique-se de que você o instalou na sua aplicação: npm install express-validator.

No comando acima, usamos esse validador como segundo parâmetro do app.get(), onde estamos selecionando o parâmetro id (param('id')), e em seguida:

  • Verificando se ele é do tipo inteiro (isInt())
  • E caso não for, o retorno será uma mensagem (withMessage())

No caso do retorno, isso é feito de forma automática pelo express-validator, pois de baixo dos panos, ele também atua como um middleware 🙂

E se caso a nossa rota tivesse mais de dois parâmetros? Como ficaria?

Simples, basta utilizar a mesma lógica da seguinte forma:

const express = require('express');
const { param, query, validationResult } = require('express-validator');
const app = express();
const port = 3000;

// Middleware para análise de query strings
app.use(express.json());

// Rota com validação para ID e e-mail
app.get('/usuario/:id',
    // Validação do parâmetro ID
    param('id').isInt().withMessage('ID deve ser um número inteiro'),

    // Validação do e-mail passado como query string
    query('email').isEmail().withMessage('E-mail inválido'),

    (req, res) => {
        // Verificação de erros
        const errors = validationResult(req);
        if (!errors.isEmpty()) {
            return res.status(400).json({ errors: errors.array() });
        }

        // Obtendo o ID e e-mail dos parâmetros e query string
        const id = req.params.id;
        const email = req.query.email;

        res.send(`ID do Usuário: ${id}, E-mail: ${email}`);
    }
);

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que no caso do :email ele usa o método isEmail(). Além disso, observe que podemos inserir quantos middlewares quisermos dentro do app.get() 🧐

Trabalhando com o método POST com o Express

O método POST é utilizado para receber requisições, principalmente de formulários.

No express, isso pode ser feito por meio do comando app.post(), em conjunto com um middleware especializado em recuperar esses parâmetros do body  da requisição usando o express.json.

Isso pode ser feito por meio de dois middlewares que podemos usar na nossa aplicação:

app.use(express.urlencoded({ extended: true }));
app.use(express.json());

Vejamos na prática como isso pode ser feito 😄

Vamos supor que nós temos uma rota chamada /dados que vai receber duas informaçoes de um determinado formulário: nome e sobrenome.

index_post.js:

const express = require('express');
const app = express();
const port = 3000;

//Middlewares para tratar as respostas de requisições POST
app.use(express.urlencoded({ extended: true }));
app.use(express.json());

app.post('/dados', (req, res) => {
    console.log(req.body);
    res.send('Dados recebidos com sucesso!');
});

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

express.urlencoded({ extended: true }): é responsável por processar dados enviados em formulários HTML, usando o método application/x-www-form-urlencoded.

Quando um formulário HTML é enviado com o método POST, os dados são enviados como uma string codificada no formato application/x-www-form-urlencoded.

Esse middleware decodifica essa string e a transforma em um objeto JavaScript acessível via req.body.

extended: true permite que você use a biblioteca qs para análise de dados aninhados, como objetos e arrays.

Caso a configuração estivesse setada como extended: false, o middleware usaria a biblioteca querystring para análise, que não suporta dados aninhados e tem limitações em comparação com qs.

express.json(): é responsável por processar dados enviados no formato JSON.

Quando você faz uma requisição POST com um corpo no formato JSON (muitas vezes de APIs), o express.json() analisa o corpo da requisição e o transforma em um objeto JavaScript acessível via req.body.

No seu código, esses middlewares são usados em conjunto para garantir que sua aplicação possa lidar com ambos os formatos de dados comuns em requisições POST.

Por fim, estamos pegando a resposta do formulário por meio do req.body e mostrando os resultados no terminal.

Legal, mas como podemos testar esse código?

Para isso, vamos criar um novo arquivo em HTML que vai representar o nosso formulário de envio.

formulario_post.html:

<!DOCTYPE html>
<html lang="pt-BR">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Formulário de Dados</title>
</head>
<body>
    <h1>Formulário de Dados</h1>
    <form action="http://localhost:3000/dados" method="POST">
        <label for="nome">Nome:</label>
        <input type="text" id="nome" name="nome" required>
        <br>
        <label for="sobrenome">Sobrenome:</label>
        <input type="text" id="sobrenome" name="sobrenome" required>
        <br>
        <button type="submit">Enviar</button>
    </form>
</body>
</html>

Abra esse arquivo com o seu navegador, preencha os dados e clique no botão enviar.

Automaticamente você será direcionado ao servidor que está sendo gerenciado express, onde receberá as seguintes mensagens (tanto na UI quant no terminal):

Como os dados estão salvos dentro de req.body, e eles representam um objeto, acredito que você já saiba o que fazer para acessá-los separadamente 😉

app.post('/dados', (req, res) => {
    console.log('Nome: ' + req.body.nome);
    console.log('Idade: ' + req.body.sobrenome);
});

Apesar do código acima funcionar muito bem, talvez você esteja se perguntando: Existem alguma forma de fazer com que os middlewares do método POST sejam exclusivos da rota /dados? Até porque, eu não vou receber, e muito menos trabalhar com eles em outras rotas. O que os tornam totalmente desnecessários estarem marcado como globais.

Sim, e você está totalmente CERTO 🥳

Atualmente existem duas formas de se fazer isso:

1) Você pode aplicar os middlewares diretamente na sua rota:

const express = require('express');
const app = express();
const port = 3000;

// Middleware para processar dados codificados em URL e JSON aplicado apenas na rota /dados
app.post('/dados',
    express.urlencoded({ extended: true }),
    express.json(),
    (req, res) => {
        console.log(req.body); // Mostra o corpo da requisição
        res.send('Dados recebidos com sucesso!');
    }
);

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

2) Você pode criar uma constante que armazene esses dois middlewares:

const express = require('express');
const app = express();
const port = 3000;

// Middleware específico para /dados
const dadosMiddleware = [
    express.urlencoded({ extended: true }),
    express.json()
];

// Rota com middlewares específicos
app.post('/dados',
    dadosMiddleware,
    (req, res) => {
        console.log(req.body); // Mostra o corpo da requisição
        res.send('Dados recebidos com sucesso!');
    }
);

app.get('/', (req, res) => {
    res.send('Meu Primeiro Servidor com Express =D');
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

No caso da segunda alternativa, ela é a mais recomendada, pois evita duplicação de código, uma vez que se precisarmos receber dados em outra rota do tipo POST, pelo menos já temos uma variável que contém essa lógica, ou você prefere ficar repetindo código?

Importando arquivos estáticos com Express

O express possui suporte a inserção de arquivos estáticos nas suas páginas, e para isso, vamos precisar de um middleware capaz de fazer isso.

Vamos usar um novo comando chamado de express.static, que serve arquivos estáticos, como imagens, folhas de estilo (CSS) e scripts JavaScript, diretamente do seu sistema de arquivos.

Para começarmos, vamos criar uma nova pasta chamada public, que será o local onde iremos armazenar todos os nossos arquivos estáticos.

E aproveitando que você acabou de criar essa pasta, crie um novo arquivo chamado de home.css, com a seguinte folha de estilo:

public > home.css:

body {
    font-family: Arial, sans-serif;
    background-color: #f0f0f0;
    margin: 0;
    padding: 0;
    text-align: center;
}

h1 {
    color: #333;
    margin-top: 50px;
}

p {
    color: #666;
}

Em seguida, vamos criar o nosso arquivo que vai conter o nosso middleware.

index_public.js:

const express = require('express');
const app = express();
const port = 3000;

// Serve arquivos estáticos da pasta 'public'
app.use(express.static('public'));

// Rota principal que envia uma página HTML com um link para o CSS
app.get('/', (req, res) => {
    // HTML inline que chama o arquivo home.css
    const html = `
        <!DOCTYPE html>
        <html lang="pt-BR">
        <head>
            <meta charset="UTF-8">
            <meta name="viewport" content="width=device-width, initial-scale=1.0">
            <title>Meu Primeiro Servidor com Express</title>
            <link rel="stylesheet" href="/home.css">
        </head>
        <body>
            <h1>Bem-vindo ao Meu Primeiro Servidor com Express =D</h1>
            <p>Este é um exemplo de como usar arquivos estáticos com Express.</p>
        </body>
        </html>
    `;
    
    // Envia o HTML como resposta
    res.send(html);
});

app.listen(port, () => {
    console.log(`Servidor está rodando em http://localhost:${port}/`);
});

Note que dentro do HTML inline, estou importanto nossa folha de estilo de forma direta com o comando: /home.css.

Por de baixo dos panos, o middleware (express.static) vai se encarregar de interceptar todas as requisições feitas pelas URLs que correspondem aos arquivos estáticos, de modo a fazer as devidas alterações para apontar para o arquivo original.

Veja como ficou o resultado final:

Observação: caso o seu arquivo home.css estivesse dentro de uma pasta específica em public (exemplo: public > css > home.css), não se esqueça de adicionar o /css/home.css durante a importação do seu HTML inline 😉

Abstraindo suas rotas para um módulo específico

Durante as jornadas do Portal da Micilini, você vai perceber que estamos batendo sempre na mesma tecla, organização estrutural do nosso código!

Aqui nos preocupamos muito com os conceitos de arquitetura limpa e código limpo, visando sempre adotar as melhores estratégias em cada uma de nossas lições 🤓

E aqui na jornada de NodeJS, isso não podia ser diferente 😆

Atualmente as nossas rotas estão sendo criadas dentro de um único arquivo chamado de index.js, onde dentro dele podem haver um conglomerado de códigos (imagina um index.js com mais de 10 mil linhas?).

E isso é ruim no quesito de organização e estrutura da nossa aplicação. Pensando nisso, por que nós não fazemos a separação por módulos?

Para isso, dentro da pasta raiz do seu projeto, você vai precisar criar uma nova pasta chamada de routes:

Essa pasta será responsável por armazenar cada arquivo que representa as nossas rotas.

Vamos supor que a nossa aplicação vai possuir 5 rotas diferentes, como por exemplo:

  • /home
  • /sobre
  • /contato
  • /usuarios/:id
  • /usuarios/:email

Para isso, você vai precisar criar dentro da pasta routes, 4 arquivos diferentes:

/seu-projeto
  /routes
    usuarios.js
    home.js
    sobre.js
    contato.js
index_routes_organizado.js

E não se esqueça do index_routes_organizado.js dentro da pasta raiz do seu projeto, pois é nele que juntaremos os restantes dos arquivos 😉

usuarios.js:

const express = require('express');
const router = express.Router();

// Rota para obter usuário por ID
router.get('/:id', (req, res) => {
  // Lógica para obter usuário por ID
  res.send(`Usuário com ID ${req.params.id}`);
});

// Rota para obter usuário por email
router.get('/email/:email', (req, res) => {
  // Lógica para obter usuário por email
  res.send(`Usuário com email ${req.params.email}`);
});

module.exports = router;

home.js:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  // Lógica para a página inicial
  res.send('Página Home');
});

module.exports = router;

sobre.js:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  // Lógica para a página Sobre
  res.send('Página Sobre');
});

module.exports = router;

contato.js:

const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  // Lógica para a página de Contato
  res.send('Página Contato');
});

module.exports = router;

Observe que cada um dos arquivos acima, representam módulos que são exportados pelo comando module.exports.

Além disso, estamos fazendo o uso do comando express.Router(), que é uma funcionalidade do Express que facilita a criação de módulos de roteamento.

Com ele, você consegue definir e organizar suas rotas em diferentes arquivos, o que promove uma estrutura de código mais modular e gerenciável.

Além disso, note que não precisamos ficar digitando /home ou /usuarios/:id e qualquer outra rota que leve o nome principal... pois a URL principal da rota será administrada pelo index.js!

Observação: Se por um acaso você precisar criar uma nova rota chamada de /home/evento, como estamos fazendo o uso do /home e ele já existe. Basta adicionar uma nova rota dentro de home.js.

Caso o nome principal da sua rota ainda não exista, e você desejar adicionar um novo conjunto de rotas, como por exemplo: /produtos, basta criar um novo arquivo chamado produtos.js dentro de routes.

Por fim, basta que você chame cada um desses módulos dentro do seu index.js da seguinte forma.

index_routes_organizado.js:

const express = require('express');
const app = express();
const port = 3000;

// Importar os roteadores
const usuariosRouter = require('./routes/usuarios');
const homeRouter = require('./routes/home');
const sobreRouter = require('./routes/sobre');
const contatoRouter = require('./routes/contato');

// Usar os roteadores
app.use('/usuarios', usuariosRouter);
app.use('/home', homeRouter);
app.use('/sobre', sobreRouter);
app.use('/contato', contatoRouter);

app.listen(port, () => {
  console.log(`Servidor rodando na porta ${port}`);
});

Note que os nossos arquivos de roteamento, eles estão sendo atribuídos a middlewares, que representam os nossos módulos (app.use()).

É dessa forma que modularizamos as rotas da nossa aplicação 🤓

Devo seguir com multiplos arquivos de roteamento, ou usar apenas um só?

No NodeJS com express, a abordagem de criar múltiplos arquivos para as nossas rotas, é uma prática comum e recomendada para a maioria dos casos, especialmente em aplicações maiores.

Em outros frameworks, como é o caso do ReactJS e Laravel, a abordagem segue um caminho um pouco diferente, onde concentramos todas as rotas em apenas um único arquivo, que geralmente é chamado de routes.js.

No entanto, a estratégia de ter um único arquivo (routes.js) pode funcionar bem em projetos menores ou em estágios iniciais de desenvolvimento, apesar de não ser recomendado em aplicações com NodeJS e express.

Com relação a abordagem de rotas com multiplos arquivos, temos algumas vantagens em relação a isso:

Organização: cada arquivo pode gerenciar um grupo específico de rotas, tornando a estrutura mais limpa e fácil de entender.

Manutenção: facilita a manutenção e a atualização de rotas específicas sem impactar outras partes do código.

Escalabilidade: permite uma escalabilidade mais fácil à medida que o projeto cresce, já que novas rotas podem ser adicionadas em novos arquivos.

Já com relação a abordagem de rotas usando um único arquivo, também tem suas vantagens:

Simplicidade: ideal para projetos pequenos ou em fase inicial, onde a complexidade das rotas é limitada.

Centralização: todas as rotas estão em um único arquivo, o que pode ser mais fácil de visualizar e gerenciar quando há poucas rotas.

Mas.... também conta com algumas desvantagens:

Manutenção: pode se tornar difícil de gerenciar à medida que o número de rotas aumenta.

Escalabilidade: menos ideal para projetos maiores onde a organização e a separação de responsabilidades são importantes.

Vejamos um exemplo do uso routes.js seguindo a abordagem de rotas com apenas um único arquivo:

const express = require('express');
const router = express.Router();

// Rota para obter usuário por ID
router.get('/usuarios/:id', (req, res) => {
  res.send(`Usuário com ID ${req.params.id}`);
});

// Rota para obter usuário por email
router.get('/usuarios/email/:email', (req, res) => {
  res.send(`Usuário com email ${req.params.email}`);
});

// Rota para a página inicial
router.get('/home', (req, res) => {
  res.send('Página Home');
});

// Rota para a página Sobre
router.get('/sobre', (req, res) => {
  res.send('Página Sobre');
});

// Rota para a página de Contato
router.get('/contato', (req, res) => {
  res.send('Página Contato');
});

module.exports = router;

Veja como o seu index.js pode trabalhar com isso:

const express = require('express');
const app = express();
const port = 3000;

// Importar e usar o roteador de rotas
const routes = require('./routes/routes');
app.use('/', routes);

app.listen(port, () => {
  console.log(`Servidor rodando na porta ${port}`);
});

Sendo assim, sempre vem aquela dúvida no final: qual abordagem escolher?

Para Projetos Pequenos: usar um único arquivo routes.js pode ser suficiente e mais simples.

Para Projetos Médios a Grandes: a abordagem de múltiplos arquivos é geralmente mais adequada, pois facilita a organização e manutenção do seu código.

Dando uma opnião sincera, pelo sim ou pelo não, eu recomendo que você sempre opte por criar suas rotas seguindo a abordagem de multiplos arquivos, pois nunca se sabe se um projeto pequeno vai se tornar médio ou grande a longo prazo, e caso aconteça, seu código já vai estar preparado 😉

Arquivos da lição

Os arquivos que você viu durante o decorrer desta lição, podem ser encontrados neste link.

Conclusão

Nesta lição, você aprendeu sobre o uso de um dos melhores frameworks de NodeJS, o Express 🥳

Viu também o funcionamento de um middleware, criação de rotas e como modularizá-las 😍

Mas se você pensa que o uso do Express acabou por aqui, você está muito engando!

Ele está apenas começando....

Te espero na próxima lição!

Criadores de Conteúdo

Foto do William Lima
William Lima
Fundador da Micilini

Inventor nato, escreve conteudos de programação para o portal da micilini.

Torne-se um MIC 🤖

Mais de 100 mic's já estão conectados na plataforma.