Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Nest.js: adicionando funcionalidades com Redis, JWT e logging

Nest.js: adicionando funcionalidades com Redis, JWT e logging

Otimização com cache e Redis - Apresentação

Evaldo: Olá! Boas-vindas a mais este curso de NestJS. Meu nome é Antônio Evaldo, e serei um dos instrutores.

Audiodescrição: Para quem não está o vendo, Antônio Evaldo se identifica como um homem de pele clara, olhos castanhos escuros, utilizando óculos com armação arredondada. Possui bigode, cavanhaque e cabelo preto encaracolado que está preso para trás. No corpo, veste uma camiseta azul-marinho. Ao fundo, um quadro de guitarra pendurado sobre uma parede lisa e iluminada em tons de azul e rosa.

Camila: Olá! Eu sou a Camila e também serei instrutora neste curso.

Audiodescrição: Caso não possa a ver, Camila se identifica como uma mulher de pele clara, rosto com feições arredondadas, cabelos e olhos castanhos. Usa óculos de armação arredondada e veste uma camisa preta. Ao fundo, uma parede lisa e iluminada em tons de azul e rosa.

Para Quem é Este Curso?

Este curso é destinado a pessoas que já possuem conhecimento sobre APIs com NestJS e com TypeORM e desejam aperfeiçoar ainda mais seus projetos, sempre seguindo boas práticas de mercado.

E o que vamos encontrar nesse curso, Evaldo?

O Que Aprenderemos?

Evaldo: Vamos abordar muitos temas avançados e interessantes, Camila. Inicialmente, começaremos a otimizar as rotas da nossa aplicação. Veremos cacheamento, o Redis (mecanismo de armazenamento no valor do banco de dados em memória), e como integrá-lo na nossa API.

Em seguida, implementaremos um sistema de autenticação, desde a etapa de cadastro até o momento de login. Vamos explorar alguns recursos do Nest Combo Pipe, reforçaremos a segurança das pessoas usuárias e criaremos uma rota para login com o JWT (JSON Web Tokens ou Tokens Web JSON) e o Bcrypt.

Aprenderemos também sobre descriptografia, e como utilizar os Guards do NestJS para proteção das rotas.

Em seguida, asseguraremos os dados da nossa aplicação, utilizando boas práticas de segurança, como variáveis de ambiente, e recursos para proteger automaticamente algumas informações que não devem ser enviadas ao front-end.

Para encerrar, implementaremos um sistema de Logs aplicado com os interceptores do Nest, prática bastante popular no mercado.

Camila: Isso mesmo, Evaldo. Vamos implementar todos esses conceitos no nosso projeto: nossa API de e-commerce da Compree, uma loja de produtos variados. Como estamos atuando em equipe, adotaremos a metodologia do pair-programming (programação pareada) para efetuar as tarefas que chegam no Trello da nossa equipe.

Pré-requisitos

Para aproveitar ao máximo este curso, é necessário que você possua conhecimento em REST API com NestJS, noções sobre criptografia e tokens JWT, e familiaridade com o padrão de arquitetura MVC (Model-View-Controller).

Evaldo: Além dos vídeos, disponibilizamos vários materiais complementares para aprofundar seus estudos. Propusemos também vários desafios para você praticar.

Em caso de dificuldades, recorra ao nosso fórum, responderemos tão rápido quanto possível. Além disso, recomendamos a utilização do Discord da Alura para interação com as demais pessoas estudantes.

Vamos estudar!

Camila: Vamos lá!

Otimização com cache e Redis - Utilizando cache do Nest

Camila: Olá! Vamos retomar o nosso e-commerce do curso anterior, nosso produto, que agora tem nome, a Compree.

Nós já tivemos várias implementações. Agora, vamos supor que a nossa e-commerce já está em funcionamento, recebendo muitos acessos simultâneos, principalmente durante períodos de promoção. Isso acaba impactando o desempenho do nosso e-commerce, sobrecarregando nosso servidor, que pode até levar a nossa e-commerce Compree a cair.

