Node.js

Da introdução à APIs REST em produção

Henrique Rotava

Apresentação

Objetivos

- Apresentar conceitos sobre o ecossistema Node.js frequentemente utilizados por profissionais no dia-a-dia do desenvolvimento de aplicações


- Instigar a curiosidade para buscar mais conhecimento sobre os tópicos apresentados e sugeridos ao longo do curso


- Demonstrar como construir aplicações prontas para serem utilizadas por clientes

Roadmap

- Introdução ao Node.js

- Programação assíncrona

- Callbacks e Promises

- Introdução ao NPM

- Trabalhando com bancos de dados não relacionais

- MongoDB e Mongoose

- Introdução a APIs RESTful

- Express

- Testes de integração

- Swagger e documentação

- Autenticação com JWT

- Configurações multi ambiente

JavaScript

- Linguagem de programação criada pela Netscape e originalmente usada para tornar páginas web dinâmicas

- ECMAScript uma especificação de uma linguagem de script, JavaScript é a implementação dessa especificação

- Uma engine é um programa que executa o código JavaScript

- Hoje está presente em navegadores, servidores, bancos de dados, hardwares, IoT, etc.

- Altamente dinâmica

- Em evolução constante com novas especificações anuais

- Muito popular e com uma comunidade enorme de desenvolvedores

O que é Node.js

- Uma plataforma para desenvolvimento de aplicações JavaScript no lado servidor

- Tem como objetivo fornecer uma maneira fácil de construir programas de rede escaláveis

- Execução single thread, possibilitando um melhor uso de memória

- Manipulação de I/O não bloqueante orientado a eventos

- Permite criar qualquer tipo de aplicação no lado servidor

- Programação em JavaScript

- Multiplataforma

O que não é o Node.js

- Não é um servidor de aplicação

- Não um framework

- Não é uma linguagem de programação

Onde usar Node.js

- Construção de APIs

- Aplicações de tempo real, chats, multi usuários

- Serviços streaming

- Aplicações com demanda de alta escalabilidade

- Construção de servidores de aplicação

- Criação de CLIs

- Automação de atividades

- Comunicação com bases de dados

- Tratamentos de requisições e respostas HTTP

- Build de aplicações

- Programas desktop

- *Manipulação de arquivos, vídeos, imagens

Como o Node.js funciona

Programação assíncrona

- Programação orientada a eventos

- Uma atividade é chamada e espera-se a resposta (callback) posterior

- Normalmente não existe uma ordem de execução do código

- Podendo gerar variáveis nulas ou indefinidas e consequentemente erros

Programação assíncrona

Demonstração: Exemplo de código sequencial vs assíncrono

// Código sequencial

console.log('Primeiro log');

function buscarPessoa() {
    console.log('Segundo log')
    return 'Asdrubal';
}

let pessoa = buscarPessoa();

console.log('Terceiro log', pessoa);
// Código assíncrono

console.log('Primeiro log');

function buscarPessoa() {
    setTimeout(function() {
        console.log('Segundo log')
        return 'Asdrubal';
    }, 1000);
}

let pessoa = buscarPessoa();

console.log('Terceiro log', pessoa);

Instalação Node.js e NPM

Windows: Acessar https://nodejs.org, baixar versão LTS e executar o instalador

Linux: executar o comando sudo apt-get install nodejs

macOS: Acessar https://nodejs.org, baixar versão LTS e executar o instalador

 

Após instalado, rodar os comandos a seguir para validar a instalação:

> node --version

> npm --version

 

Linux, Outras formas: https://nodejs.org/en/download/package-manager/#debian-and-ubuntu-based-linux-distributions-enterprise-linux-fedora-and-snap-packages

Padrão callback

- Callback é um modelo de desenvolvimento onde uma função passada como parâmetro para outra função e será chamada quando um evento assíncrono acontecer

- Por padrão deve ser o último parâmetro da função chamada

- Por padrão deve esperar dois parâmetros, erro e dado resultante, respectivamente

 

Demonstração: Implementar funções assíncronas: obterUsuario, obterEndereco e obterTelefone onde uma depende da outra

Callback hell

Implementação usando Promises

- ECMAScript 6 (2015)

- Uma forma melhor de resolver chamadas assíncronas

- É um objeto que representa a eventual conclusão ou falha de uma operação assíncrona

- Permite executar operações assíncronas de forma encadeada

Estado pending -> estado inicial, nada aconteceu

Estado fulfilled -> quando já executou

Estado rejected -> quando a operação falhou

 

