[Node.js] Creando una API REST con Node.js en 5 minutos

Como de vez en cuando me da por hacer locuras, y hacía tiempo que no escribía aquí, voy a contar tanto la última locura como el último descubrimiento que he hecho. Crear una API REST utilizando Node.js + restify + mysql.

Preludio

Hasta ahora siempre que habia necesitado hacer algo parecido, me lo había montado yo mismo con scripts PHP y peticiones POST con un campo/variable, o había hecho alguna prueba con algún framework (Slim), pero encontré este módulo para Node.js y me entró la curiosidad por probarlo.

Instalación

Partiendo de que el servidor sobre el que se ejecutará la API tenga correctamente instalado Node.js, crearemos una nueva carpeta en la cual montaremos el proyecto, y haremos:

npm install restify
npm install mysql

Nota: En el caso de que os de error de compilación, aseguraos de tener los compiladores gcc y g++ instalados.

Preparación previa

Antes de empezar a montar la API, crearemos una nueva DB en MySQL, a la cual en este ejemplo llamaremos PruebasDB.
Dentro, crearemos una tabla llamada Usuarios, en la que tendremos los campos Nombre (TEXT), Apellido (TEXT), Correo (TEXT), ID (INTEGER, PRIMARY, UNIQUE, AUTOINCREMENTAL).
Como veis, una estructura muy simple pero que servirá perfectamente para ejemplificar los conceptos más básicos de restify.

Al turrón

Bien, ya tenemos los módulos instalados, la DB creada... Vamos a por el código!

Todo se ejecutará dentro del archivo principal, llamado app.js, así que lo abrimos y empezamos:

var restify = require('restify');
var mysql = require('mysql');
connection = mysql.createConnection({
               host : 'localhost',
               user : 'nuestroUsuarioDeMySQL',
               password : 'nuestraContraseñaDeMySQL',
               database: 'PruebasDB'
         });

Con esto inicializamos la conexión a MySQL y cargamos el módulo de restify. Ahora vamos a crear el servidor:

var ip_addr = '127.0.0.1';
var port    =  '1234';
 
var server = restify.createServer({
    name : "usuarios"
});

server.use(restify.queryParser());
server.use(restify.bodyParser());
server.use(restify.CORS());
server.listen(port ,ip_addr, function(){
    console.log('%s activo en %s ', server.name , server.url);
});

Como vemos, definimos que el servidor se ejecute en localhost (127.0.0.1) y escuche en el puerto 1234 (si, pasado como String).

Bien, hasta aquí tenemos la inicialización de nuestro servidor. Ahora vamos a definir el comportamiento que deberá tener con cada tipo petición:

var PATH = '/usuarios'
server.get({path : PATH , version : '0.0.1'} , findAllUsers);
server.get({path : PATH +'/:userId' , version : '0.0.1'} , findUser);
server.post({path : PATH , version: '0.0.1'} , postNewUser);
server.del({path : PATH +'/:userId' , version: '0.0.1'} , deleteUser);

Vamos a explicar un poquito lo que acabamos de escribir:

  1. Cuando el usuario haga una petición de tipo GET a localhost:1234/usuarios, recibirá como respuesta una lista de todos los usuarios y su contenido
  2. Cuando el usuario haga una petición de tipo GET a localhost:1234/usuarios/numeroDeUsuario, recibirá como respuesta el contenido de ese usuario
  3. Cuando el usuario haga una petición de tipo POST (le pasará un JSON) a localhost:1234/usuarios, se creará un nuevo usuario en la BD
  4. Cuando el usuario haga una petición de tipo DELETE a localhost:1234/usuarios/numeroDeUsuario, se eliminará el usuario correspondiente en la BD

La estructura, como podéis ver, es:

server.VERBO({path: PATH , version : '0.0.1'}, funcionQueSeEjecutara);

El parámetro indicado como :userId será un parámetro recuperable dentro de la función, como veremos posteriormente

Ahora vamos a por las funciones:

Lista de todos los usuarios

Empezamos por la más sencilla, la que devuelve la lista de todos los usuarios:

function findAllUsers(req, res, next){
    connection.query('SELECT * FROM Usuarios', function (error, results){
      if(error) throw error;
      //console.log(results);
      res.send(200, results);
      return next();
  });
}