E se nós estamos utilizando a ferramenta Nest, um framework com muitos recursos, será que ele tem algum recurso que possa nos ajudar nesse sentido?

Evaldo: Sim, temos mesmo! Estamos prevendo essa situação.

Acessando o nosso Trello, veremos na coluna "Pronto para iniciar" que a equipe nos passou um cartão de otimizar requisições para duas rotas, uma de GET/produtos por ID e outra de GET/produtos.

Conteúdo do cartão no Trello:

Otimizar requisições na rota de GET /produtos/:id e GET /produtos

Vamos começar a desenvolver agora, então vamos passar esse cartão para a coluna "Desenvolvendo". E, como perguntado, o Nest possui um recurso que vai nos ajudar a otimizar para quando houver muitas requisições nessas rotas.

Vamos acessar o VS Code. Para utilizar esse recurso que vai nos ajudar, precisamos instalar alguns pacotes. Então, abriremos o terminal do VS Code, configurado na pasta do projeto.

Lembrando que nesta etapa já temos o projeto configurado. Já adicionamos o .env, já executamos no terminal o docker-compose up -d e já rodamos o npm install. Portanto, já temos várias coisas rodando.

Neste momento, vamos instalar os pacotes mencionados. Para isso, no terminal, vamos digitar npm install, adicionando @nestjs/cache-manager, onde o cache-manager será o nosso gerenciador de cache. Vamos especificar a versão @2.0.1 e a flag -E para instalar exatamente a nossa versão.

Vamos adicionar também outro pacote, chamado cache-manager também. A diferença é que este último não é do Nest, e sim um pacote externo que o Nest utiliza. Junto com o nome desse pacote, vamos especificar a versão @5.2.3, e a flag -E para instalar exatamente essa versão no nosso projeto.

npm install @nestjs/cache-manager @2.0.1 -E cache-manager@5.2.3 -E

Após executar o comando, vamos aguardar a instalação, e essa parte pode demorar um pouco, dependendo da máquina.

Após a instalação, vamos executar a aplicação, digitando npm run start:dev.

npm run start:dev

Com isso, vamos poder testar no Postman, e ver se está tudo funcionando como deveria.

Com esse processo em andamento, vamos ao Postman. Na aba de explorador à esquerda, podemos ver que configuramos uma seção "Compree" com as pastas "Pedidos", "Usuarios" e "Produtos".

Disponibilizamos na atividade de configuração do ambiente a maneira de importar um arquivo no Postman com esta estrutura pronta.

A rota a ser otimizada é a de produtos, portanto, vamos expandir o conteúdo da pasta "Produtos". Entre seus arquivos, vamos selecionar o de GET da rota "Obter produto por ID".

No centro da página, veremos no campo de URL um endereço com um id que sabemos que é válido.

http://localhost:3000/produtos/076ba09f-bd0f-44ad-bb29-2497bb32a230

Vamos voltar para o terminal para verificar que a aplicação está rodando sem nenhum erro.

Voltando para o Postman, vamos clicar em "Send" para enviar essa requisição GET na rota localhost:3000/produtos/, passando esse ID válido. Caso você ainda não tenha nenhum produto, basta você adicionar um, pegar o id válido dele, e colocar nesta URL.

Obtivemos o retorno do Postman na guia "Body" com algumas informações do produto, o que indica que está funcionando corretamente.

O próximo passo deve ser otimizar a rota.

Otimizando a Rota

Acessando o caminho de diretórios "src > modulos > produto" por meio do explorador do VS Code, acessaremos o módulo produto.controller.ts.

Em seu interior, abaixo do @Get referente ao método listaUm(), utilizaremos um outro Decorator chamado @UseInterceptors. Ao digitar, o VS Code sugere importá-lo de @nestjs/common. Vamos pressionar "Enter" nessa sugestão, e a importação será feita no início do arquivo.

