Skip to content

Instantly share code, notes, and snippets.

@nicolas-oliveira
Last active April 19, 2020 21:14
Show Gist options
  • Save nicolas-oliveira/82b3ea113f63bea16980afc7cc950d7e to your computer and use it in GitHub Desktop.
Save nicolas-oliveira/82b3ea113f63bea16980afc7cc950d7e to your computer and use it in GitHub Desktop.
Veja os detalhes da Semana Oministack 11.0 passo a passo, todos os detalhes da aplicação e como aplicar e outras situações de aplicações Node.js, React, React Native!

Base da aplicação

Tags: Concluído! Documentação em andamento...

Dev stories

  • Node.js & express
    • Rotas e recursos
    • Métodos HTTP
    • Tipos de parâmetros
  • Configurando o Nodemon
  • Utilizando o Insomnia
  • Diferenças entre bancos de Dados
  • Configurando banco de dados
  • Pensando em entidades e funcionalidades
  • Construção do Back-end
  • Adicionando o módulo CORS
  • Enviando o Back-end ao Github

1) Node.js & express

a) Métodos HTTP

O protocolo HTTP define um conjunto de métodos de requisição responsáveis por indicar a ação a ser executada para um dado recurso. Embora esses métodos possam ser descritos como substantivos, eles também são comumente referenciados como HTTP Verbs (Verbos HTTP). ******Cada um deles implementa uma semântica diferente, mas alguns recursos são compartilhados por um grupo deles, como por exemplo, qualquer método de requisição pode ser do tipo safeidempotent ou cacheable.

O método GET solicita a representação de um recurso específico. Requisições utilizando o método GET devem retornar apenas dados.

O método HEAD solicita uma resposta de forma idêntica ao método GET, porém sem conter o corpo da resposta.

O método POST é utilizado para submeter uma entidade a um recurso específico, frequentemente causando uma mudança no estado do recurso ou efeitos colaterais no servidor.

O método PUT substitui todas as atuais representações do recurso de destino pela carga de dados da requisição.

O método DELETE remove um recurso específico.

O método CONNECT estabelece um túnel para o servidor identificado pelo recurso de destino.

O método OPTIONS é usado para descrever as opções de comunicação com o recurso de destino.

O método TRACE executa um teste de chamada loop-back junto com o caminho para o recurso de destino.

O método PATCH é utilizado para aplicar modificações parciais em um recurso. Para a nossa stack obtemos o seguinte formato das requisições:

const app = express();

app.metodo... // Os métodos HTTP são chamados com o objeto
             // da variável que executa a aplicação express

app.get...    // Busca uma informação no backend
app.post...   // Criar uma informação no backend
app.put...    // Alterar uma informação no backend
app.delete... // Deletar uma informação no backend

b) Rotas e recursos

