Da introdução à APIs REST em produção
Henrique Rotava
- Apresentar conceitos sobre o ecossistema Node.js comumente 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 uma aplicações prontas para serem utilizadas por clientes.
- Introdução ao Node.js
- Programação assíncrona
- Reintrodução ao JavaScript
- Callbacks vs Promises
- Manipulação de listas
- Introdução ao NPM
- Trabalhando com bancos de dados não relacionais
- MongoDB e Mongoose
- Introdução a APIs RESTful
- Express
- Swagger e documentação
- Autenticação com JWT
- Configurações por ambiente
- Uma plataforma para desenvolvimento de aplicações 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 o 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
- Manipulação de arquivos, vídeos, imagens
- Criação de CLIs
- Automação de atividades
- Comunicação com bases de dados
- Tratamentos de requisições e respostas HTTP
- Executa em uma máquina virtual JavaScript, a engine V8
- Engine usada pelo Google para interpretar e executar JavaScript no navegador
- A engine é escrita em C++, é rápida e roda em qualquer arquitetura
- Orientado a eventos
- Usando JavaScript tudo se torna fácil e comum
- Processamento single thread com event loop
- Eventos empilhados na event queue e processados usando o algoritmo FIFO
- Operações bloqueantes são delegadas para o sistema operacional, liberando a fila
- Quando a atividade terminar, o event loop devolve a resposta para quem chamou, via callback
- Atividades que demandam alto processamento podem bloquear toda a fila
- Programação orientada a eventos
- Uma atividade é chamada e espera-se a resposta posterior
- 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
- 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 servidores, bancos de dados, hardwares, IoT, etc.
- Altamente dinâmica
- Em evolução constante com novas especificações anuais
- Muito popular, comunidade enorme de desenvolvedores
Disclaimer: por motivos de compatibilidade alguns códigos podem não funcionar em todos os ambientes, como em navegadores desatualizados
// var, forma antiga de declaração, evitar usar
var texto = 'exemplo de texto'
// A partir do ES2015
// const, forma preferível, não pode ser reatribuída,
// valor não é imutável, mais fácil de debugar
const fruta = 'abacaxi'
const funcao = function () {}
// let, para variáveis que mudam de valor
let valor = 12.89
valor = 5
let usuario = {
nome: 'Administrador'
}
function soma (valor1, valor2) {
return valor1 + valor2;
}
const resultado = soma(50, 33)
- São subprogramas
- Executadas apenas quando chamadas
- Sempre retornam um valor, por padrão retornam undefined
- Valores podem ser passados por parâmetro
let dinamico;
console.log(dinamico, typeof dinamico);
dinamico = 'literal';
console.log(dinamico, typeof dinamico);
dinamico = 5;
console.log(dinamico, typeof dinamico);
dinamico = ['array', 'de', 'strings']
console.log(dinamico, typeof dinamico, Array.isArray(dinamico));
dinamico = {
objeto: true
}
console.log(dinamico, typeof dinamico);
- Variáveis não tem validação do tipo de dado
class User {
constructor (nome, dataNascimento) {
this.nome = nome
this.dataNascimento = dataNascimento
}
calcularIdade () {
// Calcular idade baseada na propriedade dataNascimento
}
}
- Introduzidas no EcmaScript 6 (2015)
- Basicamente são funções especiais
- Um novo modelo de herança e orientação a objeto
- Uma forma mais simples e clara de criar objetos e lidar com heranças
/*
* Verificação condicional
* Os valores null, "", undefined, 0 e false são considerados false em uma condição
*/
console.log(!!null, !!"", !!undefined, !!0)
// Template strings, usando crases
const pontos = 102
console.log(`Você possui ${pontos} pontos`)
// Aspas simples ou duplas
const simples = 'possível' // parece mais simples e limpo
const duplas = "possível também"
// O ponto e vírgula é opcional
console.log('Quem precisa de ponto e vírgula?');
- 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 resultado, 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 axios
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
Implementar a class UserDB emulando um banco de dados
Atributos:
- users deve conter a lista de usuários
O usuário deve possuir os atributos: name, login (id, único, obrigatório) e password (obrigatória)
Métodos (devem retornar Promises):
- create(user) deve adicionar usuário a lista
- read(query, fields) deve retornar todos os usuários que satisfazerem a query, se a query não for passada retornar todos os usuários, se o argumento fields for passado, retornar apenas os campos desejados
- update(user) deve encontrar e atualizar o usuário na lista
- delete(login) deve remover o usuário que possuir o login informado
Array.prototype.push(newItem) para adicionar items a uma lista
Object.prototype.keys(object) para obter uma lista de propriedades de um objeto
Object.prototype.assign(target, source) para copiar todos atributos de um objeto para outro
Array.prototype.splice(position, quantity) para remover itens de uma lista
- O Node é leve por oferecer apenas funções básicas e permitindo instalar pacotes conforme a necessidade do projeto
- NPM é um registro de pacotes Node e também um CLI
- Pacotes são aplicações JavaScript
- Os desenvolvedores podem gerenciar, instalar e publicar pacotes usando a CLI
- Existem pacotes que podem ser usados no browser, servidor ou linha de comando
- Permite versionar os pacotes
- Não é necessário incluir as bibliotecas no projeto, apenas mapear a dependência, e elas serão baixadas e incluídas no projeto
- Para pacotes com diferentes versões para SO o NPM irá instalar o pacote correto
- As dependências podem ter dependências
- Os pacotes instalados ficam na pasta node_modules e não devem ser versionados
- Um concorrente 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
Demonstração: Implementação de uma CLI para CRUD de usuários
> npm init
> npm install commander
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
- Maior integração entre os dois por ambos usarem JavaScript
- Similaridade de paradigma
- 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@herois-dev-cyj9w.mongodb.net/dev?retryWrites=true&w=majority
Demonstração: primeiras operações com Mongoose
Demonstração: parte 2, implementação estruturada e padronizada
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
- 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
Ó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
É possível implementar uma aplicação sem usar um framework?
Demonstração: implementando uma rota usando o módulo http do Node.js
> npm install express
.
├-- 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 (responsabilidade) ou por funcionalidade (módulo)
- Todos os arquivos de um assunto ficam no mesmo diretório
- Imports são mais curtos e simples
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
- 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 e documentar 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
- 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 no Header Authorization com a flag Bearer
- Permite implementar APIs stateless apenas passando o token nas requisições
Demonstração: implementando autenticação com JWT
> npm install express-jwt
> npm install jsonwebtoken
- 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
> npm install dotenv
> npm install --save-dev cross-env
- 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
> npm install swagger-ui-express
- Aprofundar o conhecimento sobre os tópicos apresentados durante o curso
- Testes unitários
- Node.js com bancos relacionais
- hapi.js
- Microserviços
- Typescript