logoCoderhouse.png
Ë
By Oscar Lopez • April 21, 2016

Una simple API con Hapi y node.js

Una API( Application Interface ) es una forma de exponer tus servicios a clientes que pueden ser de cualquier tipo. Cuando creamos una API tenemos que hacernos ciertas preguntas como quién consumirá tus servicios, con qué frecuencia, qué permisos debo manejar, que límites debo otorgar, como mido la performance de mi API, cómo documento para que consuman correctamente los servicios, qué tipos de errores manejo, qué tipos de respuesta manejo y monitorización del sistema, entre otras.

Luego viene la parte donde se definen los endpoints (rutas que permiten acceder al servicio) como por ejemplo /autos, /users, etc.
Es clave definir los endpoints correctamente, porque una vez que los clientes empiezan a consumir tu servicio, es muy costoso cambiar los nombres o parámetros.

En nuestro caso vamos a desarrollar una API utilizando node.js y el framework Hapi.

Para hacer nuestro proyecto es necesario contar con las siguientes tecnologías instaladas.

node.js
hapi
mongodb

La idea es simple, vamos a generar una serie de endpoints con hapi y estos tendrán acceso a una base de datos con mongodb. Para la documentación vamos a utilizar swagger con un plugin de hapi, de manera tal que se genera automáticamente la documentación de nuestra API.

Dicho todo esto, comencemos!!!

Creamos un repo en git (no es obligatorio para la API pero ya que estamos.)

https://github.com/lopezoscar/coderhouse-simple-api

Clonamos el repo

<code>git clone https://github.com/lopezoscar/coderhouse-simple-api</code>
<code>cd coderhouse-simple-api</code>
<code>npm init</code>

Hay que completar los datos que pide el script (si presionan siempre enter autocompleta) y luego aparecerá algo como esto

c1

Esto generará el archivo package.json que sirve para describir nuestro proyecto, en este archivo se incluirán todas las dependencias necesarias.

Luego hay que instalar las dependencias necesarias para la API.

<code>npm install --save hapi good inert vision hapi-swagger good-console</code>

Si abren el archivo package.json en la key dependencias estarán listadas todas las librerías con sus versiones.

c2

Ahora que están las dependencias necesarias para la API, procederemos a crear el archivo app.js y empezamos a codear en ES6 !!!

Declaramos todas las dependencias


"use strict";
const Hapi = require("hapi");
//Hapi Plugins
const Good = require('good');
const Inert = require('inert');
const Vision = require('vision');
const HapiSwagger = require('hapi-swagger');

Creamos el server


const server = new Hapi.Server();

Seteamos el host y puerto


server.connection({
host: 'localhost',
port: 8000
});

const options = {
info: {
'title': 'Reservalia API Documentation',
'version': "v1.0.0"
}
};

Luego llamamos a server.register que recibe todos los plugins de hapi que quieras instalar junto con sus respectivas configuraciones. Cada plugin tiene un ejemplo asociado en la página de hapi que te permite implementarlo.


server.register([
Inert,
Vision,
{
'register': HapiSwagger,
'options': options
},
{
register: Good,
options: {
reporters: [{
reporter: require('good-console'),
events: {
response: '*',
log: '*'
}
}]
}
}], (err) => {

if (err) {
throw err; // something bad happened loading the plugin
}
//Start the server
server.start((err) => {
if (err) {
throw err;
}
server.log('info', 'Server running at: ' + server.info.uri);
});
});

Ahora nos falta lo más importante: los endpoints. Sin ellos la API no tendría sentido.

Para crear un endpoint es necesario utilizar el método server.route

Vamos a crear una nueva carpeta llamada routes

<code>mkdir routes </code>

Luego crearemos un archivo hotels.js que contendrá los endpoints asociados a los servicios para el model/entidad hoteles.


"use strict";

const Joi = require('joi');
const hotels = require("../lib/hotelsDB.js");

function hotels(server){
server.route({
method: 'GET',
path: '/api/v1/hotels',
config: {
plugins: {
'hapi-swagger': {
responses: {'400': {'description': 'Bad Request'},'200':{'description':'ok'}},
payloadType: 'json'
}
},
validate:{
query: {
limit: Joi.number().required().min(1).max(100).integer().positive().description('Page Limit between 1 and 100'),
offset: Joi.number().required().min(0).max(100).integer().description('Pagination offset. '),
}
},
tags: ['api']
},
handler: function (req, reply) {
let params = {
limit: typeof req.query.limit != "undefined" ? req.query.limit : 0,
offset: typeof req.query.offset != "undefined" ? req.query.offset : 0
};
hotelsDB.getHotels(params,(err,hotel) => {
if(err)
return reply(err);
return reply(hotel);
});
}
});
}

