Da introdução à APIs REST em produção
Henrique Rotava
- 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
- 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
- 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
- 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
- Não é um servidor de aplicação
- Não um framework
- Não é uma linguagem de programação
- 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
- 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
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);
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
- 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
- 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
- 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
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
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
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
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
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
- 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
- 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
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 |
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"
}
}
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
Pasta onde ficam os pacotes baixados pelo NPM
Não versionar!!!
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
- 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
- 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
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
- 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
Demonstração: primeiras operações com Mongoose
Demonstração: parte 2, implementação melhor estruturada
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
Operadores Mongo para Arrays:
https://docs.mongodb.com/manual/reference/operator/update-array/#update-operators
- 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
> npm i winston
- 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
- 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
Ótima ferramenta para testar, gerenciar, automatizar ou documentar APIs
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
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
- 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
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?
.
├-- 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
Automaticamente reinicia o app quando houverem modificações nos arquivos
> npm install --save-dev nodemon
"scripts": {
"dev": "nodemon main.js"
}
> npm run dev
Demonstração: Implementação da API Heroes, utilizando os padrões REST e boas práticas
- 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
- 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
- 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
- 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
- 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