Herramientas de usuario

Herramientas del sitio


cursos:node:start

Servidor RestApi Node con express y mysql

Introducción

Express es una infraestructura de aplicaciones web Node.js mínima y flexible que proporciona un conjunto de características para las aplicaciones web y móviles. Dispone de de miles de métodos de programa de utilidad HTTP y middleware que permiten la creación de una API sólida de forma rápida y sencilla.

Instalación

Para Windows, descargar el instalador. Para linux, podemos instalar la versión que deseemos siguiendo ls pasos de https://github.com/nodesource/distributions

Instalación de express

Suponiendo que ya ha instalado Node.js, creamos un directorio para la aplicación y ejecutamos npm init para crear el fichero package.json, donde se almacenarán las depedencias

$ mkdir miaplicacion
$ cd miaplicacion
$ npm init

npm init solicita varias cosas, como nombre de la aplicación, versión, etc…

Ahora, instalamos Express en el directorio app y lo guardamos en la lista de dependencias.

$ npm install express --save

Con esto, ya podemos utilizar express en nuestra aplicación. Para probarlo podemos ejecutar el siguiente ejemplo: Creamos el fichero hola.js:

var express = require('express');
var app = express();
 
app.get('/', function (req, res) {
  res.send('<a href="/saluda">Saludar</a>');
});
app.get('/saluda', function (req, res) {
    res.send('Hola mundo!');
  });
app.listen(3000, function () {
  console.log('Escuchando en puerto 3000!');
});

y lo ejecutamos con node hola.js

Generador de aplicaciones Express

El ejemplo anterior nos vale como prueba, pero habitualmente desarrollaremos aplicaciones complejas y es muy importante estructurar bien todo el código. Para ello, podemos utilizar el generardor de Express, que permite crear rápidamente un esqueleto de aplicación.

Para instalar el generador :

$ npm install express-generator -g

Ahora generamos un esqueleto de aplicación con el comando express. Por ejemplo, para crear una aplicación express en la carpeta miaplicacionx:

$ express miappexpress 

Podemos ver las opciones del generador con express -h Ahora tenemos que instalar las dependencias:

$ cd miappexpress
$ npm install

Para ejecutar nuestra aplicación: En MacOS o Linux:

$ DEBUG=miappexpress:* npm start

En Windows:

> set DEBUG=miappexpress:* & npm start

Si ha ido too bien, al entrar en http://localhost:3000 en el navegador, debe salir Welcome to Express

Modos de ejecución

Por convención, la variable de entorno NODE_ENV determina el modo de ejecución de node. Es muy importante asignarla correctamente antes de ejecutar node, ya que incide directamente en el rendimiento. Los modos son:

  • development: Modo desarrollo. No se activa caché y en los errores se suele mostrar la traza del error al usuario
  • production: Modo producción. Se activa la caché y no se muestran errores de traza

Podemos acceder al valor del entorno de varias formas, y hacer cambios en nuestra aplicación dependiendo de éste, como por ejemplo, la base de datos a los que nos conectamos, o el nivel de log que queramos mostrar. Una forma es hacerlo mediante el método get('env') de express():

var express=require('express');
var app=express();
 
var modo=app.get('env');
console.log('Arrancando en modo '+modo);

Direccionamiento básico

El direccionamiento hace referencia a la determinación de cómo responde una aplicación a una solicitud de cliente en un determinado punto final, que es un URI (o una vía de acceso) y un método de solicitud HTTP específico (GET, POST, PUT, etc.).

La definición de ruta tiene la siguiente estructura:

app.METHOD(PATH, HANDLER)

Donde:

  • app es una instancia de express.
  • METHOD es un método de solicitud HTTP.
  • PATH es una vía de acceso en el servidor.
  • HANDLER es la función que se ejecuta cuando se correlaciona la ruta.

Ejemplos de definición de rutas simples:

// Responde a una solicitud GET en la ruta raíz con "Hola Mundo"
app.get('/', function (req, res) {  
  res.send('Hola Mundo!');
});
 
//Responde a la solicitud POST en la ruta raíz (/user), habitualmente para crear un usuario:
app.post('/user', function (req, res) {
  res.send('Creando usuario');
});
 
//Responde a una solicitud PUT en la ruta /user/ID, habitualmente para modificar un usuario:
app.put('/user/:id', function (req, res) {
  res.send('Modificando usuario '+req.params.id);
});
 
//Responda a una solicitud DELETE en la ruta /user/ID de usuario, habitualmente para borrar un usuario:
app.delete('/user/:id', function (req, res) {
  res.send('Borrando usuario '+req.params.id);
});

Para ver la guía completa de direccionamiento de express pulsa aquí

Para organizar mejor nuestro código, en lugar de colocar las rutas en app.js, podemos crear un módulo aparte donde las colocaremos todas. routes.js

