Alura > Cursos de Programação > Cursos de Node.JS > Conteúdos de Node.JS > Primeiras aulas do curso Node.js: criando API Rest com autenticação, perfis de usuários e permissões

Node.js: criando API Rest com autenticação, perfis de usuários e permissões

Implementando CRUD de usuário - Apresentação

Oi, pessoal. Conheça Raphael Girão, instrutor da Alura.

Raphael se identifica como uma pessoa morena. Tem olhos castanhos-claros e cabelo preto curto. Está com uma blusa preta e sentado em uma cadeira gamer também preta. Ao fundo, uma estante com livros, quadro e outras decorações.

Se você quer aprender mais sobre segurança com Node.js, está no lugar certo!

Conteúdo

Você vai aprender a:

Conheceremos todos esses assuntos na nossa API de estoque de supermercado, onde você vai poder cadastrar, listar, editar ou deletar produtos.

Além disso, vamos entender como cadastrar pessoas usuárias e também como cadastrar perfis para cada uma dessas pessoas - como gerência, vendedor(a) e estoquista. Assim, cada pessoa vai ter diferentes permissões.

Pré-requisitos

Se você não acompanha a formação de Autenticação, testes e segurança em Node.js, você vai precisar de alguns pré-requisitos:

Na plataforma, temos várias atividades, fórum e Discord da Alura, onde você pode tirar dúvidas e acessar conteúdos exclusivos. Vamos codar?

Implementando CRUD de usuário - Criando rotas e tabelas

Vamos dar início ao nosso curso de segurança com Node.js.

No VSCode, temos nossa API de produtos de supermercado que atualmente já cadastra, lista, edita e deleta produtos. Porém, todas essas ações são realizadas por pessoas usuárias, o que ainda não temos na nossa aplicação.

Criando tabela no banco de dados

Diante disso, vamos dar início ao fazer cadastro de pessoas usuárias. Para isso, vamos abrir o terminal com "Ctrl + `" e fazer a criação da tabela no banco de dados.

No terminal, vamos utilizar um CLI do Squelize para fazer a criação da nossa tabela. Digitamos o comando sequelize model:create e usamos o alias --name para definir o nome da nossa tabela, que será usuarios.

Também vamos utilizar um alias chamado --attributes que define os parâmetros e informações que a pessoa usuária vai ter. Vamos definir nome, email e senha, todos acompanhados de dois pontos e o tipo string. Lembre-se de separá-los por vírgula.

sequelize model:create --name usuarios --attributes nome:string,email:string,senha:string

New model was created at C:\alura\seguranca-nodejs\api\models\usuarios.js

New migration was created at C:\alura\seguranca-nodejs\api\migrations\20230216210729-create-usuarios.js

Agora que temos a nossa tabela criada no banco de dados. Podemos acessar o diretório dos nossos arquivos para verificar alguns novos arquivos.

Ajustando migration

Em "api > migrations", temos um novo arquivo chamado 20230216210729-create-usuarios.js criado para a nossa migration de produtos.

Dentro dessa migration, temos todos os campos criados: id, nome, email, senha, createAt, updatedAt. Eles foram gerados automaticamente a partir do nosso comando com essas informações pelo terminal.

20230216210729-create-usuarios.js

'use strict';
/** @type {import('sequelize-cli').Migration} */
module.exports = {
  async up(queryInterface, Sequelize) {
    await queryInterface.createTable('usuarios', {
      id: {
        allowNull: false,
        autoIncrement: true,
        primaryKey: true,
        type: Sequelize.INTEGER
      },
      nome: {
        type: Sequelize.STRING
      },
      email: {
        type: Sequelize.STRING
      },
      senha: {
        type: Sequelize.STRING
      },
      createdAt: {
        allowNull: false,
        type: Sequelize.DATE
      },
      updatedAt: {
        allowNull: false,
        type: Sequelize.DATE
      }
    });
  },
  async down(queryInterface, Sequelize) {
    await queryInterface.dropTable('usuarios');
  }
};

Vamos fazer um pequeno ajuste no id da nossa tabela. Atualmente, o tipo está como inteiro. Porém, isso cria uma fragilidade no nosso sistema, porque qualquer pessoa que tentar acessar a requisição do sistema pode verificar e saber quantos registros temos na nossa base de dados.

Portanto, vamos fazer uma alteração em type do campo id. Ao invés de utilizar um id do tipo INTEGER, vamos usar um id do tipo UUID que é um hash. Assim, criamos uma segurança e evitamos possíveis ataques.

Ainda em id, vamos remover o autoIncrement, pois não vamos utilizá-lo. Vamos definir um defaultValue para a coluna id do tipo Sequelize.UUID - assim vai ser do mesmo tipo definido para a coluna.

// código omitido…

      id: {
        allowNull: false,
        primaryKey: true,
        type: Sequelize.UUID,
        defaultValue: Sequelize.UUID
      },

// código omitido…

Agora que já fizemos todos os ajustes na nossa migration, garantimos uma segurança maior na nossa tabela.

Ajustando model

Vamos acessar o outro arquivo que foi criado. Acessamos "api > models > usuarios.js".

usuarios.js:

'use strict';
const {
  Model
} = require('sequelize');
module.exports = (sequelize, DataTypes) => {
  class usuarios extends Model {
    /**
     * Helper method for defining associations.
     * This method is not a part of Sequelize lifecycle.
     * The `models/index` file will call this method automatically.
     */
    static associate(models) {
      // define association here
    }
  }
  usuarios.init({
    nome: DataTypes.STRING,
    email: DataTypes.STRING,
    senha: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'usuarios',
  });
  return usuarios;
};

Conseguimos verificar que a model de usuarios foi criada com nome, email e senha. Os três do tipo STRING, como tínhamos definido anteriormente.

Logo abaixo, temos algumas informações como o nome da pessoa tabela em modelName, onde também vamos fazer um pequeno ajuste.

As pessoas usuárias têm uma senha, mas não devemos retornar essas senhas para qualquer pessoa que fizer um select na nossa base para retornar todas as informações. Afinal, a senha é um dado sensível e não é bom ter um acesso - a menos que seja controlado.

Abaixo de modelName, vamos colocar um deafultScope para adicionar limitações na nossa model, aceitando um objeto.

Nesse objeto, vamos digitar attributes para definir em quais atributos queremos adicionar alguma ação. Dentro dele, vamos adicionar chaves e uma função chamada exclude para excluir a coluna, recebendo um array onde definimos a coluna a ser excluída quando fizer uma interação. Nesse caso, a nossa senha entre aspas simples.

  usuarios.init({
    nome: DataTypes.STRING,
    email: DataTypes.STRING,
    senha: DataTypes.STRING
  }, {
    sequelize,
    modelName: 'usuarios',
    defaultScope: {
      attributes: {
        exclude: ['senha']
      }
    }
  });

Agora que fizemos ajustes tanto na migration como na model, garantimos mais segurança no nosso módulo de usuários. Já podemos fechar ambos arquivos.

Criando rota

Em seguida, vamos acessar a pasta de "api > routes" para fazer a criação na nossa rota de usuários. Com o botão direito, escolhemos "Novo Arquivo" e o nomeamos como usuariosRoute.js.

Primeiro, vamos importar a função Router da biblioteca Express para a partir daí definir as nossas rotas e endpoints da aplicação. Para isso, digitamos const seguido de Router entre chaves que vai ser igual à require(), passando express entre aspas simples.

Em seguida, vamos criar uma instância de router para facilitar na criação. Novamente, digitamos const router, mas dessa vez em minúsculo para diferenciá-las. Isso vai ser igual a um Router() com a primeira letra maiúscula, assim criamos uma instância dessa função Router().

usuariosRoute.js:

const { Router } = require('express')

const router = Router()

Com isso, vamos conseguir criar todos os endpoints de usuário. Em uma nova linha, utilizamos router minúsculo e um ponto. O editor já sugere alguns verbos do padrão HTTP do RESTful.

O primeiro endpoint que vamos utilizar será o post() para criar o usuário. Para ficar mais organizado, saltamos uma linha, apertamos "Tab" e colocamos .post(). Nele, vamos definir qual o endpoint que vai ser o path (caminho) que vamos adicionar para acessar essa rota. Nesse caso, /usuarios entre aspas simples.

Em uma nova linha, podemos adicionar um .get() que é um verbo para buscar. Também vamos passar o mesmo endpoint de /usuarios.

Com isso, os dois endpoints têm o mesmo nome. Porém, como têm verbos diferentes, as interações também vão ser diferentes.