app.get('/',...      // Cria-se uma rota, que é o 
// conjunto completo

app.get('/users',... // Este é o recurso -> após a barra, 
 // é o caminho da rota associado 
 // a algo que será buscado no banco
 // ou à uma entidade

c) Tipos de parâmetros

Request e Response:

app.post('/users', (request, response) => {
// O 'request' guarda todos os dados feitos 
// através da requisição

// O 'response' é responsável por trazer a 
// resposta para o usuário
  • Query Params

Parâmetros Nomeados usado após '?' para Filtros e paginação

app.get('/users', (request, response) => {
    const params = request.query; // Importante -> .query

    console.log(params);

    return response.json({
        evento: 'Semana Oministack 11',
        aluno: 'Nicolas Oliveira'
    });
});

Após definir a requisição acima devemos configurar o insomnia para receber os mesmo parâmetros como:

http://localhost:3333/users?nome=Nicolas&idade=21

O terminal deverá retornar:

{ name: 'Nicolas', idade: '21' }
  • Route Params

Parâmetros usados geralmente para identificar recursos como pesquisar apenas 1 usuário. Não são nomeados e exige a quantidade especificada para não ser identificado como uma nova paginação.

app.get('/users/:id', (request, response) => {
    const params = request.params; // Importante .params

    console.log(params);

    return response.json({
        evento: 'Semana Oministack 11',
        aluno: 'Nicolas Oliveira'
    });
});

Configurando como GET a solicitação no insomnia:

http://localhost:3333/users/1   

O terminal deverá retornar:

{ id: '1' }
  • Request Body:

É o corpo da requisição, utilizado para criar ou alterar recursos. Criar exige que sejam todas os dados. Alterar não precisa ser todos.

Primeiro é necessário configurar o corpo da requisição no insomnia:

POST // Defina como post
JSON // Defina como arquivo JSON
//____Corpo da requisição:_____//
{
    "name": "Nicolas Oliveira",
    "idade": 21
}

Após isso defina a rota desta forma:

app.post('/users', (request, response) => { // Mudar para post
    const body = request.body; // Importante -> .body

    console.log(body);

    return response.json({
        evento: 'Semana Oministack 11',
        aluno: 'Nicolas Oliveira'
    });
});

O terminal deverá retornar desta forma:

undefined

Isto ocorreu pois ainda não foi definido o tipo do arquivo JSON para as requisições, o express portanto não interpreta o arquivo como objeto. Por isso acrescente ao código:

app.use(express.json()); // Deve vir antes das rotas
     // Converte JSON em um objeto do js

app.post('/users', (request, response) => {
(...)

O terminal deverá retornar desta forma:

{ name: 'Nicolas Oliveira', idade: 21 } 

2) Utilizando o Insomnia

3) Configurando o Nodemon

O nodemon automatiza a verificação das alterações no código, compilando e fazendo as alterações necessárias.

Este pacote deve ser instalado como 'devDependencies', ou seja, como dependência de desenvolvimento.

$ npm install nodemon -D

E altere o script no arquivo package.json:

"scripts": {
    "start": "nodemon index.js",
    // O index.js é o aquivo principal onde a aplicação é inicializada
  },

4) Diferenças entre bancos de Dados

A diferença fundamental entre SQL e NoSQL é que o banco de dados SQL é restritivo exigindo esquemas pré-definidos. Exige também uma preparação inicial significante, tendo como foco uma modelagem de banco de dados concisa, caso contrário os dados não serão atendidos corretamente.

Por outro lado, os banco de dados NoSQL são usados em esquemas dinâmicos com a finalidade de atender melhor dados não estruturados. Podendo ser organizado de várias formas como orientado à colunas ou orientados à documentos entre outros...

Dessa forma o NoSQL permite que:

· Você crie documentos sem ter que definir sua estrutura primeiro;

· Cada documento tenha sua própria estrutura única;

· A sintaxe varie de base de dados para base de dados;

· Você adicione campos sempre que precisar.

Exemplos de Bancos de Dados:

SQL: MySQL, SQLite, PostregreSQL, Oracle, Microsoft SQL Server

NoSQL: MongoDB, CouchDB, etc...

Como maior parte dos bancos usados no mercado são SQL, usaremos os banco de dados SQL neste projeto. Também usaremos algumas libs para fazer os selects no banco através do código javascript, dessa forma:

Driver:

SELECT * from users;

Se torna no Query Builder (knex.js):

table('users').select('*');

5) Configurando banco de dados

Para configurar o banco de dados usaremos o knex.js:

Knex.js - A SQL Query Builder for Javascript

Para instalar, rode no terminal do projeto backend:

$ npm install knex

Após isso rode no mesmo terminal o comando de instalação do banco de dados, conforme o banco de dados desejado:

// Use apenas o comando relacionado 
// ao banco de dados que será usado
$ npm install pg
$ npm install sqlite3
$ npm install mysql
$ npm install mysql2
$ npm install oracledb
$ npm install mssql

Após a instalação dos pacotes inicie a aplicação knex:

$ npx knex init

O comando irá criar um documento chamado knexfile.js na raiz do projeto, tendo as configurações mais importantes de acesso ao banco de dados em cada um dos ambientes da aplicação:

// Update with your config settings.

module.exports = {

  development: { //  Ambiente de desenvolvimento
    client: 'sqlite3',
    connection: {
      filename: './dev.sqlite3'
    }
  },

  staging: { // Ambiente de produção para o desenvolvimento, simulando a produção online
    client: 'postgresql',
    connection: {
      database: 'my_db',
      user:     'username',
      password: 'password'
    },
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  },

  production: { // Ambiente de produção da parte online que será acessada pelo clientes
    client: 'postgresql',
    connection: {
      database: 'my_db',
      user:     'username',
      password: 'password'
    },
    pool: {
      min: 2,
      max: 10
    },
    migrations: {
      tableName: 'knex_migrations'
    }
  }

};

Um pouco de organização dos arquivos:

É necessário que o código esteja o mais dividido possível, pois caso as rotas aumentem, será necessário identificar rapidamente quais arquivos e quais funções são relacionadas, seguindo uma orientação mais concisa e estável para a futura manutenção de código.

Dessa forma, algumas alterações serão feitas mantendo o mesmo resultado final.

a) Crie uma pasta /src ela será responsável por conter todos os arquivos escritos incluindo o index.js:

     project