import {
    Body,
    Controller,
    Delete,
    Get,
    Param,
    Post,
    Put,
    UseInterceptors
} from '@nestjs/common';

Acrescentaremos um bloco de parênteses ao Decorator entre os quais vamos usar o interceptador CacheInterceptor. Entenderemos como ele funciona mais adiante.

Ao digitá-lo, o VS Code sugere importá-lo de @nestjs/common ou nestjs/cache-manager. Qual deles devemos importar, Camila?

Camila: Vamos usar a dependência que vem do cache-manager, que acabamos de instalar e é um recurso nativo do NestJS.

Evaldo: Exatamente. Se importarmos do outro pacote, o código apresentará erro, então é necessário escolher exatamente esta opção.

Ao pressionar "Enter", podemos confirmar a importação no início do código, onde mostra o import de cacheManager.

import { CacheInterceptor } from '@nestjs/cache-manager';

Será que ao salvar esse arquivo, o código ainda vai rodar?

Camila: Vamos verificar no terminal.

[Nest] 9936 01/08/2023, 16:47:49 ERROR [ExceptionHandler] Nest can't resolve depend encies of the Cache Interceptor (?, Reflector). Please make sure that the argument CACHE MANAGER at index [0] is available in the ProdutoModule context.

Potential solutions:

@Module({

imports: [/* the Module containing CACHE_MANAGER */ ]

})

Deu um erro, não é mesmo? Então, qual é o próximo passo? Como resolver esse erro que parece ser de dependência?

Camila: Isso mesmo, temos que reforçar que o Nest tem vários recursos integrados e encadeados. Para usar alguns desses recursos, precisamos chamar o CacheManager de forma global no nosso AppModule.

Então, vamos fazer isso, Evaldo.

Evaldo: Antes disso, como prometido, vamos explicar um pouco mais sobre o interceptor.

Entendendo o Interceptor

Basicamente, no Nest, o interceptor permite a execução de código antes e depois de um controlador. No caso específico do CacheInterceptor, queremos que ele armazene os dados do produto que queremos otimizar em um local especial, chamado cache.

No entanto, como já mencionado, precisamos primeiro declará-lo na área de imports do appModule.

Para isso, vamos abrir o arquivo appModule.ts dentro da pasta "src" e, entre os colchetes da área de imports, abaixo de PedidoModule adicionaremos o módulo CacheModule que contém o interceptor.