Demonstração: transformando callbacks em Promises

Implementação com async/await

- ECMAScript 7 e 8 (2016 e 2017)

- Mesmo com Promises o código pode continuar bagunçado

- Facilita a visualização do fluxo das operações assíncronas, fazendo parecer uma execução síncrona

- Não altera a performance se usado corretamente

- É preciso trabalhar com Promises para usar async/await

 

Demonstração: refatorando implementação com Promises para usar async e await

Implementação com async/await

Async

- Async na frente faz com que a função retorne uma Promise

- A promise é resolvida quando a função retorna um valor

- A promise é rejeitada quanto a função lança uma exceção

Await

- A expressão await só pode ser usada dentro de uma função com async

- Pausa a execução de uma função assíncrona e aguarda a resolução da Promise, retornando o valor resolvido

- É preciso ter cuidado para não travar a execução de códigos que não devem ou não precisam aguardar a resolução de Promises

Manipulação de listas com for

Demonstração: comparação entre for, forin e forof

> npm init

> npm install request request-promise

 

Lista de heróis: https://herois.getsandbox.com/herois

Manipulação de listas com map

Array.prototype.map

O método map() invoca a função callback passada por argumento para cada elemento do Array e devolve um novo Array como resultado.

 

Demonstração: trabalhando com map

Manipulação de listas com filter

Array.prototype.filter

O método filter() cria um novo array com todos os elementos que passaram no teste implementado pela função fornecida.

 

Demonstração: trabalhando com filter

Manipulação de listas com reduce

Array.prototype.reduce

O método reduce() executa uma função reducer (provida por você) para cada membro do array, resultando num único valor de retorno.

 

Demonstração: trabalhando com reduce

NPM

- https://www.npmjs.com

- O Node possui poucos módulos por padrão sendo simples e leve

- NPM é um registro de pacotes Node e também um CLI

- Pacotes são aplicações JavaScript

- Os desenvolvedores podem gerenciar, versionar, instalar e publicar pacotes usando a CLI

- Existem pacotes que podem ser usados no browser, servidor ou linha de comando

- Não é necessário incluir as bibliotecas no projeto, apenas mapear a dependência, e elas serão baixadas e incluídas automaticamente

- Pacotes podem estar no registro npm, em um repositório git, ou no sistema de arquivos

NPM

- As dependências podem ter dependências

- Os pacotes instalados ficam na pasta node_modules e não devem ser versionados no repositório git

- Pacotes podem vir de um .zip ou um repositório .git

- Uma opção ao NPM é o Yarn https://yarnpkg.com/en/

- Yarn, mesmos pacotes, mas mais rápido?

 

Demonstração: Criando um projeto Node com NPM

> npm init

> npm init -y

> npm install request

> npm install -g yarn

Versões NPM

Code status Stage Rule Example version
Primeira release Novo produto Inicia com 1.0.0 1.0.0
Correção de bug, compatível com as versões anteriores Patch release Incrementar o terceiro dígito 1.0.1
Novas funcionalidades, compatível com as versões anteriores Minor release Incrementar o segundo dígito e reiniciar o terceiro para zero 1.1.0
Alterações que quebram a compatibilidade com versões anteriores Major release Incrementar o primeiro dígito e reiniciar o segundo e terceiro 2.0.0

Tipos de atualização em versões

Patch releases: 1.0 or 1.0.x or ~1.0.4

Minor releases: 1 or 1.x or ^1.0.4

Major releases: * or x

{
    "dependencies": {
        "@dep1": "~1.0.0",
        "@dep2": "*",
        "@dep3": "^2.0.3", // Padrão
        "@dep4": "3.3.2"
    }
}

package-lock.json

Descreve a exata árvore de dependências que o projeto foi gerado

Previne problemas em outros ambientes

Permite ver versões anteriores da árvore de dependências

Otimiza a instalação prevenindo resolução de pacotes repetidos

Deve ser versionado no repositório

node_modules

Pasta onde ficam os pacotes baixados pelo NPM

Não versionar!!!

Trabalhando com bancos de dados

Bancos de dados SQL

- Estrutura de dados fixa

- Modelagem estrutural no banco

- Menos flexibilidade

- Armazenamento em tabelas

- Fortes regras de validação (constraints)

- Maior confiabilidade e consistência

- Menor escalabilidade ou mais complexa, normalmente vertical

Bancos de dados NoSQL

- Estrutura dinâmica

- Aplicação modela os dados

- Maior flexibilidade

- Armazenamento em documentos (ou outras formas)