+    └── src
+        └── index.js

Lembre-se de alterar o comando de inicialização no package.json:

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

b) Crie um arquivo chamado 'routes.js' que será responsável por armazenar as rotas da aplicação:

    project
    └── src
        ├── index.js
+       └── routes.js

Retire a parte do express modificada no index.js e coloque no arquivo routes, será necessário fazer a importação do express novamente assim como a exportação do arquivo routes ficando mais ou menos dessa forma:

const express = require('express');

const routes = express.Router(); // Desacopla da variável 'app' para variável 'routes'
// Mude de app para routes
routes.post('/users', (request, response) => {
    const body = request.body;

    console.log(body);

    return response.json({
        evento: 'Semana Oministack 11',
        aluno: 'Nicolas Oliveira'
    });
});

module.exports = routes; // Exporta o arquivo

Faça a mesma coisa no arquivo index.js porém efetuando a importação de routes:

(...)
const routes = require('./routes'); // Importa a variável routes
(...)
app.use(routes); // executa a variável routes

Ficando mais ou menos desta forma:

const express = require('express'); 

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

const app = express();
// é necessário que as rotas estejam abaixo da execução do express
app.use(express.json());

app.use(routes);

app.listen(3333);

Crie uma pasta dentro do /src com o nome de database:

    /project
    └── /src
        ├── index.js
        ├── routes.js
+       └── /database

Finalize a configuração do banco de dados modificando o arquivo de conexão dentro do arquivo knexfile.js:

development: {
    client: 'sqlite3',
    connection: {
      filename: './src/database/db.sqlite' // aqui
    }
  },

6) Pensando em entidades e funcionalidades

As entidades são parte do conceito de modelagem de banco de dados e representam tudo aquilo que poderá ser salvo e armazenado no banco de dados.

Entidades:

  • ONG
  • Casos (incident)

Funcionalidades:

  • Login de ONG
  • Logout de ONG
  • Cadastro de ONG
  • Cadastrar novos casos
  • Deletar casos
  • Listar casos específicos de uma ONG
  • Listar todos os casos
  • Entrar em contato com a ONG

Executando o banco de dados:

a) Crie a pasta migrations:

    /project
    └── /src
        ├── index.js
        ├── routes.js
        └── /database
+            └── /migrations

b) Faça a alteração no arquivo knexfile.js indicando aonde será gerado a tabela. Conforme indicado na documentação:

Knex.js - A SQL Query Builder for Javascript

(...)
development: {
    client: 'sqlite3',
    connection: {
      filename: './src/database/db.sqlite' // aqui
    },
        migrations: {
      directory: './src/database/migrations'
    },
  },
(...)

