JSagon NodeJS Framework

Construa aplicações eficientes e escaláveis de maneira simples e rápida.


Middlewares

Resumidamente, middlewares são funções a serem executadas durante uma requisição antes da ação principal propriamente dita. Um exemplo de middleware muito comum é a verificação de autenticidade do usuário, verificar se ele possui permissão antes de poder editar ou ver determinada página ou registro.

Existe a possibilidade de criar middleware global, por aplicação e por rota. Middlewares globais serão executados em todas as aplicações e em todas as suas rotas. Middlewares por aplicação quando definidos serão executados apenas nas rotas da aplicação em questão. E por fim, os middlewares por rota são aqueles que serão executados apenas quando a rota específica for requisitada.

Middleware global e por aplicação

Os middlewares globais são inseridos em /dev/src/http/kernel.ts, e os middlewares por aplicação em /dev/src/app/NomeDaAplicacao/http/kernel.ts, ambos possuem a mesma estrutura de criação. Por motivo de melhor organização, é recomendado a criação de middlewares em arquivos externos e em um pasta que condiz com o seu objetivo.

No exemplo a seguir criaremos um middleware global que simplesmente mostra uma mensagem no console log antes de chamar a ação principal.

Criaremos um arquivo /dev/src/http/Middlewares/Log.ts com o seguinte código:

    import { Request, Response } from '@jsagon/core'
    
    export const LogMiddleware = (req: Request, res: Response, next: Function): void => {
        console.log("Log middleware")
        next()
    }

No exemplo anterior, exportamos uma função seguindo a mesma convenção de uma rota padrão para podermos usá-la fora do arquivo. O terceiro parâmetro, next, é responsável por prosseguir com a execução para a próxima execução.

Agora é preciso editar o arquivo /dev/src/http/kernel.ts para registrar o middleware anterior. Para isso, faça como o exemplo a seguir:

    import { LogMiddleware } from './Middlewares/Log'
    import { AuthMiddleware } from './Middlewares/Auth'
    
    export default {
      // Middlewares that should run for every route in project - Middleware que rodará em todas as rotas do projeto
      globalMiddlewares: {
        registered: {
          auth: AuthMiddleware
        },
        // Middlewares that should run before a route - Middleware a ser executado antes da rota
        before: [
          LogMiddleware,
          'auth'
        ],
        // Middlewares that should run after a route - Middleware a ser executado depois da rota
        after: []
      }
    }

No código anterior, os middlewares de Log e Auth são importados de seus arquivos e adicionados ao kernel. O Auth é registrado com um alias, um apelido, e posteriormente adicionado ao before dos middlewares globais. O Log é passado diretamente para o before. Inicializando a aplicação e executando qualquer requisição no sistema, as funções deverão ser executadas e suas mensagens mostradas no console.

Conceitos: before e after

Tanto os middlewares globais como os por aplicação possuem as chaves "before" e "after". A lista de middlewares registrada em "before" serão executadas antes da rota principal ser executada, sendo este o modelo de middleware mais comumente usado. E a lista de middlewares registrada em "after" é para aquelas funções que serão executadas após a execução da rota principal, sendo estes usados para propósitos muito específicos.

Obs.: os middlewares adicionados em "before" necessitam da execução do "next" para prosseguir na cadeia de execução.

Abaixo veremos como adicionar os middlewares diretamente nas rotas em dois cenários, utilizando decorators ou o construtor de rotas.


Middlewares - Decorators

Um controlador com middlewares definidos em suas actions fica assim:

    import { ControllerBase, Request, Response, Controller, Post, Get, Middleware} from '@jsagon/core'
    
    @Controller()
    class HomeController extends ControllerBase {
    
      @Get('/admin')
      @Middleware('auth')
      public async admin(req: Request, res: Response) {
         // tela de administrador
      }
    
      @Post('/login')
      @Middleware([], ['auth'])
      public async login(req: Request, res: Response) {
         // realizar login no sistema
      }}
    
    }
    
    export default HomeController

No exemplo anterior foi definido que para acessar a tela de admin, o middleware de autenticação precisa ser executado previamente. Caso o middleware 'auth' esteja registrado globalmente, não é necessário especificar como no exemplo anterior. No método de login, o auth foi passado como segundo parâmetro, skip, ou seja, pular a execução do middleware quando chamado a rota /login.

Definição:

Middleware(string | string[] | Function | Function[], string | string[]) Primeiro parâmetro, middlewares a serem executados na chamada da rota. Segundo parâmetro, middlewares que deverão ser pulados na execução da rota.

Middlewares - Construtor de Rota

Utilizando do construtor de rotas, é possível adicionar middlewares específicos para determinadas rotas.

É possível adicionar middlewares no instanciamento do construtor, ou chamando seus métodos setBaseMiddleware ou addBaseMiddleware. Tanto a passagem do middleware no instancionamento quanto no setBaseMiddleware aceitam um único middleware ou um array de middlewares, já o addBaseMiddleware aceitando apenas um único.

Obs.: O setBaseMiddleware sobrescreve a lista de middlewares anteriores já definidas. E o addBaseMiddleware adiciona um middleware a lista atual de middlewares.

Um exemplo prático do exemplo anterior:

    import { Route } from '@jsagon/core'
    import Log from '../Middlewares/Log'
    import Auth from '../Middlewares/Auth'
    import LogAuth from '../Middlewares/LogAuth'
    import HomeController from '../Controller/HomeController'
    
    // Passando um middleware Log no construtor.
    const route = Route('/', HomeController, [Log])
      .index()
      .get({ uri: '/entrar', action: 'login' })
      // Registrando um único ou uma nova lista de middlewares base. Todos os middlewares anteriores não serão mais executados nas rotas seguintes
      // Podendo ser passado também como .setBaseMiddleware(Auth) por ser um único.
      .setBaseMiddleware([Auth])
      .get({ uri: '/admin', action: 'viewAdmin' })
      // Adicionando um novo middleware sem a remoção dos anteriores.
      .addBaseMiddleware(LogAuth)
      .get({ uri: '/sair', action: 'logout' })
    
    export default [ route ]

Quando um middleware é definido, todas as rotas registradas após ele farão parte do seu escopo. No exemplo anterior, quando foi registrado o middleware Auth (".setBaseMiddleware([Auth])"), em todas as rotas posteriores, admin e sair, será verificado se o usuário tem permissão de acesso. Neste caso, até que um novo setBaseMiddleware seja registrado.


Ordem de execução

Os middlewares possuem a seguinte ordem de execução: Middlewares globais > Middlewares por aplicação > Middlewares por rota. E cada um seguindo a sua ordenação de como foi adicionada a lista.