- Restrições fracas de validação

- Maior escalabilidade (horizontal) e performance

- Possibilidade de salvar dados embedded

Node.js + MongoDB

- Geralmente em aplicações Node.js usa-se MongoDB

- Não é uma regra, podendo-se usar qualquer banco

- Grande proximidade pelo fato da linguagem e da escalabilidade

MongoDB

- https://www.mongodb.com/cloud

- Armazena as informações no formato de documentos Binary JSON (BSON)

- Os dados são agrupados na estrutura database > collections > documentos

- A manipulação dos dados e queries são feitas utilizando JavaScript

MongoDB, exemplos

mongo “mongodb+srv://usuario:senha@url/banco” // Conectar

show dbs // Lista databases no servidor

use dev // Indica qual banco usar

show collections // Mostra collections do banco atual

db.herois.insert({name: 'Superman'}) // Insere documento

db.herois.find() // Lista todos os documentos

db.herois.find().pretty() // Lista formatada

db.herois.update({_id: ObjectId("")}, {nome:'Batman'}) // Atualização completa do documento

db.herois.find({_id: ObjectId("")}) // Busca por query

db.herois.update({_id: ObjectId("")}, {$set: {nome:'Batman'}}) // Atualização de atributos específicos

db.herois.update({}, {$set: {nome:'Batman'}}) // Atualiza um

db.herois.update({}, {$set: {nome:'Batman'}}, {multi: true}) // Atualiza todos

db.herois.count() // Contador de documentos na collection

db.herois.findOne({nome: 'Batman'}) // Busca um

db.herois.find().forEach(function(heroi){print(heroi.poderes)}) // Execução de JavaScript

db.herois.find().map(heroi => heroi.nome); // Execução de JavaScript

db.herois.remove({}) // Remove todos documentos

Mongoose

- Biblioteca para modelagem de dados

- Permite, gerenciar conexões com o servidor do banco

- Permite gerenciar relacionamentos, validação e tradução de dados

- Schema: definição de atributos de um documento

- Model: definição de uma collection e seu schema

> npm install mongoose

 

Conexão com o banco:

mongodb+srv://guardiao:grayskull80@mongocloud-cyj9w.mongodb.net/nome-dev?retryWrites=true&w=majority

Mongoose

Demonstração: primeiras operações com Mongoose

Demonstração: parte 2, implementação melhor estruturada

Exercício

Implementar um repositório TeamsRepository para armazenamento dos dados das equipes de heróis estendendo o repositório Mongo.

- Atributo name do tipo string

- Atributo members do tipo array de string, para salvar a lista dos ids dos membros

- Atributo publisher para salvar a editora

- Método addHeroToTeam(team, hero) para adicionar o herói na equipe

- Método removeHeroFromTeam(team, hero) para remover o herói da equipe

- Método byHero(heroId) que retorna as equipes que o herói faz parte

Exercício (Dicas)

Configurações por ambiente

- Configurações, não devem ficar fixas no código

- Geralmente são diferentes em cada ambiente

- Uma forma de tornar estas informações dinâmicas é através de variáveis de ambiente

- Variáveis de ambiente em cloud e orquestradores

 

> npm install env-cmd

Logs

> npm i winston

APIs RESTful

- API (Application Programming Interface)

- Rotinas e padrões documentados por uma aplicação que permitem outros sistemas se comunicarem com ela sem saber detalhes da implementação

 

- REST (Representational State Transfer)

- Princípios e regras que se seguidos permite a implementação de projetos com uma interface definida, padronizada

- Permite a integração entre sistemas pela Web, baseado em uma comunicação via rede

- Utilização da arquitetura e padrões da própria web, como o protocolo HTTP

URI's e recursos

- Uma API REST gerencia recursos: cliente, produto, página, imagem, arquivo

- Os recursos são identificados através da URI

- Não usar uma ação como parte da URI

- Manter um padrão (singular, plural, formato letras, etc)

- URI's legíveis e amigáveis

- Transformar ações em atributos quando necessário

 

-- Fazer --
http://api.com.br/usuarios
http://api.com.br/usuarios/{id}
http://api.com.br/usuarios/{id}/times/{id}
https://api.com.br/produtos/{id}/likes

-- Não fazer --
http://api.com.br/usuarios/criar
http://api.com.br/usuarios/post

Postman

Ótima ferramenta para testar, gerenciar, automatizar ou documentar APIs

Link: https://www.getpostman.com/

Métodos HTTP