module.exports = hotels;

Vayamos de a paso.


server.route({
method: 'GET',
path: '/hotels'
});

Esto indica que tipo de método HTTP permite acceder a este servicio. El path es lo que llamamos endpoint.


server.route({
method: 'GET',
path: '/hotels',
handler: function (req, reply) {
let params = {
limit: typeof req.query.limit != "undefined" ? req.query.limit : 0,
offset: typeof req.query.offset != "undefined" ? req.query.offset : 0
};
hotelsDB.getHotels(params,(err,hotel) => {
if(err)
return reply(err);
return reply(hotel);
});
}
});

Luego es necesario setear una propiedad handler que recibe un objeto request y un objeto reply que corresponde al response.
Cuando quiero acceder a propiedades como los headers, path params, query params, url utilizo el objeto req y cuando quiero responder el request http utilizo reply(//Lo que voy a responder).

El contenido del handler en este momento no va a funcionar porque faltan propiedades.


server.route({
method: 'GET',
path: '/api/v1/hotels',
config: {
plugins: {
'hapi-swagger': {
responses: {'400': {'description': 'Bad Request'},'200':{'description':'ok'}},
payloadType: 'json'
}
},
validate:{
query: {
limit: Joi.number().required().min(1).max(100).integer().positive().description('Page Limit between 1 and 100'),
offset: Joi.number().required().min(0).max(100).integer().description('Pagination offset. ')
}
},
tags: ['api']
},
handler: function (req, reply) {
let params = {
limit: typeof req.query.limit != "undefined" ? req.query.limit : 0,
offset: typeof req.query.offset != "undefined" ? req.query.offset : 0
};
hotelsDB.getHotels(params,(err,hotel) => {
if(err)
return reply(err);
return reply(hotel);
});
}
});

Ahora agregamos la propiedad config, que sirve para indicar opciones para los plugins, como por ejemplo el de swagger y también algo que es muy importante una propiedad llamada validate que es la que define que path params query params tendrá que validar y también aparecerán en la documentación de swagger.

Dentro de la propiedad validate aparece query params y también podrían aparecer params para los path params. Para validar los query params y path params utilizamos Joi, que es un plugin de hapi que te permite crear un schema para validaciones. Por ejemplo, yo puedo hacer lo siguiente.

<code>{ limit: Joi.number().required().min(1).max(100).integer().positive().description('Page Limit between 1 and 100') }</code>

Este código indica que la property limit tiene que ser un número, es obligatoria, mínimo es 1 y máximo es 100, tiene que ser entero positivo y tiene una descripción que es la que va a aparecer en la documentación de la API.

Para instalar joi

<code>npm install --save joi</code>

Ahora que tenemos el endpoint armado, faltaría incluir lógica y el acceso a la base de datos.

Para ello crearemos una carpeta lib

<code>mkdir lib</code>

y dentro de ella crearemos un archivo llamado hotelsDB.js

hotelsDB.js accederá a una base de datos de mongodb. Para ello utilizaremos la lib mongojs

<code>npm install --save mongojs</code>

Luego en el archivo hotelsDB.js


"use strict";

var db = require("mongojs")("localhost/specialdom",["hotels"]);

function HotelsDB(){
return {
getHotels(params,cb){
db.hotels.find({}).skip(params.offset).limit(params.limit,cb);
}
}
}

module.exports = new HotelsDB();

Ahora nos falta un solo paso que es agregar el routes/hotels.js al app.js para pasarle el objeto server


require("./routes/hotels.js")(server);

Listo. Ya tenemos la api.

Para ver los servicios, vamos a la terminal y colocamos

<code>node app.js</code>

Ingresamos en el navegador: localhost:8000/documentation y deberían ver algo como esto.

c3

En la db puedo insertar cualquier tipo de objeto en este caso porque no estamos filtrando por nada. Si quisieramos mejorar la estructura de la API podríamos agregar una capa de controllers donde se procesarían parámetros para ordenamiento, filtros, etc. También podríamos agregar plugins para authentication, Api Keys, Monitorización con New Relic.

En este repo tienen todo el proyecto, se lo baja y ejecutan

<code>npm install</code>

Nos vemos!!!