module.exports = function (app) {
  app.use('/', 		require('./routes/index'));
  app.use('/autores',	require('./routes/autores'));
 
};

En app.js sustituiremos las lineas app.use('/', index) y app.use('/users', users) por la carga del módulo routes y tampoco hará falta cargar los módulos de routes (index,users..etc):

app.js

//var index = require('./routes/index');
//var users = require('./routes/users');
 
//app.use('/', index);
//app.use('/users', users);
require('./routes')(app);

Y en el carpeta routes tendremos los controladores de nuestras rutas: index, user, autores…

Rutas estáticas

El módulo static de express permite dar acceso a ficheros de una determinada carpeta.

app.use(express.static(path.join(__dirname, 'public')));

De esta forma, si en public/images hay un fichero llamado logo.jpg accederemos a él de la forma http://localhost:3000/images/logo.jpg

Utilizamos _dirname para que las rutas sean absolutas, porque por defecto son relativas al directorio desde donde ejecutamos node.

Si tenemos varias rutas públicas, añadiremos una línea por cada ruta:

app.use(express.static(path.join(__dirname, 'public')));
app.use(express.static(path.join(__dirname, 'docs')));

Integración con MySql

Manual de referencia

Instalación:

$ npm install mysql

Para gestionar la conexión a la base de datos, lo más práctico es crear un módulo que establezca la conexión y la exporte y así estará disponible en todos los controladores o modelos. Este módulo puede cargar la configuración de la base de datos de un fichero json en función del modo de ejecución :

db.js

var mysql      = require('mysql');
var express = require ('express');
var app= express();
 
var config= require('./dbconfig.json')[app.get('env')];
 
var db = mysql.createConnection(config);
 
db.connect(function(err) {
  if (err) {
    console.error('Error al conectar BD: ' + err.stack);
    return;
  }
 
  console.log('Conectado a BD ' + db.threadId);
});
 
module.exports=db;

dbconfig.json

{
    "development":{
        "host"     : "www.iesfsl.org",
        "user"     : "2daw",
        "password" : "*****",
        "database" : "test_libros",
        "charset"  : "utf8_spanish_ci"
        },
 
    "production":{
        "host"     : "www.iesfsl.org",
        "user"     : "2daw",
        "password" : "****",
        "database" : "test_ies",
        "charset"  : "utf8_spanish_ci"
    }
}

Ejecutar consultas y devolver datos

En cualquier controlador o modelo podemos acceder al módulo db para ejecutar consultas:

routes/autores.js

var express = require('express');
var router = express.Router();
var db= require('../db');
 
/* GET devuelve autores. */
router.get('/', function(req, res, next) {
  db.query('select * from autores where id<1000',function(error,result,fields){
    if(error!==null)
      res.send(error);
    else
      res.json(result)
  });
 
});
 
 
module.exports = router;

Parser de datos de la petición (REQUEST body)

Para acceder de una forma cómoda a los datos de peticiones POST, PUT y DELETE, utilizaremos el módulo bodyParser. En app.js:

var bodyParser = require('body-parser');
...
...
var app=express();
...
 
// Si se envian los datos como application/x-www-form-urlencoded
app.use(bodyParser.urlencoded({ extended: false }))
// Si se envian en json
// parse application/json
app.use(bodyParser.json());

Escape de variables para evitar inyección SQL

Debemos evitar la inyección SQL siempre, montando la consulta con parámetros ? y pasando los valores como parámetros:

/* GET devuelve un autor */
router.get('/:id', function(req, res, next) {
  db.query('select * from autores where id=?',[req.params.id],
  function(error,result,fields){
    if(error!==null || !result.length)
      res.send(error);
    else
      res.json(result[0])
  }); 
}); 

Una alternativa es utilizar el método escape() de la conexión para cada variable:

/* GET devuelve un autor*/
router.get('/:id', function(req, res, next) {
  db.query('select * from autores where id='+db.escape(req.params.id),
  function(error,result,fields){
    if(error!==null || !result.length)
      res.send(error);
    else
      res.json(result[0])
  }); 
}); 
/* PUT actualiza un autor */
router.put('/:id',function(req, res, next) {
 
  db.query('update autores set nombre=? where id=?',
    [req.body.nombre,req.params.id],
  function(error,result,fields){
    if(error!==null) 
      res.json({'status':1,'error':error});
    else 
      if(result.affectedRows!=1)
        res.json({'status':404}); //No existe
      else
        res.json({'status':0});
  });
}); 
/* DELETE borra un autor */
router.delete('/:id',function(req, res, next) {
 
  db.query('delete from autores where id=?',[req.params.id],
  function(error,result,fields){
    if(error!==null) 
      res.json({'status':1,'error':error});
    else 
      if(result.affectedRows!=1)
        res.json({'status':404}); //No existe
      else
        res.json({'status':0});
  });
});

Acceso remoto