Aunque el código requiere de poca explicación, vamos a darla igualmente:
La función recibe 3 parámetros, request, response, y next. En esta función no vamos a interactuar demasiado con ellos.
En cuanto la función sea llamada, realizará la consulta a la base de datos SELECT * FROM Usuarios, devolviendo así todo el contenido de la tabla.
Como la función connection.query es asíncrona, tendrá un callback el cual se ejecutará una vez la consulta termine, recibiendo como parámetros error y results

  • error: en caso de que no sea nulo, se lanza el error
  • results: el resultado de la consulta a la BD

Al no necesitar más tratado de datos, directamente escribimos el resultado en la respuesta por parte del servidor, y le devolvemos un código HTTP 200 (OK)
NOTA: He comentado el console.log(results), pero lo podéis descomentar si queréis observar también los resultados en la consola, o por si en la llamada a la aplicación deseáis guardar un log de la actividad de la API.

Parámetros de un usuario concreto

Vamos ahora a utilizar un parámetro pasado en la petición GET, el definido anteriormente como :userId
La función de nuevo recibe 3 parámetros, request, response, y next:

function findUser(req, res, next){
    connection.query('SELECT * FROM Usuarios WHERE ID='+req.params.userId, function(error, results){
     	if(error) throw error;
        //console.log(results);
        res.send(200, results);
        return next();
    });
}

Es un método muy parecido al anterior, solo que en esta ocasión filtramos la consulta y mostramos sólo el usuario que hemos recibido en la petición de tipo GET.
Como podemos ver, el parámetro que definimos en el comportamiento del servidor, lo recibimos aquí extrayéndolo de req.params.

Inserción de un nuevo usuario

Para insertar un nuevo usuario en la BD, recibiremos una petición de tipo POST, la cual contendrá un JSON con toda la información necesaria, y nosotros nos encargaremos de meter manualmente los datos recibidos cada uno en su columna.
Para ello lo primero que haremos será crearnos un array vacío e irle metiendo el contenido, para después recoger de ahí los datos para introducirlos.

function postNewUser(req , res , next){
    var user = {};
    user.Nombre = req.params.Nombre;
    user.Apellido = req.params.Apellido;
    user.Correo = req.params.Correo;

    connection.query('INSERT INTO Usuarios (Nombre, Apellido, Correo) VALUES (\''
        +user.Nombre+'\', \''
        +user.Apellido+'\', \''
        +user.Correo+'\', \')'
        , function (error, success){
            if(error) throw error;
            console.log(success);
            res.send(200, success.insertId);
        }
    );
}

Además, esta función, aparte de devolver el código HTTP 200 (OK), devuelve el parámetro insertId, el cual es el valor que toma el campo ID de la base de datos.

Como podemos imaginar, el JSON enviado tendrá la estructura

{
  "Nombre":"MiNombre",
  "Apellido":"MiApellido",
  "Correo":"estoes @uncorreo.com"
}

Desde el objeto req recibiremos todos los campos ya parseados, y solo tendremos que recuperarlos mediante req.params.NombreCampo, y lo guardaremos en su campo correspondiente dentro del elemento user.

Eliminación de un usuario concreto

Finalmente vamos a contemplar la posibilidad de que se quiera borrar un usuario concreto de la BD.
Para ello tenemos que hacer uso del tercer verbo de HTTP, el DELETE.
Siguiendo un esquema muy muy parecido al de recuperar la información de un usuario concreto, ejecutaremos el siguiente código:

function deletePref(req , res , next){
    connection.query('DELETE FROM Usuarios WHERE ID = '+req.params.userId, function (error, success){
        if(error) throw error;
        res.send(200, 'Eliminado con exito');
    }); 
}

Como vemos, una función muy muy simple.
Ahora solo quedaría ejecutar la aplicación con el comando node app.js, y ya tendríamos iniciado el servidor, listo para recibir peticiones.

Es posible que en un par de días escriba otro post sobre como ampliar esto, y como recibir peticiones desde el exterior, enlazándolo con nginx mediante un upstream.

Espero que os haya gustado y que os animéis a probarlo!

PD: Por si hay algún problema, aquí va el código definitivo:
http://pastebin.com/YjFQHy33