// códido omitido…

router
    .post('/usuarios')
    .get('/usuarios')

Abaixo de .get(), podemos adicionar outro verbo .get(), porém, com uma diferença do caminho de cima.

O primeiro busca /usuarios, retornando todas as pessoas usuárias da aplicação. Porém, em alguns casos podemos precisar de apenas de uma pessoa usuária.

Por isso, nesse segundo .get(), vamos fazer um endpoint de retorno de uma pessoa usuária a partir de um ID. Isto é, /usuarios/id/:id entre aspas simples. Esse :id significa que é um parâmetro do nosso endpoint. Quando consumimos esse endpoint, vamos passar um ID e recebê-lo no back-end.

Em seguida, vamos para a função de editar do tipo .put(). Da mesma forma que temos a função de buscar apenas uma pessoa usuária pelo ID, o put() também vai precisar de um ID. Pois, vamos editar apenas uma pessoa usuária por vez. Por isso, colocamos /usuarios/id/:id entre aspas simples.

Por último, temos a função de deletar a pessoa usuária. Parecido a função de editar, a de deletar também vai precisar de um ID para saber qual pessoa deletar. Digitamos o verbo .delete() com /usuarios/id/:id entre aspas simples.

Agora que já temos todas as rotas e endpoints criados, vamos precisar exportá-las para importá-las no nosso arquivo index.js do projeto.

Em uma nova linha, vamos exportar a variável router, utilizando module.exports igual à router.

// códido omitido…

router
    .post('/usuarios')
    .get('/usuarios')
    .get('/usuarios/id/:id')
    .put('/usuarios/id/:id')
    .delete('/usuarios/id/:id')

module.exports = router

Após exportar nossas rotas, vamos acessar o arquivo index.js dentro de "src > routes". Verificamos que já importamos os produtos, pois já temos as rotas de produtos criadas.

Da mesma forma, abaixo de const produto, vamos criar uma variável const para importar o usuario que vai ser igual à require(), pegando usuariosRoute do mesmo diretório.

Agora, podemos adicionar essa variável usuario dentro da variável app para poder utilizá-la. Então, após produto, vamos digitar usuario.

index.js:

const bodyParser = require('body-parser')

const produto = require('./produtoRoute')
const usuario = require('./usuariosRoute')

module.exports = app => {
  app.use(
    bodyParser.json(),
    produto,
    usuario
  )
}

Com isso, sabemos que nossas rotas de usuário funcionam. Agora, abrimos o terminal e digitamos o comando que definimos dentro do pack de JSON para rodar o projeto:

npm run start

servidor está rodando na porta 3000

Pronto. Nosso projeto está rodando sem mostrar nenhum erro.

Nesse vídeo, fizemos a criação da nossa tabela no banco de dados, criando a nossa migration e model. Também definimos um arquivo com todas as rotas de usario e a importamos no index.js.

No próximo vídeo, vamos dar início a parte da criação da primeira pessoa usuária.

Implementando CRUD de usuário - Implementando cadastro de usuários

Em vídeos anteriores, fizemos a criação de todas as rotas de usuário e a tabela no banco de dados. Com isso, podemos dar início a criação da primeira pessoa usuária.

Criando controller e service de usuário

Para isso, vamos fechar todos os arquivos que não utilizamos no VSCode. Em "api > controllers", vamos criar um novo arquivo usuarioController.js.

Nele, vamos fazer a criação de uma classe da controller de usuário chamada UsuarioController.

Já vamos exportar nossa controller para poder acessá-la em outros arquivos. Fora da classe, vamos dar um module.exports igual à classe UsuarioController.

usuarioController.js:

class UsuarioController {

}

module.exports = UsuarioController

Agora que temos a controller de usuário criada, podemos ir ao diretório "src > services" e fazer a criação da service de usuário. Vamos criar um novo arquivo chamado usuarioService.js.

Nele, também vamos criar uma classe chamada UsuarioService para referenciar o service de usuário.

Da mesma forma, vamos fazer exportar essa classe para poder acessá-la em outros locais, como o controller. Para isso, escrevemos module.exports igual à UsuarioService.

usuarioService.js:

class UsuarioService {

}

module.exports = UsuarioService