Puede ser interesante habilitar el bloqueo Cross-domain (CORS) en nuestro servidor, filtrando los accesos como deseemos. Para ello, utilizaremos el middleware cors.

npm install cors

En app.js, habilitaremos corsa, antes de la carga de las rutas. Se puede hacer de forma global, para unas rutas en particular, para una serie de hosts remotos, etc.. Pincha aquí para ver todas las opciones.

var cors = require('cors')
var app = express();
 
app.use(cors());

Autenticación

A diferencia de PHP, en Node no trabajamos con sesiones, pero necesitamos disponer de un mecanismo de bloquear el acceso a determinadas rutas a usuarios no identificados de alguna forma. Una forma típica de bloquear el acceso a determinadas rutas de nuestra aplicación, de forma que solamente puedan acceder los usuarios “identificados” consiste en usar un token (Bearer tokn) que ha de tener asociado cada usuario en su cuenta. El usuario ha de enviar ese token en cada petición, y si no es correcto, no se le permite el acceso. La forma de gestionarlo es la siguiente:

  • El cliente ejecuta una petición POST a /auth (por ejemplo), enviando usuario y password

routes.js

module.exports = function (app) {
  app.use('/', 		require('./routes/index'));
  app.use('/auth',	require('./routes/auth'));
  app.use('/autores',	require('./routes/autores'));
 
};

routes/auth.js

var express=require('express');
 
var router = express.Router();
var db= require('../db');
 
/* valida usuario y contraseña y devuelve  token. */
router.post('/', function(req, res, next) {
  db.query('select id,nombre,token from usuarios where usuario=? and password=md5(?)',
        [req.body.usuario,req.body.password],function(error,result,fields){
    if(error!==null)
      res.send({"status":1,"error":error});
    else 
      if(!result.length)
        res.send({"status":2,"error":"Usuario inexistente"});
      else
        res.json({"status":0,"user":result[0]});
  });
 
});
 
module.exports = router;
  • Si son correctos, el servidor responde con los datos del usuario incluyendo el token:
{
    "status": 0,
    "user": {
        "id": 2,
        "nombre": "Cecilio Albero",
        "token": "c81e728d9d4c2f636f067f89cc14862c"
    }
}
  • El cliente almacena el token y lo utiliza en las sucesivas peticiones, enviando en el Header una entrada de tipo Authentication con el texto “Bearer c81e728d9d4c2f636f067f89cc14862c”
  • El servidor lee ese token y lo comprueba en cada petición, denegando el acceso si no es correcto

Una forma sencilla de implementar este mecanismo de validación del token es crear nuestro propio middleware mediante un módulo que llamaremos checkAuth:

checkAuth.js

var db=require('./db');
 
// Comprueba Bearer token
module.exports=function (req, res, next) {
    var bearerToken;
    var bearerHeader = req.headers["authorization"];
    if (typeof bearerHeader !== 'undefined') {
        var bearer = bearerHeader.split(" ");
        bearerToken = bearer[1];
        console.log("Identificando "+bearerToken);
        db.query('select id,nombre from usuarios where token=?',[bearerToken], 
            function(error, result,fields) {
 
                if (error!==null || !result.length) 
                    res.sendStatus(403,"Error de acceso");
                else {
                    req.user = result[0]; // Guardamos los datos del usuario en request, para poder acceder después
                    console.log("Usuario: " + result[0].nombre);
                    next();
                }
        });
 
 
    } else {
        console.log("Acceso sin token");
        res.send(403).body("Acceso sin token no permitido");
    }
}

Y ahora, en las acciones de los controladores donde deseemos bloquear el acceso sin token, incluiremos checkAuth en la declaración de la ruta, y si todo es correcto, en req.user tendremos los datos del usuario que ha enviado la petición, para aplicar cualquier restricción de acceso o filtro de acceso a base de datos:

routes/autores.js

var checkAuth= require('../checkAuth');
 
...
router.get('/autores', checkAuth, function(req, res, next) {
  console.log("El usuario que ha accedido tiene el código "+req.user.id);
  ...
 
});

Ampliaciones

SQL Builders

En lugar de utilizar el módulo sql, podemos utilizar Knex, un módulo para construir SQL de forma intuitiva y potente, que añade promises, streams y muchas cosas más al acceso estándar

Uso de ORM

Podemos utilizar un ORM y crear modelos de nuestras tablas de base de datos, en lugar de acceder a la base de datos directamente desde los controladores, de forma similar a como hacemos en Yii2 con los modelos.

Las ventajas de utilizar un ORM son:

  • Eliminan el trabajo tedioso de escribir SQL “a pelo”
  • Facilitan los accesos con datos relacionados
  • Permite crear atributos virtuales y modificar fácilmente la estructura de datos que se devuelve al usuario

Las desventajas son:

  • No tenemos control completo respecto a la forma en que se accede a la base de datos
  • Implican una pérdida de rendimiento

Los ORM para Node más utilizados son: