1. Code
  2. JavaScript
  3. Node

Introducción a Generators & Koa.js: Parte 2

Podemos comenzar creando un servidor base Koa:
Scroll to top
This post is part of a series called Introduction to Generators & Koa.js.
Introduction to Generators & Koa.js: Part 1

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

Final product imageFinal product imageFinal product image
What You'll Be Creating

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=new
  • http://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.