Execute o comando de criação do banco de dados no terminal:

$ npx knex migrate:make create_ongs

PS: 'create_ongs' poderia receber qualquer nome necessário da tabela.

O comando gerará um arquivo com a seguinte configuração:

exports.up = function(knex) {

};

exports.down = function(knex) {

};

A variável com o objeto 'up' será a responsável por criar a tabela no banco de dados, já em 'down' serão todas as ações tomadas caso haja algum erro em 'up'. Conforme está indicado na documentação:

Knex.js - A SQL Query Builder for Javascript

O seguinte código:

knex.shema.createTable(tableName, callback);

Irá criar uma nova tabela no banco de dados dentro da pasta especificada, com uma função callback para modificar a estrutura da tabela, usando os comandos de construção de esquemas.

Mas o que são funções callback?

De forma simples, callback é uma função passada como parâmetro para outra função. Como por exemplo: setTimeout(() => alert("1"),5000); a função alert("1") é uma função callback nesse contexto.

Dessa forma a função callback de createTable() irá receber todas as informações de como será o nosso banco de dados:

Ficando assim:

exports.up = function(knex) {
  knex.schema.createTables('ongs', function(table) {});
};

Dentro da função callback basta adicionar as propriedades da tabela segundo a própria documentação:

exports.up = function(knex) {
knex.schema.createTables('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();
    });
};

Para o 'down' usaremos a função que deleta a tabela criada chamando .dropTable():

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

Para executar a criação:

$ npx knex migrate:latest

Dessa forma, pode-se criar quantas tabelas forem necessárias.

7) Construção do Back-end

