Introducción a Generators & Koa.js: Parte 2
Spanish (Español) translation by Elías Nicolás (you can also view the original English article)



Bienvenido a la segunda parte de nuestra serie sobre generadores y Koa. Si te lo perdiste, puedes leer la parte 1 aquí. Antes de comenzar con el proceso de desarrollo, asegúrese de haber instalado Node.js 0.11.9 o superior.
En esta parte, crearemos una API de diccionario usando Koa.js, y aprenderá sobre el enrutamiento, la compresión, el registro, la limitación de velocidad y el manejo de errores en Koa.js. También utilizaremos Mongo como nuestro almacén de datos y aprenderemos brevemente sobre la importación de datos en Mongo y la facilidad que conlleva la consulta en Koa. Finalmente, veremos cómo depurar las aplicaciones de Koa.
Comprender el Koa
Koa tiene cambios radicales
construidos bajo su capó que aprovechan la bondad del generador de ES6.
Además
del cambio en el flujo de control, Koa presenta sus propios objetos
personalizados, como this, this.request y this.response, que actúan
convenientemente como una capa de azúcar sintáctico construida sobre los
objetos de req y res de Node, dándote acceso a varios métodos de
conveniencia y getters / setters.
Además de la comodidad, Koa también limpia el middleware que, en Express, dependía de hacks feos que a menudo modificaban objetos centrales. También proporciona un mejor manejo de flujo.
Espera, ¿qué es un middleware?
Un middleware es una función conectable que agrega o elimina una pieza particular de funcionalidad al hacer algún trabajo en los objetos de solicitud / respuesta en Node.js.
El middleware de Koa
Un middleware Koa es esencialmente una función de generador que devuelve una función de generador y acepta otra. Por lo general, una aplicación tiene una serie de middleware que se ejecutan para cada solicitud.
Además, un middleware debe ceder el paso al siguiente middleware 'downstream' si es ejecutado por un middleware 'upstream'. Discutiremos más sobre esto en la sección de manejo de errores.
Creando un Middleware
Solo
una última cosa: para agregar un middleware a su aplicación Koa, usamos
el método koa.use () y proporcionamos la función de middleware como
argumento. Ejemplo: app.use(koa-logger) agrega koa-logger a la lista de
middleware que usa nuestra aplicación.
Construyendo la aplicación
Para
comenzar con la API de diccionario, necesitamos un conjunto de
definiciones que funcione. Para recrear este escenario de la vida real,
decidimos ir con un conjunto de datos real. Tomamos el volcado de
definición de Wikipedia y lo cargamos en Mongo. El conjunto consistió en
aproximadamente 700,000 palabras ya que solo importamos el volcado
inglés. Cada registro (o documento) consiste en una palabra, su tipo y
su significado. Puede leer más sobre el proceso de importación en el
archivo import.txt en el repositorio.
Para avanzar en el proceso de desarrollo, clone el repositorio y verifique su progreso cambiando a diferentes compromisos. Para clonar el repositorio, use el siguiente comando:
1 |
$ git clone https://github.com/bhanuc/dictapi.git
|
Podemos comenzar creando un servidor base Koa:
1 |
var koa = require('koa'); |
2 |
var app = koa(); |
3 |
|
4 |
app.use(function *(next){ |
5 |
this.type = 'json'; |
6 |
this.status = 200; |
7 |
this.body = {'Welcome': 'This is a level 2 Hello World Application!!'}; |
8 |
});
|
9 |
|
10 |
if (!module.parent) app.listen(3000); |
11 |
console.log('Hello World is Running on http://localhost:3000/'); |
12 |
En la primera línea, importamos Koa y guardamos una instancia en la variable de la aplicación. Luego agregamos un solo middleware en la línea 5, que es una función de generador anónimo que toma la siguiente variable como parámetro. Aquí, establecemos el tipo y el código de estado de la respuesta, que también se determina automáticamente, pero también podemos establecerlos manualmente. Entonces finalmente establecemos el cuerpo de la respuesta.
Como
hemos establecido el cuerpo en nuestro primer middleware, esto marcará
el final de cada ciclo de solicitud y no se involucrará ningún otro
middleware. Por último, iniciamos el servidor llamando a su método listen y transmitimos el número de puerto como parámetro.Podemos
iniciar el servidor ejecutando el script a través de:
Podemos iniciar el servidor ejecutando el script a través de:
1 |
$ npm install koa |
2 |
$ node --harmony index.js |
Puede llegar directamente a esta etapa moviéndose para confirmar 6858ae0:
1 |
$ git checkout 6858ae0
|
Agregar capacidades de enrutamiento
El enrutamiento nos permite redirigir diferentes solicitudes a
diferentes funciones en función del tipo de solicitud y la URL. Por
ejemplo, es posible que deseemos responder a /login de forma
diferente que signup. Esto
se puede hacer agregando un middleware, que verifica manualmente la URL
de la solicitud recibida y ejecuta las funciones correspondientes. O
bien, en lugar de escribir manualmente ese middleware, podemos usar un
middleware hecho en la comunidad, también conocido como un módulo de
middleware.
Para agregar capacidad de enrutamiento a nuestra aplicación,
utilizaremos un módulo de comunidad denominado koa-router.
Para usar
koa-router, modificaremos el código existente al código que se muestra a
continuación:
1 |
var koa = require('koa'); |
2 |
var app = koa(); |
3 |
var router = require('koa-router'); |
4 |
var mount = require('koa-mount'); |
5 |
|
6 |
var handler = function *(next){ |
7 |
this.type = 'json'; |
8 |
this.status = 200; |
9 |
this.body = {'Welcome': 'This is a level 2 Hello World Application!!'}; |
10 |
};
|
11 |
|
12 |
var APIv1 = new router(); |
13 |
APIv1.get('/all', handler); |
14 |
|
15 |
app.use(mount('/v1', APIv1.middleware())); |
16 |
if (!module.parent) app.listen(3000); |
17 |
console.log('Hello World is Running on http://localhost:3000/'); |
18 |
Aquí hemos
importado dos módulos, donde el router almacena koa-router y mount almacena el módulo koa-mount, lo que nos permite usar el enrutador en
nuestra aplicación Koa.
En la línea 6, hemos
definido nuestra función handler, que es la misma función que
antes, pero aquí le hemos asignado un nombre. En la línea
12, guardamos una instancia del enrutador en APIv1, y en la línea 13
registramos nuestro manejador para todas las solicitudes GET en ruta /all.
Entonces, todas las solicitudes excepto cuando se envía una
solicitud get a localhost:3000/all devolverán "no encontrado".
Finalmente
en la línea 15, utilizamos el middleware mount, que proporciona
una función de generador utilizable que se puede alimentar a app.use().
Para llegar directamente a este paso o comparar su aplicación, ejecute el siguiente comando en el repositorio clonado:
1 |
$ git checkout 8f0d4e8
|
Antes de ejecutar nuestra aplicación, ahora necesitamos instalar koa-router y koa-mount usando npm. Observamos que a medida que aumenta la complejidad de nuestra aplicación, la cantidad de módulos / dependencias también aumenta.
Para
realizar un seguimiento de toda la información relacionada con el
proyecto y poner esos datos a disposición de npm, almacenamos toda la
información en package.json, incluidas todas las dependencias. Puede
crear package.json manualmente o utilizando una interfaz de línea de
comando interactiva que se abre utilizando el comando $ npm init.
1 |
{
|
2 |
"name": "koa-api-dictionary", |
3 |
"version": "0.0.1", |
4 |
"description": "koa-api-dictionary application", |
5 |
"main": "index", |
6 |
"author": { |
7 |
"name": "Bhanu Pratap Chaudhary", |
8 |
"email": "bhanu423@gmail.com" |
9 |
},
|
10 |
"repository": { |
11 |
"type": "git", |
12 |
"url": "https://github.com/bhanuc/dictapi.git" |
13 |
},
|
14 |
"license": "MIT", |
15 |
"engines": { |
16 |
"node": ">= 0.11.13" |
17 |
}
|
18 |
}
|
19 |
Un archivo package.json muy minimalista se parece al anterior.
Una vez que package.json está presente, puede guardar la dependencia usando el siguiente comando:
1 |
$ npm install <package-name> --save |
Una vez que package.json está presente, puede guardar la dependencia usando el siguiente comando:
1 |
$ npm install koa-router koa-mount --save |
Ahora puede ejecutar la aplicación usando $ node --harmony index.js.
Puede leer más sobre package.json aquí.
Agregar rutas para la API de diccionario
Comenzaremos por crear dos rutas para la API, una para obtener un único resultado en una consulta más rápida y otra para obtener todas las palabras coincidentes (que es más lenta por primera vez).
Para
que todo sea manejable, guardaremos todas las funciones de API en una
carpeta separada llamada api y un archivo llamado api.js, e importaremos
más tarde en nuestro archivo index.js principal.
1 |
var monk = require('monk'); |
2 |
var wrap = require('co-monk'); |
3 |
var db = monk('localhost/mydb'); |
4 |
var words = wrap(db.get('words')); |
5 |
/**
|
6 |
* GET all the results.
|
7 |
*/
|
8 |
exports.all = function *(){ |
9 |
if(this.request.query.word){ |
10 |
var res = yield words.find({ word : this.request.query.word }); |
11 |
this.body = res; |
12 |
} else { |
13 |
this.response.status = 404; |
14 |
}
|
15 |
};
|
16 |
/**
|
17 |
* GET a single result.
|
18 |
*/
|
19 |
exports.single = function *(){ |
20 |
if(this.request.query.word){ |
21 |
var res = yield words.findOne({ word : this.request.query.word }); |
22 |
this.body = res; |
23 |
} else { |
24 |
this.response.status = 404; |
25 |
}
|
26 |
};
|
Aquí
estamos usando co-monk, que actúa como envoltorio alrededor de monk, lo
que nos facilita la consulta de MongoDB usando generadores en Koa. Aquí,
importamos monk y co-monk, y nos conectamos a la instancia de MongoDB
en la línea 3. Llamamos a wrap() en colecciones, para que sean
compatibles con el generador.
A continuación,
agregamos dos métodos de generador denominados all y single como una
propiedad de la variable exports para que puedan importarse en
otros archivos. En cada una de las funciones, primero verificamos el
parámetro de consulta 'palabra'. Si está presente, consultamos el
resultado o respondemos con un error 404.
Usamos
la palabra clave yield para esperar los resultados como se discutió en
el primer artículo, que pausa la ejecución hasta que se reciba el
resultado. En la línea 12, usamos el método find,
que devuelve todas las palabras coincidentes, que se almacena en res y
luego se envía de regreso. En la línea 23, usamos el método findOne
disponible en la colección, que devuelve el primer resultado
coincidente.
Asignación de estos controladores a las rutas
1 |
var koa = require('koa'); |
2 |
var app = koa(); |
3 |
var router = require('koa-router'); |
4 |
var mount = require('koa-mount'); |
5 |
var api = require('./api/api.js'); |
6 |
|
7 |
var APIv1 = new router(); |
8 |
APIv1.get('/all', api.all); |
9 |
APIv1.get('/single', api.single); |
10 |
|
11 |
|
12 |
app.use(mount('/v1', APIv1.middleware())); |
13 |
if (!module.parent) app.listen(3000); |
14 |
console.log('Dictapi is Running on http://localhost:3000/'); |
Aquí
importamos métodos exportados desde api.js y asignamos manejadores a
rutas GET /all /single y tenemos una API y una aplicación totalmente
funcionales listas.
Para ejecutar la aplicación, solo necesita instalar los módulos monk y co-monk utilizando el siguiente comando. Además,
asegúrese de tener una instancia en ejecución de MongoDB en la que haya
importado la colección presente en el repositorio de git siguiendo las
instrucciones mencionadas en import.txtweird.
1 |
$ npm install monk co-monk --save |
Ahora puede ejecutar la aplicación usando el siguiente comando:
1 |
$ node --harmony index.js |
Puede abrir el navegador y abrir las siguientes URL para verificar el funcionamiento de la aplicación. Simplemente reemplace 'nuevo' con la palabra que desea consultar.
http://localhost:3000/v1/all?word=newhttp://localhost:3000/v1/single?word=new
Para llegar directamente a este paso o comparar su aplicación, ejecute el siguiente comando en el repositorio clonado:
1 |
$ git checkout f1076eb
|
Manejo de errores en Koa
Al
utilizar middlewares en cascada, podemos detectar errores utilizando el
mecanismo de try/catch, ya que cada middleware puede responder
mientras se cede tanto en sentido descendente como ascendente. Entonces,
si agregamos un middleware Try and Catch al comienzo de la aplicación,
detectará todos los errores encontrados por la solicitud en el resto del
middleware, ya que será el último middleware durante la ejecución
ascendente. Agregar el siguiente código en la línea 10 o antes en index.js debería funcionar.
1 |
app.use(function *(next){ |
2 |
try{ |
3 |
yield next; //pass on the execution to downstream middlewares |
4 |
} catch (err) { //executed only when an error occurs & no other middleware responds to the request |
5 |
this.type = 'json'; //optional here |
6 |
this.status = err.status || 500; |
7 |
this.body = { 'error' : 'The application just went bonkers, hopefully NSA has all the logs ;) '}; |
8 |
//delegate the error back to application
|
9 |
this.app.emit('error', err, this); |
10 |
}
|
11 |
});
|
Agregar registro y límite de velocidad a la aplicación
El almacenamiento de registros es una parte esencial de una aplicación moderna, ya que los registros son muy útiles para depurar y descubrir problemas en una aplicación. También almacenan todas las actividades y, por lo tanto, pueden usarse para descubrir patrones de actividad del usuario y otros patrones interesantes.
La limitación de tarifas también se ha convertido en una parte esencial de las aplicaciones de hoy en día, donde es importante evitar que los spammers y los bots desperdicien los valiosos recursos de tu servidor y evitar que raspen tu API.
Es bastante fácil agregar el registro y la
limitación de velocidad a nuestra aplicación Koa. Utilizaremos dos
módulos comunitarios: koa-logger y koa-better-rate-limiting.
1 |
var logger = require('koa-logger');
|
2 |
var limit = require('koa-better-ratelimit');
|
3 |
//Add the lines below just under error middleware. |
4 |
app.use(limit({ duration: 1000*60*3 , // 3 min
|
5 |
max: 10, blacklist: []})); |
6 |
app.use(logger()); |
Aquí hemos importado dos módulos y los hemos agregado como middleware. El
registrador registrará cada solicitud e imprimirá en stdout, que se puede guardar fácilmente en un archivo. Y
limitar el middleware limita el número de solicitudes que un usuario
determinado puede solicitar en un período de tiempo dado (aquí hay un
máximo de diez solicitudes en tres minutos). También puede agregar una
matriz de direcciones IP que se incluirán en la lista negra y su
solicitud no se procesará.
Recuerde instalar los módulos antes de usar el código usando:
1 |
$ npm install koa-logger koa-better-ratelimit --save |
Comprimir el tráfico
Una de las formas de garantizar una entrega más rápida es descomprimir
gzip su respuesta, que es bastante simple en Koa. Para comprimir su
tráfico en Koa, puede usar el módulo koa-compress.
Aquí, las opciones pueden ser un objeto vacío o pueden configurarse según el requisito.
1 |
var compress = require('koa-compress'); |
2 |
var opts = { |
3 |
filter: function (content_type) { return /text/i.test(content_type) }, // filter requests to be compressed using regex |
4 |
threshold: 2048, //minimum size to compress |
5 |
flush: require('zlib').Z_SYNC_FLUSH }; |
6 |
}
|
7 |
//use the code below to add the middleware to the application
|
8 |
app.use(compress(opts)); |
Incluso puede desactivar la compresión en una solicitud agregando el siguiente código a un middleware:
1 |
this.compress = true; |
No te olvides de instalar compress usando npm.
1 |
$ npm install compress --save |
Para llegar directamente a este paso o comparar su aplicación, ejecute el siguiente comando en el repositorio clonado:
1 |
git checkout 8f5b5a6 |
Escritura de pruebas
La prueba debe ser una parte esencial de todo el código, y uno debe apuntar a la máxima cobertura de prueba. En este artículo, escribiremos pruebas para las rutas a las que se puede acceder desde nuestra aplicación. Usaremos supertest y Mocha para crear nuestras pruebas.
Almacenaremos nuestra prueba en test.js en la carpeta api. En
ambas pruebas, primero describimos nuestra prueba, dándole un nombre más
legible para el ser humano. Después
de eso, pasaremos una función anónima que describe el comportamiento
correcto de la prueba y toma una devolución de llamada que contiene la
prueba real. En cada prueba, importamos nuestra
aplicación, iniciamos el servidor, describimos el tipo de solicitud, la
URL y la consulta, y luego configuramos la codificación en gzip.
Finalmente buscamos la respuesta si es correcta.
1 |
var request = require('supertest'); |
2 |
var api = require('../index.js'); |
3 |
|
4 |
describe('GET all', function(){ |
5 |
it('should respond with all the words', function(done){ |
6 |
var app = api; |
7 |
request(app.listen()) |
8 |
.get('/v1/all') |
9 |
.query({ word: 'new' }) |
10 |
.set('Accept-Encoding', 'gzip') |
11 |
.expect('Content-Type', /json/) |
12 |
.expect(200) |
13 |
.end(done); |
14 |
})
|
15 |
})
|
16 |
|
17 |
describe('GET /v1/single', function(){ |
18 |
it('should respond with a single result', function(done){ |
19 |
var app = api; |
20 |
|
21 |
request(app.listen()) |
22 |
.get('/v1/single') |
23 |
.query({ word: 'new' }) |
24 |
.set('Accept-Encoding', 'gzip') |
25 |
.expect(200) |
26 |
.expect('Content-Type', /json/) |
27 |
.end(function(err, res){ |
28 |
if (err) throw err; |
29 |
else { |
30 |
if (!('_id' in res.body)) return "missing id"; |
31 |
if (!('word' in res.body)) throw new Error("missing word"); |
32 |
done(); |
33 |
}
|
34 |
});
|
35 |
})
|
36 |
})
|
Para ejecutar nuestra prueba, haremos un Makefile:
1 |
test: |
2 |
@NODE_ENV=test ./node_modules/.bin/mocha \ |
3 |
--require should \ |
4 |
--reporter nyan \ |
5 |
--harmony \ |
6 |
--bail \ |
7 |
api/test.js |
8 |
|
9 |
.PHONY: test |
Aquí, hemos configurado el reportero (nyan cat) y el marco de prueba (mocha). Tenga en cuenta que la importación debe agregar --harmony para habilitar el modo ES6. Finalmente, también especificamos la ubicación de todas las pruebas. Se
puede configurar un Makefile para realizar pruebas interminables de su
aplicación.
Ahora para probar su aplicación, solo use el siguiente comando en el directorio principal de la aplicación.
1 |
$ make test |
Recuerde instalar los módulos de prueba (mocha, should, supertest) antes de realizar la prueba, utilizando el siguiente comando:
1 |
$ npm install mocha should mocha --save-dev |
Corriendo en producción
Para ejecutar nuestras aplicaciones en producción, usaremos PM2, que es un monitor de proceso de Nodo útil. Deberíamos deshabilitar la aplicación registradora durante la producción; se puede automatizar usando variables de entorno.
Para instalar PM2, ingrese el siguiente comando en la terminal
1 |
$ npm install pm2 -g |
Y nuestra aplicación se puede iniciar con el siguiente comando:
1 |
$ pm2 start index.js --node-args="--harmony" |
Ahora, incluso si nuestra aplicación falla, se reiniciará automáticamente y podrá dormir profundamente.
Conclusión
Koa es un middleware ligero y expresivo para Node.js que hace que el proceso de escritura de aplicaciones web y API sea más agradable.
Le permite aprovechar una gran cantidad de módulos de la comunidad para ampliar la funcionalidad de su aplicación y simplificar todas las tareas mundanas, haciendo que el desarrollo web sea una actividad divertida.
No dude en dejar comentarios, preguntas u otra información en el campo a continuación.



