NodeJS és una plataforma per a desenvolupament web amb JavaScript. Ha arribat a disposar de moltes llibreries que gestionem amb la utilitat NPM.
Express és un framework per a NodeJS que ens facilita la creació d'aplicacions web.
Referències:
$ mkdir myApp
$ cd myApp
package.json
amb:$ npm init
Express
amb $ npm install express
$ node app.js
http://localhost:3000
Necessitem dues maneres de fer anar l'aplicació
Per la primera farem servir el paquet nodemon
, i per la segona el paquet forever
.
$ npm install nodemon $ npm install forever
El paquet nodemon
observa l'estructura de directoris i reinicia el servidor si hi ha canvis, així podem desenvolupar sense la molèstia d'anar reiniciant nosaltres el servidor
El paquet forever
observa el servidor i el reinicia si veu que s'ha aturat o quedat penjat, així sabem que funciona encara que no estiguem vigilant contínuament
Hem de configurar l'arxiu package.json
per poder arrencar el servidor en mode desenvolupament o en mode d'aplicació. Podem fer-ho amb un editor de text afegint scripts
:
"scripts": { "development": "nodemon app.js", "app": "forever app.js" }
L'arxiu package.json
ha de ser semblant a aquest:
{ "name": "myApp", "version": "1.0.0", "description": "", "main": "index.js", "author": "", "license": "ISC", "dependencies": { "express": "^4.18.1", "forever": "^4.0.3", "nodemon": "^2.0.19" }, "scripts": { "development": "nodemon app.js", "app": "forever app.js" } }
Per arrencar l'aplicació quan estem desenvolupant:
$ npm run development
Per arrencar l'aplicació quan funciona normalment:
$ npm run app
Com que desenvolupem, arrencar el servidor amb npm run development
, aleshores editar l'arxiu app.js
per tal que quedi així:
const express = require('express') const app = express() const port = 3000 app.get('/', getHello) async function getHello (req, res) { res.send(`Hello World`) } app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
Ja pots navegar a: http://localhost:3000 i comprovar que el servidor funciona
Les claus d'aquest codi són:
Per afegir direccions URL amb express
cal afegir entrades '.get' com en aquest exemple:
const express = require('express') const app = express() const port = 3000 app.get('/clients', getClients) async function getClients (req, res) { res.send(`Llista de clients`) } app.get('/login', getLogin) async function getLogin (req, res) { res.send(`Entra al sistema`) } app.get('/', getHello) async function getHello (req, res) { res.send(`Hello World`) } app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
Fixa't que cada entrada de '.get' configura una direcció, i que es segueix la preferència segons s'han definit al codi. Ara ja es pot navegar a les diferents web:
Habitualment, els servidors tenen una carpeta de la que es serveixen els arxius tal i com estàn al disc, sense modificacions. D'aquests arxius en diem arxius estàtics
. I normalment aquesta carpeta es diu public
perquè quedi clar que el què conté està publicat a través de la xarxa.
Crea una carpeta anomenada public
i posa-hi dins una foto anomenada foto.jpg
Aleshores afegeix aquesta línia de configuració al codi:
app.use(express.static('public'))
Per tal que quedi semblant a aquest codi:
const express = require('express') const app = express() const port = 3000 app.use(express.static('public')) app.get('/clients', getClients) async function getClients (req, res) { res.send(`Llista de clients`) } app.get('/login', getLogin) async function getLogin (req, res) { res.send(`Entra al sistema`) } app.get('/', getHello) async function getHello (req, res) { res.send(`Hello World`) } app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
Ara pots accedir als arxius de la carpeta public
posant-ne la direcció com si fós una URL:
http://localhost:3000/foto.jpg
Fixa't que la carpeta public
té preferència perquè està configurada la primera, però que les altres direccions continuen funcionant
Les URLs poden contenir paràmetres, i aquests fan que es rebin uns continguts o uns altres. Els paràmetres van al final de la URL darrera d'un símbol de ?
com en aquest exemple:
http://localhost:3000/llistat?cerca=cotxe
L'objecte req
ens dóna informació de la crida que ens fa el client, i part d'aquesta informació són els paràmetres de la URL.
Per veure els paràmetres de la URL i els seus valors, des del codi de Node ho podem fer així:
const express = require('express') const url = require('url') const app = express() const port = 3000 app.get('/llistat', (req, res) => { let query = url.parse(req.url, true).query; if (query.cerca && query.color) { res.send(`Aquí tens el llistat de ${query.cerca} de color ${query.color}`) } else { res.send('Paràmetres incorrectes') } }) app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
Comprova la diferència al navegar a aquestes direccions:
EJS és un sistema de plantilles que ens facilita renderitzar pàgines web. En combinació amb NodeJS i Express ens resulta molt pràctic per separar el codi (processament) del HTML (presentació).
views
.include
.L'objectiu ara serà que la nostra aplicació respongui a les dades que li enviem. Per això necessitem formularis…
La documentació de EJS és una mica escassa d'exemples. Si vols saber si una variable existeix o no dins d'una plantilla EJS NO testejis amb
<% if( var1 ) {...} %
sinó amb
<% if( locals.var1 ) {...} %>
tal i com expliquen en aquest post.
Quan s'envia un formulari HTML les dades es poden adjuntar utilitzant diversos mètodes, els principals són GET i POST (però també hi ha PUT o DELETE).
Les dades enviades per GET
podem accedir-les amb la variable req.query
.
Per exemple, si tenim un formulari amb un
<form action="/checklogin"> <input "name"="username"> ...
les dades s'enviaran a la pàgina checklogin
(action) dins la URL aproximadament així:
http://elmeudomini.com/checklogin?username=manolo
i podrem accedir via req.query.username
.
No cal instal·lar cap complement.
Crea una aplicació «Hello, Manolo» que demani el nom d'usuari en un formulari i que a l'enviar-ho via GET
ens saludi amb «Hello, <nomusuari>».
Per les dades POST
necessitem el package ''body-parser''.
Les dades enviades per POST
es poden accedir amb la variable req.body
.
Instal·la body-parser
amb
npm install body-parser
Tens la documentació oficial del body-parser.
El codi d'exemple ens podria quedar així:
const express = require('express') const app = express() const port = 3000 // setup app.set('view engine', 'ejs'); var bodyParser = require('body-parser') app.use(bodyParser.urlencoded({ extended: false })) // routes app.get('/', (req, res) => { res.render("form.ejs"); // cal crear una plantilla a views/form.ejs }) app.post('/', (req, res) => { res.send("Welcome "+req.body.username); }); // start app app.listen(port, () => { console.log(`Example app listening on port ${port}`) })
radiobutton
al formulari per indicar si el tractament ha de ser «Benvingut» o bé «Benvinguda».
La persistència amb NodeJS, com és d'esperar, es pot fer mitjançant arxius i bases de dades. Hi ha una manera força fàcil d'implementar una persistència senzilla: localStorage
. Aquest mecanisme és propi dels browsers, raó per la qual en NodeJS no està disponible de forma nadiua i caldrà carregar la llibreria node-localstorage.
localStorage
és un sistema de persistència de tipus key-value, cosa que simplifica molt la forma de treballar, tot i que limita les seves features.
Per instal·lar-ho fes
$ npm install node-localstorage
En el fons aquesta llibreria node-localstorage
utilitza arxius (podràs veure les variables emmagatzemades a la carpeta scratch
si segueixes les instruccions de la documentació).
Si vols injectar dades al localStorage
de la teva aplicació pots fer crear arxius a aquesta carpeta, o bé, de forma més «pulida», des de la consola:
$ node > var LocalStorage = require('node-localstorage').LocalStorage; > localStorage = new LocalStorage('./scratch'); > localStorage.setItem("clau","valor");
localStorage
només emmagatzema strings. Si volem posar objectes complexes, com diccionaris o arrays, caldrà utilitzar algun mètode de serialització. Les funcions típiques que s'empren per a això son:
localStorage
.
En principi, NodeJS no hauria de servir arxius estàtics (com imatges, scripts JS de client, etc.), ja que no és molt eficient realitzant aquesta tasca i és millor deixar-la per al servidor web, sigui Apache o Nginx o el què prefereixis.
Tot i això, en alguns casos volem facilitar-nos aquesta funcionalitat fent-ho des de Node i en particular amb Express. Podeu veure com servir arxius estàtics en aquest enllaç.
Per carregar arxius podeu utilitzar el mòdul formidable
de NPM.
Aquí teniu un breu però clar tutorial de formidable, i també podeu mirar la pàgina oficial de formidable a NPM.
localStorage
com a sistema de persistència, tal i com s'explica més amunt.
Més endavant voldrem comprovar des d'una app mòbil si el login i pass és correcte. I per això no volem fer servir un formulari, sinó que volem poder fer una crida a una URL que ens pugui respondre OK o KO i prou, sense HTML.
De fet, la majoria d'APIs funcionen amb format JSON tant per a l'entrada com per a la sortida de dades. Per retornar dades en format JSON, que son els propis de JavaScript, només cal utilitzar res.json(jsonObj)
, per exemple:
app.get('/api/get_user', (req, res) => { res.json( {"username":"manolo"} ); })
Aquesta crida remota és el que acostumem a denominar una «web» API («web» ja que la fem via HTTP).
Per testejar des de la shell fem les crides amb la comanda curl
. En HTTP podem triar entre fer-les per GET o per POST i en diferents formats:
curl http://localhost:5000/api/login -d "user=pepe&password=123"
curl http://localhost:5000/api/logincheck/pepe/123
curl -X POST localhost:5000/api/logincheck -d '{"nom":"pepe", "pass":"123"}' -H "Content-Type: application/json"
Fixa't en què:
app.use(bodyParser.json())
Teniu aquí més exemples de com es fan les crides amb curl.
Express ens permet recollir els paràmetres de la URL de forma molt fàcil amb la variable req.params
.
app.get('/api/user/:id', function (req, res) { userId = req.params.id; userObj = getUser(userId); res.json() })
Recopilant el dit fins ara, per accedir als paràmtres d'entrada de l'usuari amb Express utilitzarem:
req.query
per a les dades GETreq.body
per a les dades POST (amb body-parser)req.params
per a les dades de la URLreq.headers
per a les dades del headers HTTP.
Implementa la ruta /api/logincheck/
, i respon si l'usuari i contrasenya enviats per GET són correctes o incorrectes.
Les aplicacions web que correm en browsers amb JS solen tenir una protecció de seguretat per evitar crides entre diferents orígens de dades. És el que anomenem CORS o Cross-Origin Resource Sharing.
Pots mirar una demo de com funciona CORS a http://ieti.cat/cors/
Utilitza aquest mòdul per habilitar CORS a la nostra flamant API.
Mirant més de prop, podem comprovar si la nostra aplicació està funcionant amb CORS si trobem al seu headers el següent:
Access-Control-Allow-Origin: *
Per veure els headers de la response només cal que feu la crida curl -i
Els mètodes més habituals d'autenticació per a APIs son:
BasicAuth és un sistema molt simple i que no es recomana en entorns de producció. Tot i així, sol utilitzar-se en alguns entorns de test com a mètode simple d'autenticació. Per a NodeJS+express disposem del plugin express-basic-auth, senzill però efectiu per a aquesta tasca.
Per cridar una BasicAuth amb curl fem:
curl -u usuari:contrasenya http://...
Podeu llegir sobre token authentication i les seves bones pràctiques. L'article explica força bé un tipus particular de token auth per al context web anomenat JWT (JSON Web Tokens) que utilitzen un sistema de signatura digital. La única pega que té és que té una certa complicació en la implementació.
Com a referència, aquí tens un tutorial per a JWT amb NodeJS, tingues en compte que utilitza MongoDB (amb mongoose per emmagatzemar els tokens. Utilitza'l només si has entès bé com emprar la signatura digital.
Segueix el tutorial NodeJS Heroku d'aquesta mateixa wiki.
Segueix el tutorial accedint mongodb en què explica com utilitzar MongoDB amb PHP i NodeJS.
El protocol HTTP no és adequat per a temps real ja que no està orientat a connexió, i crea i destrueix el socket en cada request
efectuada al servidor. Això es fa per tal d'alliberar recursos del servidor i no saturar-lo. La contrapartida és que afegeix un overhead de precessament que fa que per demanar dades molt sovint no sigui adequat. Per enviar dades repetidament (com podria ser el cas d'un chat, o millor encara, el moviment d'un joystick) sigui millor mantenir el socket obert.
Una eina adequada per acomplir això és Socket.IO . Recomanable el tutorial inicial per crear un programa de xat que il·lustra molt bé com treballar.