@Module({
    imports: [
        UsuarioModule, ProdutoModule,
        ConfigModule.for Root({
            isGlobal: true,
        }),
        TypeOrmModule.forRootAsync({
            useClass: PostgresConfigService,
            inject: [PostgresConfigService],
        }),
        PedidoModule,
        CacheModule,
    ],
    // Código omitido

O VS Code sugerirá a importação do @nestjs/common, mas sabemos que não utilizaremos esse pacote, portanto, vamos ignorá-lo. Ele não sugeriu o pacote correto, portanto, vamos adicioná-lo manualmente no topo do arquivo, abaixo do último import.

import { CacheModule } from '@nestjs/cache-manager';

Salvaremos o arquivo para atualizá-lo e, em seguida, voltaremos para o arranjo de imports. Nele, vamos adicionar o método chamado register() ao CacheModule.

Com ele, conseguimos passar algumas configurações. Como queremos torná-lo global, então adicionamos entre seus parênteses um objeto de configurações com a propriedade isGlobal com valor true. Assim, não precisamos importar o módulo em todas as partes do código que desejamos usar o gerenciador de cache.

A outra propriedade que queremos adicionar é chamada ttl, que significa Time to Live (Tempo de vida), com o valor 10000.

@Module({
    imports: [
        UsuarioModule, ProdutoModule,
        ConfigModule.for Root({
            isGlobal: true,
        }),
        TypeOrmModule.forRootAsync({
            useClass: PostgresConfigService,
            inject: [PostgresConfigService],
        }),
        PedidoModule,
        CacheModule.register({ isGlobal: true, ttl: 10000 }),
    ],
    // Código omitido

Camila: Com o ttl no valor 10000, vamos instruir o nosso cache a manter essa informação na memória durante 10 segundos. Essa relação de chave-valor está dizendo exatamente isso, conforme nós veremos na prática em breve.

Evaldo: Perfeito. Então, vamos lá.

Vamos salvar este arquivo e abrir o terminal para ver se o erro desapareceu. Parece que sim.

Testando o Cache

Antes de testar a aplicação no Postman com um novo GET, vamos acessar o arquivo produto.controller.ts e inserir um console.log() entre as chaves de async listaUm(), logo abaixo da const produtoSalvo que recupera o produto salvo do banco de dados.

Entre os parênteses desse console.log(), adicionaremos a mensagem produto sendo buscado do BD! entre aspas simples.

    @Get('/:id')
    @UseInterceptors(CacheInterceptor)
    async listaUm(@Param('id') id: string) {
        const produtoSalvo = await this.produtoService.listaUmProduto(id);

        console.log('Produto sendo buscado do BD!');

        return produtoSalvo;
    }

Vamos salvar o código para formatar e esperamos que recompile observando o terminal. Deu certo e não retornou erros. Portanto, vamos voltar para o Postman.

Em seu interior, vamos fazer três solicitações bem rápidas dentro deste limite de 10 segundos para ver o que acontece. Ok? Para isso, vamos clicar no botão "Send" três vezes seguidas.

Nos três envios, obtivemos as mesmas informações retornadas sobre o produto. Mas agora, se olharmos no terminal do VS Code, a mensagem "Produto sendo buscado do BD!" apareceu apenas uma vez. Então, como funciona isso?

Camila: Podemos notar que as informações retornam corretamente na resposta, mas o que a mensagem "Produto sendo buscado no BD!" realmente significa?

Isto significa que fizemos apenas uma única busca diretamente no banco de dados e as outras vieram do nosso cache, da memória da aplicação. Essa técnica é muito interessante para otimizar o processo de buscas sem sobrecarregar a nossa aplicação em geral. Isso é o que chamamos de caching ou cacheamento, em português.

Evaldo: Então, já passaram 10 segundos. Será que se buscarmos agora, o retorno será diretamente do banco de dados? Porque o tempo do ttl, que foi citado anteriormente, deveria ter expirado.

Camila: Vamos testar.

Evaldo: Vamos clicar novamente em "Send". É certo que já passou 10 segundos nessa conversa. Então, se verificarmso no terminal do VS Code, a mensagem apareceu de novo.

E se enviarmos dentro do intervalo de 10 segundos, será que irá para o cache? Ao fazer mais um "Send" para realizar outra solicitação dentro desses 10 segundos, a mensagem não apareceu. Então, funcionou, não é, Camila?

Camila: Muito legal, está funcionando perfeitamente.

Assim, podemos notar que trabalhamos com o cache nativo do Nest em memória. Mas no dia a dia do mercado de trabalho, temos outras soluções que são mais robustas do que esse recurso nativo do Nest. E uma delas é o Redis que vamos aplicar no próximo vídeo. Vamos lá?

Otimização com cache e Redis - Configurando o Redis

Camila: Olá! No vídeo anterior, conseguimos trabalhar com o recurso de cacheamento nativo do NestJS. No entanto, nós já percebemos que essa opção, além de utilizar bastante memória da nossa aplicação, não é tão comum no mercado quanto a utilização de outras ferramentas.

Aliás, conseguimos trabalhar com outros recursos, como o Redis, que é bastante robusto se comparado ao cache nativo do NestJS. Com microserviços, por exemplo, o Redis se integra muito bem.

Se quisermos evoluir a nossa aplicação, é interessante dispor dessas ferramentas que já estão no mercado há algum tempo. Mas, como implementar o Redis? Quais são os passos que precisamos seguir para trabalhar com ele na nossa aplicação?

Evaldo: A boa notícia é que nós já estamos utilizando o Docker no nosso projeto, o que vai facilitar bastante a implementação do Redis na nossa API.

Integrando o Redis com o NestJS

Então, com o VS Code aberto, acessaremos o arquivo docker-composer.yaml na pasta raiz do projeto. Em seu interior, vemos que já existem dois serviços na seção services: o postgres e o do pgadmin.

Queremos adicionar mais um serviço que vai trazer o servidor do Redis para o nosso computador. Então, abaixo do bloco pgadmin, vamos adicionar o código, tomando cuidado com a indentação (o bloco deve se iniciar no nível de pgadmin, com dois espaços à direita após o início da linha).

O nome do nosso serviço será redis, o nome do nosso container (container_name) no Docker será redis-cache. A imagem (image) que utilizaremos será redis:7.0-alpine, sendo alpine uma versão mais leve, para não consumir tanto recurso do nosso computador.

Abaixo da propriedade image, adicionaremos a ports, pressionaremos "Enter", "Espaço" e adicionaremos um - 6379:6379 para indicar que a porta do servidor será a 6379, onde o hífen marca um item de lista.

Na linha de baixo, no nível de ports, adicionaremos a propriedade volume para dizer onde quero armazenar os dados do Redis, esse banco de dados de chave-valor. Pressionaremos "Enter", "Espaço" e adicionaremos um item de lista por meio de um hífen, que será redis:/data.

  redis:
    container-name: redis-cache
    image: redis:7.0-alpine
    ports:
      - 6379:6379
    volumes:
      - redis:/data

No final do arquivo, no bloco volumes configuraremos o volume da forma padrão, adicionando abaixo do postgre somente um redis e dois pontos.

volumes:
    postgres:
    redis:

Isso não será suficiente para configurar o nosso servidor local, pois precisamos recuperar essa imagem da web. Para isso, vamos acessar o terminal com "Ctrl+J", em no seu interior utilizaremos "Ctrl+C" para finalizar.

O terminal perguntará se queremos finalizar o arquivo em lotes, e digitaremos "S" de "sim" para confirmar essa interrupção do servidor. Por fim, faremos um "Ctrl+L" para limpar os comandos no terminal.

Agora, rodaremos o comando abaixo para subir a nova imagem e ter o servidor do Redis disponível para nós.

docker-compose up -d

Esse processo pode demorar um pouco.

Com a imagem baixada, fecharemos o terminal. Se abrirmos o Docker Desktop, conseguimos ver a seção "redis-cache" funcionando corretamente.

Nesta aula, disponibilizamos uma atividade de instalação do RedisInsight para conseguirmos nos comunicar com o servidor do Redis.

Já estamos com ele aberto no computador, na tela inicial. Em seu interior, há um botão central chamado "Add Redis Database" ("Adicionar Banco de Dados Redis"). Vamos clicar nele.

Com isso, iremos para uma nova tela chamada "My Redis databases", na qual já está selecionada uma opção chamada "Add Database Manually" ("Adicionar Banco de Dados Manualmente").

Abaixo dessa opção tem alguns comandos preenchidos em campos de texto, como o campo "Host", que é "127.0.0.1", e o campo "Port" que possui a porta "6379". Manteremos essas configurações, pois são as mesmas que configuramos no Docker.

No final da tela, no canto direito, clicaremos em "Add Redis Database" ("Adicionar Banco de Dados Redis") para adicionar esta base de dados do Redis.

Com isso, teremos uma tabela com esta base de dados listada para nós.

Database AliasHost:PortConnection TypeModulesLast connection
127.0.0.1:6379127.0.0.1:6379Standalone-less than a minute

Se clicarmos nela dentro da coluna "Database Alias" e esperarmos um pouco, veremos uma nova tela dividida em duas abas, à direita e à esquerda, nas quais não há nada. Então, como podemos testar essa base de dados? Adicionando uma chave?

Camila: É possível identificar um botão chamado "+ Key" no canto superior direito, acima das duas abas, então tentaremos trabalhar com esse recurso. Afinal, o Redis também trabalha com o formato de dados de chave e valor, o que é muito interessante.

Evaldo: Perfeito, então, quando clicarmos nesse botão, na verdade, vamos adicionar uma nova chave que terá um valor correspondente.

Na aba direita, será exibido um campo de texto chamado "Key type" ("Tipo da chave") que está com a opção "Hash". Vamos clicar neste campo e alterar a opçãp para "String" para facilitar o teste. No campo "TTL", que já comentamos anteriormente, vamos colocar "10000" para ver o que acontece, pois o NestJS trabalha com milissegundos. Vamos tentar esta unidade aqui também.

No campo "Key name", que é o nome da chave, vamos adicionar um nome para a chave. Nos dê um nome, Camila.

Camila: "teste".

Evaldo: No campo "Value" (valor), podemos colocar "produto 1", para simular que o produto 1 está sendo armazenado no cache como string.

Agora vamos clicar no botão "Add key" (Adicionar chave), no canto inferior direito da aba atual. Na aba esquerda, podemos ver a chave chamada "teste" e o tipo "STRING" à sua esquerda. Se clicarmos no nome da chave, podemos ver na aba direita o valor, que é "produto 1".

Na aba esquerda, também podemos ver à direita do nome da chave esse tempo de duas horas, que corresponde ao valor 10000 que adicionamos. Ou seja, são 10 mil segundos, então temos que ter cuidado, pois no RedisInsight trabalhamos com segundos, enquanto no NestJS trabalhamos com milissegundos. Isso pode até depender da versão dos pacotes que utilizamos.

Vamos adicionar outra chave para testar melhor o campo "TTL". Vamos selecionar novamente a opção "string" no campo "Key Type", colocar um TTL de 10 segundos dessa vez, e chamar a chave de "teste 2". O valor será "produto 2". Clicaremos em "Add key".

Com isso, a chave "teste 2" apareceu na aba esquerda, acima da chave "teste". Se clicarmos no botão de refresh (atualizar), localizado acima nas chaves, alinhado ao campo de TTL, veremos que o tempo da chave "teste 2" diminui. O TTL se tornou 5 segundos. Clicando novamente o tempo diminuirá até a chave desaparecer.

É exatamente assim que os dados no Redis funcionam: quando o TTL expira, esse espaço da memória cache é liberado.

Para limpar tudo, vamos excluir também a chave "teste", pois não vamos mais precisar dela. Para isso, posicionaremos o cursor em cima dela e veremos uma lixeira à direita, na qual vamos clicar e selecionar o botão "Delete".

Qual é o próximo passo agora, Camila?

Camila: Foi muito interessante, conseguimos entender na prática como funciona esse cacheamento com o Redis. Mas está faltando um detalhe: precisamos integrar o nosso Redis com o NestJS, ou seja, com a nossa aplicação de fato. Faremos isso no próximo vídeo.

Sobre o curso Nest.js: adicionando funcionalidades com Redis, JWT e logging

O curso Nest.js: adicionando funcionalidades com Redis, JWT e logging possui 162 minutos de vídeos, em um total de 58 atividades. Gostou? Conheça nossos outros cursos de Node.JS em Programação, ou leia nossos artigos de Programação.

Matricule-se e comece a estudar com a gente hoje! Conheça outros tópicos abordados durante o curso:

Aprenda Node.JS acessando integralmente esse e outros cursos, comece hoje!

Conheça os Planos para Empresas