Node.js
Node.js
Desenvolvido por Ryan Dahl em 2009
Plataforma para desenvolvimento de aplicações servidoras
utilizando JavaScript
Baseado naa Google’s V8 JavaScript Engine
Desenhado para situações de alta concorrência
•
Sem threads nem lançamento de novos processos
MEAN, a full stack
M
ongoDB
: base de dados NoSQL (não relacional)
E
xpress
: server-side web framework
A
ngular
: framework de desenvolvimento de aplicações no
cliente/browser
O que é que o Node.js pode fazer?
Node.js can generate dynamic page content
Node.js can create, open, read, write, delete, and close files on
the server
Node.js can collect form data
Porquê utilizar Node.js?
Operações de I/O sem bloqueios
Muitas bibliotecas (módulos)
Comunidade muito ativa (IRC, Mailing lists, Twitter, Github)
Suportado pelos principais sistemas operativos
Porquê utilizar Node.js?
“Node's goal is to provide na easy way to build
Node.js vs ASP/PHP – Processamento do pedido
Node.js elimina a fase da espera continuando, simplesmente,
para a tarefa seguinte
ASP e PHP
Node.js
1 Sends the task to the computer's file system.
1 Sends the task to the computer's file system.
2 Waits while the file system opens and reads the file.
2 Ready to handle the next request.
3 Returns the content to the client.
3 When the file system has opened and read the file,
the server returns the content to the client
Node.js
Suporta concorrência através do conceito de
eventos
e
callbacks
Utiliza
funções assíncronas
e o
padrão observer
Existe uma única thread que mantem um ciclo de eventos e, sempre
que uma tarefa termina, dispara o evento correspondente que
Tratamento de eventos
Criar uma
função callback
que responde ao evento (criar o listener)
Registar a função callback no emissor do evento para um dado evento
Quando o emissor emite o evento a função callback é executada
var listener1 = function() {
console.log('Execução da função listener 1.’); }
on('evento1', listener1)
//ou
addListener('evento1', listner1)
Exemplo
var eventos = require('events');
var eventEmitter = new eventos.EventEmitter(); var listener1 = function() {
console.log('Execução da função listener.'); }
eventEmitter.on('evento1', listener1); eventEmitter.emit('evento1');
console.log("Fim do Programa.");
C:\> node exemplo.js
Execução da função listner
Fim do Programa.
C:\>
Node.js – orientado a eventos
Uma aplicação servidora Node.js corre num único thread.
Embora só possa realizar uma tarefa de cada vez, Node.js é muito
eficiente a tratar muitos pedidos simultaneamente, devido à sua
natureza orientada a eventos.
Se no processamento de um pedido inicia a realização de uma
operação mais demorada, o programa retorna e passa para o
próximo pedido.
Quando a operação mais demorada termina, lança um evento e
Node.js continua a processar esse pedido.
Node.js – orientado a eventos
Threads
Asynchronous Event-driven
Lock application / request with listener-workers
threads
Only one thread, which repeatedly fetches an event
Using incoming-request model
Using queue and then processes it
multithreaded server might block the request which
might involve multiple events
Manually saves state and then goes on to process
the next event
Using context switching
No contention and no context switches
Using multithreading environments where listener
and workers threads are used frequently to take an
incoming-request lock
Using asynchronous I/O facilities (callbacks, not
poll/select or O_NONBLOCK) environments
Processamento síncrono vs. assíncrono
Here is how PHP or ASP handles a file request:
1.
Sends the task to the computer's
file system.
2.
Waits while the file system opens
and reads the file.
3.
Returns the content to the client.
4.
Ready to handle the next
request.
Here is how Node.js handles a file request:
1.
Sends the task to the computer's file
system.
2.
Ready to handle the next request.
3.
When the file system has opened and
read the file, the server returns the
content to the client.
4.
Node.js eliminates the waiting, and
simply continues with the next request.
5.
Node.js runs single-threaded,
non-blocking, asynchronously programming,
which is very memory efficient.
Exemplo
Sem tratamento de eventos
var http = require('http');
http.createServer(function(req,res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('<h2> Olá mundo! </h2>');
}).listen(8080);
Exemplo
Com tratamento de eventos
var http = require('http');
var server = http.createServer();
server.on('request', function(req,res) {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' }); res.end('<h2> Olá mundo! </h2>');
});
server.listen(8080)
Node.js – Não obstrutivo
Todos os recursos e módulos disponibilizados para o Node.js
adotam um padrão não obstrutivo para a escrita do código.
Código estruturado para que as operações possam ser executadas
de forma independente entre si
Exemplo
var frase;
carregaFrase = function (callback) { setTimeout(function() {
//Simula leitura da frase de uma base de dados.
frase = "Minha frase obstrutiva"; callback(); }, 3000) } imprimeFrase = function () { console.log(frase); } carregaFrase(imprimeFrase); console.log("Olá");
C:\> node exemplo.js
Olá
Minha frase obstrutiva
C:\>
NPM
NPM – Node Package Manager
Repositório online de projetos em código livre para o Node.js
(
http://www.npmjs.com
)
Utilitário de linha de comando que interage com o repositório
online e permite a instalação, a gestão de versões e de
dependências dos módulos
Requisitos Node.js
Download do Node.js em
http://nodejs.org
Instalar Node.js
Instalar a framework Express
Download Visual Studio Code em
https://code.visualstudio.com/
Instalar Visual Studio Code
C:\> npm install express --save
C:\> node –v
Primeiro projeto Node.js
Criar um diretório para o projeto
Executar Visual Studio Code e escolher
Open folder …
Selecionar diretório criado para o projeto
Primeiro projeto Node.js
Ficheiro package.json
•
Criado na raiz do diretório do projeto
•
Contem informações sobre o projeto
o
Nome
o
Versão
o
Dependências
o
Licença
o
Ficheiro main
o
…
Pode ser gerado com o comando npm init
Primeiro projeto Node.js
Exemplo de um ficheiro package.json
{ "name": "node-api", "main": "server.js", "dependencies": { "body-parser": "~1.0.1", "express": "~4.0.0", "mongoose": "*", "mongoose-id-validator": "^0.4.3" } } package.json
Primeiro projeto Node.js
Com o package.json definido
Instalar módulos referenciados
Este comando cria diretório
node_modules
com as bibliotecas
C:\> cd ProjectFolder
Primeiro projeto Node.js
Os módulos instalados são carregados pelo código recorrendo à
função
require(
<nome do módulo>
)
Exemplo:
var uc = require('upper-case'); console.log(uc("hello world!"));
Exemplo.js
C:\> node exemplo.js
HELLO WORLD
Primeiro projeto Node.js
Módulo HTTP
•
Permite ao Node.js transferir informação com o protocolo HTTP
Servidor Web
•
O módulo HTTP permite criar um servidor Web (método createServer())
var http = require('http');
var http = require('http');
//create a server object:
http.createServer(function (req, res) {
res.write('Hello World!'); //write a response to the client
res.end(); //end the response
Cabeçalho HTTP
Para que o servidor HTTP envie Código HTML deve ser alterada a
propriedade content-type do cabeçalho HTTP
Para tal, utiliza-se o método
writeHead(
<status_code>
,
<header>
)
•
O primeiro argumento é o Código HTTP 200
•
O Segundo, é um objeto contendo o cabeçalho da resposta.
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/html'}); res.write('Hello World!');
res.end(); }).listen(8080);
Query String
Argumento
req
•
Objeto, do tipo http.IncomingMessage, que representa o pedido do cliente
•
Este objeto possui a propriedade
url
que armazena a parcela do endereço que
segue o nome do domínio.
var http = require('http');
http.createServer(function (req, res) { res.writeHead(200,
{'Content-Type': 'text/html'}); res.write(req.url);
res.end(); }).listen(8080);
Query String
Outro Exemplo
var http = require('http'); var url = require('url’);
http.createServer(function (req, res) { res.writeHead(200,
{'Content-Type': 'text/html'}); var qstring = url.parse(req.url, true).query; var txt = qstring.nome + " " + qstring.idade; res.end(txt);
Módulos relevantes
Alguns módulos relevantes para o desenvolvimento de aplicações
Web em Node.js
•
express
– framework para desenvolvimento de aplicações Web
•
body-parser
– middleware para tratamento de JSON e dados
•
cookie-parser
– processamento de cookies
RESTFull API com
Express
Gerar estrutura da aplicação Web (scafolding)
https://expressjs.com/en/starter/generator.html
How to structure a Node.js Express project, by Sean McGary on Mar 27, 2016
C:\> cd folderWithProjects
C:\> npm install -g express-generator
C:\> express myAppName
Criar uma RESTful API
Criar o ficheiro server.js com o seguinte código:
var express = require('express'); app = express();
port = process.env.PORT || 8080; app.listen(port);
console.log('RESTful API server started on: ' + port);
OO, Database & ODM
Model
•
As “classes” dos objetos de negócio/domínio
MongoDB
•
NoSQL, document-centered database
Mongoose
•
http://mongoosejs.com
•
ODM: Objet/Document Mapper
Definição do Modelo
Conexão à base de dados MongoDB
mongoose
– modulo que permite a inteação com uma base de
dados MongoDB. Disponibiliza funcionalidades de modelação da
informação baseadas em esquemas e inclui processos para
conversão de tipos de informação, validação e construção de
inquéritos.
var mongoose = require('mongoose');
Definição do Modelo
A modelação assenta em esquemas que definem a estrutura da
informação
var Schema = mongoose.Schema; var blogSchema = new Schema({
title: String, author: String, body: String,
comments: [{ body: String, date: Date }], date: { type: Date, default: Date.now }, hidden: Boolean, meta: { votes: Number, favs: Number } });
Definição do Modelo
Os Schema Types são:
•
String
•
Number
•
Date
•
Buffer
•
Boolean
•
Mixed
•
ObjectId
•
Array
var schema = new Schema({ name: String,
binary: Buffer, living: Boolean,
updated: { type: Date, default: Date.now }, age: { type: Number, min: 18, max: 65 }, mixed: Schema.Types.Mixed, _someId: Schema.Types.ObjectId, ofString: [String], ofNumber: [Number], ofObjectId: [Schema.Types.ObjectId], ofArrays: [[]] ofArrayOfNumbers: [[Number]] nested: {
stuff: { type: String, lowercase: true, trim: true } }
Definição do Modelo
O relacionamento entre esquemas é feito pelo element
ObjectId
(…)
var authorSchema = Schema({ name : String,
stories : [{ type: Schema.Types.ObjectId, ref: 'Story' }]
}); (…) (…)
var storySchema = Schema({
author : { type: Schema.Types.ObjectId, ref: 'Author' }, title : String
}); (…)
Definição do Modelo
A criação do modelo é responsabilidade do método
model()
do
mongoose
var Blog = mongoose.model('Blog', blogSchema);
Model name
Validações
Todos os Schema Types possuem funcionalidades de validação de
campo obrigatório
O tipo Number possui validação de min e max
O tipo String possui:
•
enum – que permite especificar a lista de valores possiveis para o campo
•
match – que permite definer uma expressão regular para validar o valor do
campo
•
maxlength e minlength – para definir a dimensão máxima e minima do
texto
Módulo mongoose-id-validator
Plug-in que permite verificar se um documento que refere outro pelo
seu ID está a referenciar um documento que, de facto, existe.
Não garante coerência da informação
•
Não identifica situações de IDs válidos aquando da inserção e que foram
posteriormente removidos
•
É necessário implementa lógica de validação para operações de DELETE
Instalação:
Módulo mongoose-id-validator
Utilização:
var idvalidator = require('mongoose-id-validator’);
var ManufacturerSchema = new Schema({ name : String
});
var Manufacturer = mongoose.model('Manufacturer', ManufacturerSchema); var CarSchema = new Schema({
name : String,
manufacturer : { type: Schema.Types.ObjectId, ref: 'Manufacturer’, required: true } });
CarSchema.plugin(idvalidator);
Módulo mongoose-id-validator
Utilização:
var ford = new ManufacturerSchema({ name : 'Ford' }); ford.save(function() {
var focus = new Car({ name : 'Focus' });
focus.manufacturer = "50136e40c78c4b9403000001"; focus.validate(function(err) {
//err.errors would contain a validation error for manufacturer with default message
focus.manufacturer = ford;
focus.validate(function(err) {
//err will now be null as validation will pass
}); }); });
Definição do Modelo
Esquema
•
documento
vs.
entidade/tabela
•
desnormalização baseada em DDD
vs.
normalização
o
Aggregates + Parent-child vs. Chave estrangeira
Operações
atómicas
de escrita
•
Num documento e numa coleção
Exemplo do Modelo
var mongoose = require("mongoose");
var mongoose_validator = require("mongoose-id-validator"); var departamentoSchema = mongoose.Schema({
Nome: String,
Diretor: {type: mongoose.Schema.Types.ObjectId, ref: 'Pessoa'}, Alunos: [{
Aluno: {type: mongoose.Schema.Types.ObjectId, ref: 'Pessoa’}, Notas: [{
UnidadeCurricular: {type: mongoose.Schema.Types.ObjectId, ref: 'UC'}, Nota: Number, Data: Date }] }], }); receitaSchema.plugin(mongoose_validator);
Routeamento
Configuração do Roteamento
A framework express fornece funcionalidades de roteamento
O método
Router()
permite instanciar e gerir essas funcionalidades
var router = express.Router();
router.use(function(req, res, next) {
console.log('Something is happening.'); next();
});
router.get('/', function(req, res) {
res.json({ message: 'hooray! welcome to our api!' }); });
Configuração do Roteamento
A definição de novas rotas é feita pelo método
Route()
Este objeto também permite definir os métodos HTTP associados ao
roteamento
router.Route('/tasks')
.get(todoList.list_all_tasks) .post(todoList.create_a_task); router.Route('/tasks/:taskId')
.get(todoList.read_a_task) .put(todoList.update_a_task)
.delete(todoList.delete_a_task);
Cliente REST usando ‘node-rest-client’ - GET
outros métodos e mais detalhes em:
Autenticação e
Autorização
Introdução
Autenticação
: determina que o utilizador é quem diz ser
Autorização
: determina quem (autenticado) tem acesso a quê
Since the HTTP protocol is stateless, this means that if we authenticate
a user with a username and password, then on the next request, our
application won't know who we are. We would have to authenticate
again, and again, and again, ...
Session-based (server-based) authentication
•
Informação de autenticação é armazenada no servidor em sessões (em memória
ou com persistência)
•
Escalabilidade é afetada
•
E ainda Cross-Origin Resource Sharing - CORS e Cross-Site Request Forgery - CSRF
(cf. Administração de Sistemas)
Token-based authentication
1.
User Requests Access with Username / Password
2.
Application validates credentials
3.
Application provides a signed token to the client
4.
Client stores that token and sends it along with
every request
5.
Server verifies token and responds with data
cf. https://scotch.io/bar-talk/the-ins-and-outs-of-token-based-authentication
JWT (JSON Web Tokens)
A JSON Web Token has three parts:
•
the crypto information, the payload, and the signature.
On the server end the token is verified by re-encrypting the
payload with the crypto info and checking to see if it matches the
signature. Any change to the payload will negate the signature
and invalidate the token.
The token is secure because the Salt is only known to the server
so resigning a fake token is virtually impossible.
Como?
Create and verify JWTs
Encrypt and decrypt password
Tutoriais:
•
https://scotch.io/tutorials/authenticate-a-node-js-api-with-json-web-tokens•
https://medium.freecodecamp.org/securing-node-js-restful-apis-with-json-web-tokens-9f811a92bb52•
https://github.com/expressjs/express/blob/master/examples/route-middleware/index.js•
https://github.com/VinceZK/authorizationC:\> npm install jsonwebtoken --save
Modelo User
Definição da classe modelo user.js
var mongoose = require('mongoose'); var Schema = mongoose.Schema;
module.exports = mongoose.model('User', new Schema({ name: String, password: String, email: String, isAdmin: Boolean }) ); user.js
Definição das rotas para User
Alterar server.js referenciando os novos roteamentos
Criar as novas rotas em UserRoutes.js
var express = require('express'); var jwt = require('jsonwebtoken'); var bcrypt = require('bcryptjs’);
var UserModel = require('../models/user');
var VerifyToken = require('../auth/VerifyToken’); var router = express.Router();
(…)
UserRoutes.js server.js
(…)
var UserRoutes = require('./app/routes/UserRoutes'); app.use('/api/users',UserRoutes);
Definição das rotas para User - register
Rota
/api/users/register
(…)
router.post('/register',function(req,res){ var user= new UserModel();
user.name = req.body.name; user.password = bcrypt.hashSync(req.body.password,8); user.email = req.body.email; user.isAdmin = req.body.isAdmin; user.save(function (err){ if(err)
return res.status(500).send("There is a problem registering the user."); res.json({message:"User registered!"});
}) });
Definição das rotas para User - register
Definição das rotas para User - login
Rota
/api/users/login
(…)
router.post('/login',function (req,res){
UserModel.findOne({email: req.body.email}, function(err,user){ if (err) throw err;
if(!user) {res.json({success:false,message:'Authentication failed.’});} else if(user){
if(!bcrypt.compareSync(req.body.password, user.password))
return res.status(401).send({auth:false,token:null,message: 'Auth failed.'}); else {
const payload= {user:user.email};
var theToken = jwt.sign(payload, 'TheSecret_123456789', {expiresIn:86400}); res.json({success:true,message:'Enjoy your token!',token:theToken});
} } }); });
Definição das rotas para User - login
Definição das rotas para User - /
Rota
/api/users/
(…)
function hasRole(userEmail, role, func){
UserModel.findOne({email: userEmail}, function (err, user){ if (err) throw err;
if(!user){
res.json({success: false, message: 'Authentication failed.'}); }
else if (user) {
func(role === 'administrator' && user.isAdmin === true) }
}) }
Definição das rotas para User - /
Rota
/api/users/
(…)
router.get('/', VerifyToken, function(req, res){
hasRole(req.user, 'administrator', function (decision) { if (!decision)
return res.status(403).send(
{auth:false,token: null,message: 'You have no authorization.'} );
else
UserModel.find(function (err, users){ if (err) res.send(err); res.json(users); }) }); }) module.exports = router; UserRoutes.js
Definição das rotas para User - /
Middleware
var jwt = require('jsonwebtoken'); function verifyToken(req, res, next){
var token = req.headers['x-access-token']; if (!token)
return res.status(403).send({auth:false,message:'No token provided.’}); jwt.verify(token, 'TheSecret_123456789', function(err, decoded){
if (err)
return res.status(500).send({auth:false,message:'Failed to authenticate token.'}); req.user = decoded.user; next(); }); } module.exports = verifyToken; VerifyToken.js
Implantação no
Azure com VS Code
Deploy a Node.js Application to Azure
URL:
https://code.visualstudio.com/tutorials/nodejs-deployment/getting-started
Instalar Microsoft CLI 2.0 for Azure
•
URL:
https://aka.ms/InstallAzureCliWindows
Na consola:
Aceder a
https://aka.ms/devicelogin
e colocar o
código
obtido na
consola
C:\> az login
To sign in, use a web browser to open the page https://aka.ms/devicelogin and
enter the code
XXXXXXXXX
to authenticate.
Deploy a Node.js Application to Azure
Iniciar sessão no azure com a
conta pessoal
.
É enviado um objeto JSON para a consola com a seguinte estrutura:
[ {
"cloudName": "AzureCloud",
"id": "8oct2f12-8f0a-412e-99f3-e1405333bfb", "isDefault": true,
"name": "Microsoft Imagine", "state": "Enabled", "tenantId": "84d32234-9d44-407a-9d64-d0b21be94631", "user": { "name": “eu@gmail.com", "type": "user" } } ]
Deploy a Node.js Application to Azure
Criar o sítio Web no Azure
•
Criar Resource Group (só necessário na 1ª publicação)
“A "Resource Group" is essentially a named collection of all our application's resources in Azure. For
example, a Resource Group can contain a reference to a website, a database, and an Azure function.”
•
Configurar Resource Group para ser o grupo utilizado por omissão
•
Criar um “App Service Plan” para definir os recursos físicos utilizados para a
hospedagem (só necessário na 1ª publicação)
C:\> az group create --name myResourceGroup --location westus
Nome do grupo
West US data center
https://azure.microsoft.com/pt-pt/regions/
C:\> az configure --defaults group=myResourceGroup location=westus
C:\> az appservice plan create --name myPlan --sku F1
Deploy a Node.js Application to Azure
Criar o sítio Web no Azure
•
O nome escolhido para o sitio Web terá de ser único uma vez que o acesso será
feito pelo endereço URL
http://nome_escolhido.azurewebsites.net
•
A criação será efetuado pelo comando:
C:\> az webapp create --name nome_escolhido --plan myPlan --runtime "node|6.10"
Tells Azure to use node
version 6.10.x when
running this application
Deploy a Node.js Application to Azure
Deploy do sítio Web
•
Este processo recorre ao
git
e ao
Azure CLI
e implica um push do repositório
para o Azure.
1.
Criar um projeto com ficheiro .gitignore
2.
Copiar o ficheiro .gitignore para a pasta do projeto
3.
Executar na pasta do projeto
C:\> express --git
C:\Proj> git init
C:\Proj> git add -A
Deploy a Node.js Application to Azure
Deploy do sítio Web
4.
Criar um Remote (nome para designar o repositório remoto no Azure)
5.
Fazer o deploy para Azure
6.
Inserir as credenciais de deployment criadas em 4.
C:\Proj> az webapp deployment user set --user-name <user> --password <pass>
C:\Proj> az webapp deployment source config-local-git --name <nome_escolhido>
{
"url": "https://<user>@<nome_escolhido>.scm.azurewebsites.net/<nome_escolhido>.git" }