OmniStack11: backend da aplicação com node e express. [Dia 02]

OmniStack11: backend da aplicação com node e express. [Dia 02]

Enfim vamos iniciar a o desenvolvimento do backend da aplicação com node e express.

A proposta é desenvolver a aplicação Be the Hero que visa conectar pessoas com capital investidor a ONGs nessa semana Omnistack e vamos fazer com Node, ReactJS e React Native.

Abaixo os links para todos os dias da Semana Omnistack 11

Dia 02 – Criando a base da aplicação

Ok! Vamos começar o segundo dia da semana OmniStack 11. Nessa fase vamos conhecer algumas funcionalidades das ferramentas e frameworks que utilizaremos e com finalmente começar a desenvolver o backend da aplicação.

Node e express

Inicialmente são passados conceitos e definições sobre Node e Express.

Rotas e Recursos

Vamos usar como exemplo para definir o que são as Rotas e Recursos no Express, usaremos o método app.get( ... criada no arquivo index.js na pasta /omnistack11/backend

app.get('/', (req,res) => {
  return res.json({
    evento: 'Semana Omnistack 11',
    aluno: 'Abner Oliveira'
  });
});

Lembrando que para acessar o conteúdo da rota era preciso ir no navegador e acesar localhost:3333/. Definimos ROTA todo esse caminho e RECURSO tudo que vem depois da /.

Se alterarmos o parâmetro '/' para '/users', estamos alterando o recurso da rota. Geralmente o recurso está relacionado a uma entidade do banco de dados.

A definição de rotas aceita a seguinte estrutura:

app.METHOD(PATH, HANDLER)

Onde:

  • app é uma instância do express.
  • METHOD método de solicitação HTTP.
  • PATH o caminho no servidor ou recurso
  • HANDLER é a função executada quando a rota é correspondida.
Métodos HTTP

Existem 9 métodos HTTP, porém você só vai utilizar 4 nesse projeto e na maioria dos outros também. São basicamente os métodos que você pode utilizar para criar um CRUD são eles:

  • GET: buscar uma informação no backend
  • POST: inserir uma informação na API
  • PUT: atualizar uma informação na API
  • DELETE: deletar uma informação no backend

Se você alterar a método get para post, lá no arquivo index.js o navegador vai retornar a seguinte resposta Cannot get /users

Por padrão onavegador faz requisições apenas GET, sendo assim vamos precisar de uma ferramenta para acessar as com rotas com outros métodos HTTP.

Utilizando Insomnia

O Insomnia é um cliente REST open source, através dele é possível fazer todos os tipo de testes de requisições, o que auxilia bastante no desenvolvimento da API.

Para começar a utilizar você precisa entender sobre os tipos de parâmetros: Query Params, Route Params e Request Body

Os Query Params são parâmetros nomeados enviados na rota após o "?", geralmente utilizados para filtros, paginações. Pode ser acessado pelo request.query

http://localhost:3333/users/?page=2&name=Abner

Os Route Params são parâmetros utilizados para identificar recursos. Pode ser acessado pelo request.params.

http://localhost:3333/users/1234

O Request Body é usado quando você quer criar ou atualizar informações. São os dados que você deve enviar no corpo da requisição. Para acessar utilize request.body

Configurando o Nodemon

O nodemon é um aplicativo que detecta toda vez que um arquivo é alterado no seu projeto e automaticamente reinicia o servidor Node, o que agiliza bastante o desenvolvimento.

Com o nodemon é possível definir quais extensões e pastas serão monitoradas.

#adicionar o nodemon ao projeto como dependência de desevolvimento
yarn add nodemon -D

Agora é necessário definir o script no package.json para facilitar a chamada do nodemon. Sendo assim deve ser inserido o código abaixo no seu package.json

"scripts": {
  "start": "nodemon index.js"
},

Com essa alteração para iniciar o servidor basta utitlizar yarn start uma única vez e o nodemon reiniciará o servidor toda vez que for detectada alguma alteração nos arquivos .js do projeto.

Diferenças entre banco de dados

Existem basicamente 2 tipos de bancos de dados: SQL e NoSQL.

Costumamos dizer que bancos SQL seguem uma modelagem relacional, pois estes se baseiam no fato de que todos seus dados sejam guardados em tabelas. Temos com exemplos o: MySQL, SQLite, Oracle e etc.

NoSQL (Not Only SQL) é o termo utilizado para banco de dados não relacionais de alto desempenho, onde geralmente não é utilizado o SQL como linguagem de consulta.

O NoSQL foi criado para ter uma performance melhor e uma escalabilidade mais horizontal para suprir necessidades onde os bancos relacionais não são eficazes. Podem ser baseados em: Documentos; Colunas; Grafos e Chave-Valor.

Resumindo o conceito de modelo relacional (SQL) se baseia no fato de que todos os dados sejam guardados em tabelas. Ao modelo não-relacional (NoSQL) não se aplica o conceito de schema: uma chave de valor é que é utilizada para recuperar valores, conjunto de colunas ou documentos.

No projeto do backend da semana Omnistack 11 você deve utilizar o SQLite. Com isso não vai preciso instalar nada na máquina, pois o banco vai ficar salvo em arquivo dentro da própria aplicação.

Configurando o banco de dados do backend da aplicação com node e express.

Para a integração do projeto com o banco de dados você vai utilizar o KNEX.JS.

Primeiramente instale o knex.js

yarn add knex

E depois instale o driver que é o sqlite3

yarn add sqlite3

Agora para criar o arquivo de configurações do KNEX

npx knex init

Ok! O arquivo knexfile.js foi criado e ele contem as configurações da conexão com o banco de dados

Agora vamos organizar a estrutura de pastas do projeto. Crie na raiz uma pasta /src coloque o arquivo index.js lá dentro e altere o script start no package.json para:

"scripts": {
    "start": "nodemon src/index.js"
  },

Agora vamos criar um arquivo routes.js que conterá todas as rotas da nossa API. Recorte o código da rota no arquivo index.js e cole dentro do arquivo de rotas e faça as importações e a exportação. O arquivo de rotas deve ficar assim:

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


routes.get('/', (req,res) => {
  return res.json({
    evento: 'Semana Omnistack 11',
    aluno: 'Abner Oliveira'
  });
});

module.exports = routes;

No final o arquivo index.js deve ficar dessa forma

const express = require('express');
const routes = require('./routes');

const app = express();

app.use(express.json());
app.use(routes);

app.listen(3333);

A linha app.use(express.json()) define o JSON para as requisições e respostas. Enquanto app.use(routes), que deve ficar obrigatoriamente depois após a do json, define o uso do arquivo de rotas.

Para testar vá no terminal e rode o script yarn start

Continuando a configuração banco de dados.

Crie a pasta /database dentro de /src. Agora crie a pasta /migrations dentro de /database depois vá no arquivo knexfiles.js no pasta raiz do projeto e altere a configuração da conexão de desenvolvimento para:

development: {
    client: 'sqlite3',
    connection: {
      filename: './src/database/db.sqlite'
    },
    migrations: {
      directory: './src/database/migrations'
    },
    useNullAsDefault: true
  },

Mas o que essas migrations? A migrations são nada mais do que o código que define a estrutura do banco. Elas são feitas em um padrão em que pode ser usado para qualquer banco de dados. Na documentação do KNEX tem toda as definições de como usar esse recurso.

Análise do backend da aplicação com node e express.

Durante a análise do projeto foram definidas as entidades e funcionalidades do sistema

Entidade

  • ONGs
  • Casos

Funcionalidades

  • Login da ONG
  • Logout da ONG
  • Cadastro da ONG
  • Cadastrar novos casos
  • Listas casos de uma ONG
  • Listar todos os casos
  • Entrar em contato com a ONG
Criando as migrations

Você deve criar as duas entidades

npx knex migrate:make create_ongs

npx knex migrate:make create_incidents

A migration de ONGs deve ficar assim

exports.up = function(knex) {
  return knex.schema.createTable('ongs', function (table) {
      table.string('id').primary();
      table.string('name').notNullable();
      table.string('email').notNullable();
      table.string('whatsapp').notNullable();
      table.string('city').notNullable();
      table.string('uf', 2).notNullable();
  })
};

exports.down = function(knex) {
   return knex.schema.dropTable('ongs');
};

A de Casos assim:

exports.up = function(knex) {
  return knex.schema.createTable('incidents', function (table) {
      table.increments();      
      table.string('title').notNullable();
      table.string('description').notNullable();
      table.string('value').notNullable();      
      table.string('ong_id').notNullable();      
      table.foreign('ong_id').references('id').inTable('ongs');
  })
};

exports.down = function(knex) {
  return knex.schema.dropTable('incidents');
};

Agora rode o comando npx knex migrate:latest no terminal para criar as tabelas no banco a partir das migrations. Isso também de criar o arquivo db.sqlite dentro da pasta /src/database.

Construção do backend da aplicação com node e express.

Quando você for realizar as requisições o backend precisará da conexão com o banco de dados então crie o arquivo connections.js dentro da pasta /src/database

const knex = require('knex');
const configuration = require('../../knexfile');

const connection = knex(configuration.development);

module.exports = connection;

Poderíamos colocar os métodos que compões as rotas diretamente no arquivo de rotas, mas para melhorar a estrutura de pastas de projeto crie a pasta /src/controllers . Lá crie os arquivos OngController.js, IncidentController.js, ProfileController.js e SessionController.js.

Agora basicamente iremos mexer nos arquivos de controllers e no arquivo de rotas. Nos controllers serão definidas todas as funcionalidades listadas acima.

Controller de ONGs

Então vamos lá iniciar pelo controller de ONGs. Lá nos temos que controlar duas funcionalidades: Listar todas as ONGs, criaremos o método assíncrono index e Criar uma ONG, que será feito a partir do método post.

O index deve ficar assim

 async index(req, res) {
        const ongs = await connection('ongs').select('*');
        return res.json(ongs);
    }

O método index só usa a conexão para selecionar todos dados da tabela, salva em uma variável ongs e retorna em formato json

O post necessita da biblioteca crypto pois ao criar uma ONG é criada uma chave de acesso para logar com essa ONG. Portanto o biblioteca crypto que é nativa do node deve ser importada nesse arquivo.

O post deve ficar assim

async create(req, res) {
        const {name, email, whatsapp, city, uf} = req.body;

    const id = crypto.randomBytes(4).toString('HEX');
    console.log(id);
    
    await connection('ongs').insert({
        id,
        name,
        email,
        whatsapp, 
        city,
        uf,
    })

    return res.json({id}); 
    }

O método post usa o desmebramento do javascript para pegar todos os campos do corpo da requisação (req.body), cria um hash para o id da tabela e usa a conexão para salvar tudo na tabela ongs do db.sqlite.

No final o arquivo fica com OngController.js fica dessa forma

const crypto = require('crypto');
const connection = require('../database/connection');

module.exports = {

    async index(req, res) {
        const ongs = await connection('ongs').select('*');
        return res.json(ongs);
    },

    async create(req, res) {
        const {name, email, whatsapp, city, uf} = req.body;

    const id = crypto.randomBytes(4).toString('HEX');
    console.log(id);
    
    await connection('ongs').insert({
        id,
        name,
        email,
        whatsapp, 
        city,
        uf,
    })

    return res.json({id}); 
    }
}

Controller de ONGs OK! Agora você deve atualizar o arquivo de rotas importando o OngController.js, bem como definir as rotas no routes.js a partir das funções index e create. O arquivo routes.js deve ficar com o código abaixo:

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

const OngController = require('./controllers/OngController');


routes.get('/ongs', OngController.index);
routes.post('/ongs', OngController.create);

module.exports = routes;

Agora só falta testar no Insomnia. Crie uma pasta Ongs depois crie duas requisições uma get e uma post, ambas com a URL http://localhost:3333/ongs.

Vamos lá! Crie a primeira ONG passando o JSON abaixo como body da requisição.

{
	"name": "Athus",
	"email": "athus@gmail.com",
	"whatsapp": "85987654321",
	"city": "Fortaleza",
	"uf": "CE"	
}

A resposta deve ser algo como isso:

Rota post para ONG - backend da aplicação com node e express.

Para listar todas as ONGs no Insomnia acesse a rota GET utilizando o mesmo recurso.

Controller de Casos

No controller de Casos, o IncidentController.js, vamos ter 3 métodos o index, create e delete.

O index como já sabe é usado para listar todos os casos. Nele tem alguns recursos a mais, pois utiliza o Request Params e o Header Params, para paginação e contagem de registros. Verifique o método

async index(req, res) {
         const { page = 1 } = req.query;

         const [count] = await connection('incidents').count();

         const incidents = await connection('incidents')
         .join('ongs', 'ongs.id',  '=', 'incidents.ong_id')
         .limit(5).offset((page - 1)*5)
         .select([
             'incidents.*', 
             'ongs.name',
              'ongs.email',
               'ongs.whatsapp',
                'ongs.city',
                 'ongs.uf']);
        
         res.header('X-Total-Count', count['count(*)']);

         return res.json(incidents);
    },

Você pode verificar que ele faz um join da tabela incidents com a tabela de ongs para listar os casos.

Agora vamos fazer os insert de casos!!!

Só quem pode cadastrar casos é a ONG “logada”, sendo assim temos que de alguma forma dizer ao método create qual o id da ONG logada.

async create(req, res) {
        
        const {title, description, value} = req.body;
        const ong_id = req.headers.authorization;

        const [id] = await connection('incidents').insert({
            title,
            description,
            value,
            ong_id,
        });
        return res.json({id});
    },

Isso é feito na linha const ong_id = req.headers.authorization que pega o id salvo no cabeçalho da requisição e coloca seu valor com método connection('incidents').insert( ... e registra o Caso em nome da ONG logada.

Logo mais veremos o controller que faz a autenticação. Calma!!

O delete usa o mesmo recurso para confirmar que somente a ONG que cadastrou o caso pode deletá-lo

async delete(req, res) {
        const { id } = req.params;
        const ong_id = req.headers.authorization;

        const incident = await connection('incidents').where('id', id).select('ong_id').first();

        console.log(incident);

        if(incident.ong_id !== ong_id) {
            return res.status(401).json({error: 'Operation not permited'});
        }

        await connection('incidents').where('id', id).delete();

        return res.status(204).send();
    }

Importe o arquivo de IncidentsController.js no arquivos de rotas tal como foi feito para o controller de ONGs.

Vamos testar no Insomnia!!!

Primeiro crie a pasta Casos no Insomnia e dentro crie as 3 requisições:

Mas primeiro insira no HEADER da requisição o campo Authorization e como valor o id de uma ONG já cadastrar com ilustra a imagem. O Authorization vai agir como a ONG logada. Faça isso para a rota post e delete.

Header de autenticado - backend da aplicação com node e express.
backend da aplicação com node e express.

Agora você já pode criar, deletar e listar casos. A imagem a seguir é a rota de criação já com o Header preenchido.

backend da aplicação com node e express.
Rota de Create

Abaixo a listagem de Casos

backend da aplicação com node e express.
backend da aplicação com node e express.

E por fim o delete para excluir a rota http://localhost:3333/incidents/1 para deletar o caso de id = 1.

Controller de Perfil do backend da aplicação com node e express.

Esse controller de perfil terá somente o método index, que listará todos os casos da ONG logada.

Já saber então precisaremos novamente do authorization no Header. O ProfileController.js deve ficar assim.

 const connection = require('../database/connection');
 
 module.exports = {
     async index(req, res) {

        const ong_id = req.headers.authorization;
        
        const incidents = await connection('incidents').where('ong_id', ong_id).select('*');
        return res.json(incidents);
     }
 }

Ok! Para testar no Insomnia crie a requisição GET e insira o Header necessário. Ah e não esqueça de atualizar o arquivo de rotas no mesmo padrão dos outros controllers.

backend da aplicação com node e express.
Controller de Sessão

O controller de sessão vai autenticar apenas utilizando esse hash de cada ONG. Esse mesmo que estamos colocando no Header.

Crie o SessionController.js, atualize o routes.js e faça a autenticação pelo Insomnia

const connection = require('../database/connection');

module.exports = {
    async create(req, res) {
        const { id } = req.body;
        const ong = await connection('ongs').where('id', id).select('name').first();

        if(!ong) {
            return res.status(400).json({error: 'No ONG exist'});
        }
        
        return res.json(ong);
    }
}

Então você deve passar o hash da ONG que deseja logar e a requisição deve retornar o nome dela.

Adicionando o CORS

No arquivo index.js você de adicionar o CORS, que é uma módulo de segurança que define quais domínios podem acessar o backend.

Instale o CORS

yarn add cors

Importe o CORS para o index.js

const cors = require('cors');

E depois da definição da constante app insira o código abaixo

app.use(cors());

Ufa! Hoje criamos todos backend da aplicação com node e express. Por hoje é só amanhã começaremos a criar o frontend com ReactJS.

Até logo!!

Tags: | |

Sobre o Autor

Abner Oliveira
Abner Oliveira

Bombeiro Militar do CBMCE, Professor do CMCB. Mestre em Ciências da Computação pelo IFCE. Amante de desenvolvimento Web e Mobile. Iniciando no Marketing Digital.

0 Comentários

Deixe um comentário

O seu endereço de e-mail não será publicado. Campos obrigatórios são marcados com *

Este site utiliza Cookies e Tecnologias semelhantes para melhorar a sua experiência. Ao utilizar nosso site você concorda que está de acordo com a nossa Política de Privacidade.