Utilizar os métodos HTTP para indicar a ação a ser realizada:

- GET: solicita a representação de um recurso

- POST: submeter uma entidade a um recurso, criando essa entidade

- DELETE: remove um recurso específico

- PUT: substitui a representação do recurso pelo dado enviado na carga da solicitação

- PATCH: aplicar modificações parciais de um recurso

Códigos HTTP

Uma boa prática para informar o resultado da requisição

- 2xx Indica que a requisição foi processada com sucesso

- 3xx Indica ao cliente uma ação a ser tomada para que a requisição possa ser concluída

- 4xx Indica erro(s) na requisição causado(s) pelo cliente

- 5xx Indica que a requisição não foi concluída devido a erro(s) ocorrido(s) no servidor

Outros Padrões

- Stateless, é o cliente quem se identifica e mantém seus dados de identidade

- Não manter dados de autenticação em sessão

- Vários formatos de representação de recursos: JSON, XML, HTML, CSV, outros

- Definir qual conteúdo é aceito via cabeçalho accept

- Utilizar SSL/TLS para transporte da informação

Express

Um dos frameworks mais populares para criação de aplicações web e serviços em Node.js

- Funciona através de middlewares

- Simples e leve

 

Porque usar um framework?

Estrutura de diretórios

.
├-- config
|    ├- db.json
|    ├- service.json
|
├-- routes
|    ├- controllers
|    ├- middlewares
|    ├- routes.js
|
├-- services
|    ├- serviceOne
|    ├- serviceTwo
|
├-- db
|    ├- models
|    ├- migrations
|
├-- core
|    ├- users.js
|    ├- products.js
|    ├- comments.js
|
├-- app.js
.
├-- config
|    ├-- db.json
|    ├-- service.json
|
├-- db
|    ├-- connectionStuff
|
├-- users
|    ├-- controllers # Lógica
|    ├-- database
|    ├-- model
|    ├-- routes
|    ├-- service
|    ├-- index.js # Módulo
|
├-- app.js    # Express
├-- main.js   # Rede

Organização por função e organização por módulo

Nodemon

Automaticamente reinicia o app quando houverem modificações nos arquivos

 

> npm install --save-dev nodemon

"scripts": {
    "dev": "nodemon main.js"
}

> npm run dev

API Heroes

Demonstração: Implementação da API Heroes, utilizando os padrões REST e boas práticas

Testes de integração

- Testes disparando requisições à API e validando a resposta

- Validação de alto nível, validando a parte do software usada pelo cliente

- Testes unitários, validam o funcionamento de pequenas partes do sistema e podem ser usados em conjunto

- Consegue fazer uma validação geral do funcionamento de todas as camadas em conjunto

- Permite fazer grandes alterações na aplicação e manter os mesmo testes

 

Demonstração: Implementação de testes para as rotas da API

> npm i --save-dev mocha chai chai-http chai-date-string

Validação de dados

- Retornar respostas que deixem claro o que ocorreu durante o processamento da requisição

- Erros de autenticação

- Erros do servidor

- Erros de validação de dados

- Mensagem de sucesso

 

> npm install express-validator

Exercício

- Implementar rotas da API para o recurso teams (equipes dos heróis):

    - GET /teams

    - POST /teams

    - PUT /teams/:id

      - DELETE /teams/:id

 

- Implementar rota para obter equipes de um herói:

     - GET /heroes/:id/teams

     - GET /heroes/:id/teams/:id

Autenticação com JWT

- JSON Web Token

- Padrão RFC 7519 para transmitir e armazenar objetos JSON de forma segura entre aplicações formado por três partes:

- Header: identifica o tipo do token

- Payload: Informação a ser transmitida

- Signature: Concatenação do hash do header e payload com uma chave secreta

- Enviar token via Header Authorization com a flag Bearer

- Permite implementar APIs stateless apenas passando o token nas requisições

Autenticação com JWT

- https://jwt.io/

 

Demonstração: implementando autenticação com JWT

> npm i express-jwt jsonwebtoken

Swagger e documentação

- Documentar software é importante, APIs mais ainda

- Demonstrar quais recursos existem, quais ações são possíveis e quais são os parâmetros esperados

- Swagger, dentre outras utilidades, é uma ferramenta de interface para disponibilizar especificações de APIs REST


Swagger Editor: https://editor.swagger.io/

 

> npm install swagger-ui-express

Obrigado!

Node.js - Da introdução à APIs REST em produção

By Henrique Rotava

Node.js - Da introdução à APIs REST em produção

  • 674