Com isso, podemos voltar ao arquivo usuarioController.js e importar o service de usuário. Na primeira linha, vamos criar uma const chamada UsuarioService igual à require(), onde colocamos dois pontos para sair da pasta atual e ir para a pasta services, onde temos o arquivo usuarioService.

Agora podemos criar uma instância da service de usuário para poder acessar as suas funções internas. Em uma nova linha, criamos uma const chamada usuarioService com o "U" minúsculo para fazer a distinção da classe. Essa variável vai ser igual à new UsuarioService().

usuarioController.js:

const UsuarioService = require('../services/usuarioService')

const usuarioService = new UsuarioService()

// código omitido…

Função assíncrona para cadastrar usuário

Vamos voltar ao arquivo usuarioService.js para criar nossa primeira função que vai cadastrar o usuário.

Dentro da classe UsuarioService, vamos digitar async para informar que o tipo da função é assíncrona para ser aguardada enquanto faz o cadastro.

Vamos dar o nome da função de cadastrar(), recebendo um objeto chamado dto. Dentro do DTO, vamos ter todas as informações do usuário, como nome, e-mail e senha. Em seguida, abrimos e fechamos chaves.

usuarioService.js:

class UsuarioService {
    async cadastrar(dto) {

    }
}

// código omitido…

Vamos voltar novamente no arquivo usuarioController.js para verificar se o import e a criação da classe estão funcionando.

Na classe UsuarioController, vamos fazer a criação de uma função para cadastrar o usuário. Assim como na service, a função vai ser do tipo async e ter o nome cadastrar().

Essa função de cadastro de usuários vai receber duas variáveis de entrada, uma vai ser a requisição que vai ter todos os dados do usuário e a outra vai ser a response que vamos retornar para quem solicitar a controller. Por isso, passamos req e res.

Entre as chaves da função cadastrar, vamos fazer uma criação de variáveis via desestruturação para pegar as informações do usuário a partir da requisição do body.

Para isso, vamos dar um const e colocar entre chaves: nome, email e senha. Essas informações virão da req.body. Com isso, temos todas as informações da requisição do usuário para poder cadastrá-lo.

Por isso, podemos acessar a função do service para cadastrar o usuário. Criamos a variável const chamada usuario igual à await para esperar ser feito o cadastro e chamar a função usuarioService.cadastrar().

Perceba que o cadastrar() recebe um dto do tipo any. Porém, vamos adicionar todas as informações do usuário. Por isso, vamos passar o objeto com nome, email e senha do usuário.

Agora, podemos retornar o usuario salvo. Vamos usar a função res seguida de .status(). Dentro do status, vamos passar um número que representa a ação que fizemos, ou seja, a requisição. Como estamos criando um usuário, vamos usar o status code 201 que significa created.

Após o status(201), vamos escrever .send() para retornar as informações que queremos. No nosso caso, é o usuario.

usuarioController.js:

class UsuarioController {
    async cadastrar(req, res) {
        const { nome, email, senha } = req.body

        const usuario = await usuarioService.cadastrar({ nome, email, senha})

        res.status(201).send(usuario)
    }
}

// código omitido…

Verificando usuário já cadastrado

Agora que recebemos os dados do usuário, enviamos para o back-end e retornamos da requisição, podemos acessar a service e receber esses dados.

Dentro de cadastrar() em usuarioService.js, vamos receber os dados do usuário. Porém, como vamos fazer um cadastro de um novo usuário, precisamos verificar se esse usuário já existe na base de dados.

Para fazer essa busca, vamos utilizar o e-mail do usuário, pois é único para cada usuário. Com isso, vamos evitar a duplicidade de cadastro. O nome pode se repetir, mas o e-mail pode ser a nossa chave primária.

Mas, ainda não temos acesso a nossa base de dados para poder acessar a tabela de usuários, pois não a importamos na service. Por isso, no começo do arquivo, vamos saltar uma linha e criamos const database igual à require(), saindo dessa pasta e acessando a pasta "models". Isto é, ../models.

Com isso, já conseguimos acessar a base de dados. Pois, dentro de "models", temos um arquivo chamado index.js que faz referência às models de produtos e usuários.

Agora, vamos continuar pela busca pelo usuário no service. Na função assíncrona cadastrar(), criamos uma variável const chamada usuario, seguido do sinal de igual. Depois, vamos adicionar um await e acessar a base de dados e a tabela de usuários. Isto é, database.usuarios também vamos usar a função .findOne() que recebe um objeto para passar filtros.

Entre as chaves, vamos utilizar o where para definir qual vai ser o parâmetro de busca. Como dito anteriormente, vamos utilizar o email, pegando da variável de entrada dto.email.

A partir disso, já podemos ter o retorno de um usuário ou não. Por isso, vamos adicionar um if() para verificar se esse usuário já é cadastrado. Caso já seja, vamos retornar um aviso para o usuário que não pode haver um cadastro duplicado.

Ainda em cadastrar(), vamos adicionar um if(), passando o usuario como parâmetro. Um if verdadeiro significa que o usuário está cadastrado. Por isso, utilizamos a função throw new Error() para passar uma mensagem de erro ao usuário e parar a requisição. A mensagem que passamos é: "Usuario ja cadastrado".

usuarioService.js:

const database = require('../models')

class UsuarioService {
    async cadastrar(dto) {
        const usuario = await database.usuarios.findOne({
            where: {
                email: dto.email
            }
        })

        if (usuario) {
            throw new Error('Usuario ja cadastrado')
        }
}

// código omitido…

Com isso, conseguimos validar o cadastro do usuário. Porém, como damos um throw new Error() e paramos a requisição, vamos precisar tratar o retorno na controller para evitar erros.

Em usuarioController.js, vamos utilizar um padrão chamado try-catch para tentar fazer algo ou retornar uma exceção.

Em cadastrar(), abaixo das variáveis nome, email, senha, vamos adicionar try e escolher a sugestão de autocompletar "try-catch statement".

Vamos recortar as linhas de const usuario e res.status(201) com as informações da criação e retorno do usuário e colá-las dentro do try.

Em catch, vamos adicionar um retorno para o usuário passando a mensagem de erro. Para isso, digitamos res.status() com o status code 400 que sinaliza um problema na requisição. Também vamos acrescentar .send(), passando o error.message que passamos na service. Isto é, passamos um objeto chamado message passando error.message.

usuarioController.js:

class UsuarioController {
    async cadastrar(req, res) {
        const { nome, email, senha } = req.body

        try {
            const usuario = await usuarioService.cadastrar({ nome, email, senha})

            res.status(201).send(usuario)
        } catch (error) {
            res.status(400).send({ message: error.message})
        }

    }
}

// código omitido…

Dessa maneira, conseguimos validar que vamos o usuário vai receber uma mensagem de erro caso já esteja cadastrado. Além de já saber se está cadastrado pela service.

Com isso, finalizamos a primeira parte de buscar o usuário. No próximo vídeo, vamos terminar a parte de cadastro de usuário ao fazer a criptografia e salvar no banco de dados.

Sobre o curso Node.js: criando API Rest com autenticação, perfis de usuários e permissões

O curso Node.js: criando API Rest com autenticação, perfis de usuários e permissões possui 193 minutos de vídeos, em um total de 48 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!

Plus

De
R$ 1.800
12X
R$109
à vista R$1.308
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

Matricule-se

Pro

De
R$ 2.400
12X
R$149
à vista R$1.788
  • Acesso a TODOS os cursos da Alura

    Mais de 1500 cursos completamente atualizados, com novos lançamentos todas as semanas, emProgramação, Front-end, UX & Design, Data Science, Mobile, DevOps e Inovação & Gestão.

  • Alura Challenges

    Desafios temáticos para você turbinar seu portfólio. Você aprende na prática, com exercícios e projetos que simulam o dia a dia profissional.

  • Alura Cases

    Webséries exclusivas com discussões avançadas sobre arquitetura de sistemas com profissionais de grandes corporações e startups.

  • Certificado

    Emitimos certificados para atestar que você finalizou nossos cursos e formações.

  • Luri, a inteligência artificial da Alura

    Luri é nossa inteligência artificial que tira dúvidas, dá exemplos práticos e ajuda a mergulhar ainda mais durante as aulas. Você pode conversar com Luri até 100 mensagens por semana.

  • Alura Língua (incluindo curso Inglês para Devs)

    Estude a língua inglesa com um curso 100% focado em tecnologia e expanda seus horizontes profissionais.

Matricule-se
Conheça os Planos para Empresas

Acesso completo
durante 1 ano

Estude 24h/dia
onde e quando quiser

Novos cursos
todas as semanas