a) Criação de usuários (ONG's)

Neste momento, faremos a primeira parte da criação do back-end, criando a função responsável por cadastrar novas 'ongs'.

Para isso acesse o arquivo routes.js e crie uma constante que irá receber os dados da requisição:

routes.post('/ongs', (request, response) => {
    const data = request.body;
        console.log(data);
    return response.json();
});

Por sua vez crie no insomnia o arquivo JSON relacionado a tabela com o método GET:

{
    "name": "Patas Amigas",
    "email": "contato@patas.com.br",
    "whatsapp": "3500000000",
    "city": "Passos",
    "uf": "MG"
}

Dessa forma, o terminal deverá retornar os valores correspondentes de dentro do insomnia comprovando o funcionamento até aqui. Caso não ha um terminal rodando o código, basta rodar na pasta do projeto (backend) npm start novamente.

Após isso deve-se criar um arquivo connections.js dentro da pasta database que será responsável por realizar a conexão com o banco de dados. Obs: Caso haja neste momento um arquivo db.sqlite certifique-se de deletá-lo.

    /project
    └── /src
        ├── index.js
        ├── routes.js
        └── /database
            ├── /migrations
            │  ├── 51441_create_ongs.js // arquivo de exemplo gerado pelo knex
            │  └── 56415_create_incidents.js
-           ├── db.sqlite X Deletar
+           └── connections.js -> Criar

Basicamente a configuração do banco de dados do knex segue este comando:

const connection = knex(configuration.development); //development -> arquivo knexfile.js

Para executá-lo corretamente deveremos importar e exportar alguns arquivos:

const knex = require('knex');
const configuration = require('../../knexfile'); // Voltar duas pastas para encontrar a raiz do projeto

const connection = knex(configuration.development);

module.exports = connection;

Dentro do arquivo routes.js faça a desestruturação dos elementos que serão recebidos do corpo da requisição:

routes.post('/ongs', (request, response) => {
    const {name, email, whatsapp, city, uf} = request.body;

    return response.json();
});

Iremos retornar um ID criptografado ao cliente após ele efetuar um cadastro, portanto usaremos a biblioteca crypto:

(...)
const crypto = require('crypto');
(...)
routes.post('/ongs', (request, response) => {
    const {name, email, whatsapp, city, uf} = request.body;

        const id = crypto.randomBytes(4).toString('HEX');

        return response.json();
});

Deveremos fazer a inserção dos dados no banco de dados, por enquanto sem tratamento, usando o método insert() de connection():

connection('ongs').insert({ });

Usando o Object Short Syntax podemos apenas o nome da constante do corpo da requisição retornando o id para o cliente:

(...)
connection('ongs').insert({
        id,
        name,
        email,
        whatsapp,
        city,
        uf,
});
return response.json({id});
(...)

Importante ressaltar que as requisições não são instantâneas, deveremos usar funções que usem do assincronismo para que não impeça o nosso código de continuar. Portanto o recomendado aqui é usar async await para as funções ficando desta forma:

routes.post('/ongs', async (request, response) => {
    const {name, email, whatsapp, city, uf} = request.body;

    const id = crypto.randomBytes(4).toString('HEX');

    await connection('ongs').insert({
        id,
        name,
        email,
        whatsapp,
        city,
        uf,
    });

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

b) Listagem de usuários

Caso os passos anteriores forem executados com sucesso, teremos uma função dentro de routes.js que executa de forma assíncrona a cada vez que criamos um novo usuário retornando um ID para o mesmo.

Iremos aqui fazer outra com o mesmo formato que irá listar todos os usuários cadastrados desde então:

routes.get('/ongs', async (request, response) => {
        const ongs = await connection('ongs').select('*');

    return response.json(ongs);
});

c) Tornando a aplicação mais escalável

Para tornar o código melhor até aqui, vamos rever a estrutura criada até então. As funções criadas em routes.js deverão ser globais e acessíveis por todo o código, não se limitando apenas a '/ongs'. Dessa forma, vamos criar uma pasta chamada controllers e isolar cada uma das funções com seu respectivo nome:

    /project
    └── /src
+       ├── /contollers
+       │    └── OngController.js // Controlador das requisições das ongs
        ├── index.js
        ├── routes.js
        └── /database
            ├── /migrations
            │   ├── 51441_create_ongs.js
            │   └── 56415_create_incidents.js
            └── connections.js

Basicamente as alterações aqui serão de mudança de diretórios e importação e exportação dos módulos. Então vamos lá:

  • Importe o arquivo criado em routes.js
    const OngController = require('./controllers/OngController');
  • Acesse o arquivo OngController.js e crie um module.exports para exportarmos os métodos criados até então:
module.exports = {};
  • Vamos pegar literalmente as funções criadas até aqui e modificá-las após isso:
module.exports = {
    // função de listagem
    routes.get('/ongs', async (request, response) => {
            const ongs = await connection('ongs').select('*');

        return response.json(ongs);
    });

    // Função de criação
    routes.post('/ongs', async (request, response) => {
        const {name, email, whatsapp, city, uf} = request.body;

        const id = crypto.randomBytes(4).toString('HEX');

        await connection('ongs').insert({
            id,
            name,
            email,
            whatsapp,
            city,
            uf,
        });

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

Basicamente as alterações necessárias para que funcione normalmente é retirar os métodos get e post que serão usados explicitamente em routes.js, nomeá-las e retirar suas arrow-functions. A função de listagem será nomeada como 'index' e a de criação como 'create' passando para antes do nome a funçãoasync:

module.exports = {
    // função de listagem
    async index ('/ongs', (request, response) { // async no começo com nome da função
            const ongs = await connection('ongs').select('*');

        return response.json(ongs);
    });

    // Função de criação
    async create ('/ongs', (request, response) { // async no começo com nome da função
        const {name, email, whatsapp, city, uf} = request.body;

        const id = crypto.randomBytes(4).toString('HEX');

        await connection('ongs').insert({
            id,
            name,
            email,
            whatsapp,
            city,
            uf,
        });

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

};
  • Retornando a routes.js, execute os métodos acima após o routes:
(...)
const OngController = require('./controllers/OngController');

const routes = express.Router();

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

8) Adicionando o módulo CORS

9) Enviando o Back-end ao Github

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment