En el post anterior hablamos largo y tendido sobre este increíble framework, te contamos que es koa js, mencionamos las características que lo hacían una muy buena opción frente a otros frameworks, sobretodo nos centramos como funcionaba por debajo y por qué los middlewares de koa2 son una obra de arte, eso nos da una buena base, así que te recomiendo que des una vuelta por ese post y luego regreses.

Bien, asumo que ya lo hiciste, en este post trataremos ya las funcionalidades en sí que trae koa, es decir a un nivel superior, empezando desde cero(0), hasta complicarlo de a pocos, sin más preámbulo, vamos a ello!

¬ŅC√≥mo instalar koa?

Es tan sencillo como ubicarte en tu carpeta de trabajo y ejecutar:

npm install koajs --save // en npm
yarn add koa --save // en yarn
Instalación de koa2

Tu package.json debe verse de esta forma:

package.json

¬ŅC√≥mo se crea una aplicaci√≥n b√°sica?

Sencillamente con estas 3 lineas:

const Koa = require('koa'); // (1)
const app = new Koa(); // (2)

app.use(async ctx => { // (3)
  ctx.body = 'Saludos desde eldevsin.site';
});

app.listen(3000); // (4)
Creando una app

Primero importamos el paquete que acabamos de instalar (1), luego creamos una instancia de koa (2), con la instancia creada podemos agregar un middleware super sencillo (3) que le devuelve un texto al cliente, para finalmente inicializar el servidor haciendo que la aplicación escuche en el puerto 3000 (4), o el puerto libre que quieras. Ten en cuenta que este es el ejemplo más sencillo que podrás encontrar, analízalo para pasar al siguiente punt0.

Sobre app.listen();

Es la función muy aparte que inicia la escucha del servidor, abstrae la forma en que se ejecuta un servidor con el paquete nativo http, así que nos evita de estar haciendo lo siguiente:

const http = require('http');
const Koa = require('koa');
const app = new Koa();
http.createServer(app.callback()).listen(3000); // reemplaza esta parte
Listen en koajs

Sobre app.callback();

Ya lo vimos en el post anterior, ve y revísalo xD, pero en pocas palabras se encarga de devolver una función completamente compatible con la librería nativa http para manejar la solicitudes del servidor.

Es m√°s, si lo pensamos bien, podemos utilizar la funcionalidad ya programada con koa para representar el servidor en diferentes formas:

const http = require('http');
const https = require('https');

// lo mismo con koa
const Koa = require('koa');
const app = new Koa();

http.createServer(app.callback()).listen(3000); // (1)
https.createServer(app.callback()).listen(3001); // (2)
Utilizando koa para reutilizar levantar por otras formas

Utilizar esto en las librer√≠as de http (1) o https (2), o en diferentes puertos es un juego de ni√Īos.

¬ŅC√≥mo agregar un middleware en koa?

Hasta este momento ya lo deberías tener claro, pero si estuviste un poco despistado, no te preocupes, eso se logra a través de app.use(), de la siguiente forma:

app.use(async ctx => { // -.- si, tuvimos que volverlo a dejar claro
  ctx.body = 'Saludos desde eldevsin.site';
});
Middleware en koajs

¬ŅQu√© es el context(ctx) de koa?

B√°sicamente el context, es un objeto que tiene propiedades √ļtiles de koa, como los objetos request o response ...¬ŅS√≥lo eso? Pues no, hay muchas mas propiedades √ļtiles que tiene este objeto, vamos a revis√°ndolas:

  • ctx.req: Objeto request de nodejs.
  • ctx.res: Objeto response de nodejs.
  • ctx.request: Objeto request de koajs.
  • ctx.response: Objeto response de koajs.
  • ctx.state: Objeto recomendado para pasar datos entre middlewares.
  • ctx.app: Referencia de la instancia actual de koa.
  • ctx.cookies: Instancia de paquete cookies, permite hacer ¬†agregar/obtener cookies firmadas.
  • ctx.throw: Funci√≥n √ļtil para lanzar errores en la aplicaci√≥n, por defecto devuelve el status 500.
  • ctx.assert: Funci√≥n similar a throw, la diferencia es que assert eval√ļa la existencia de un valor pasado como par√°metro.

¬ŅPuedo a√Īadirle propiedades personalizadas a ctx?

Puedes hacerlo, sin embargo es considerado un anti patrón, si tienes esa necesidad es recomendable hacerlo a través de la propiedad state, ¡que para eso fue hecha!, iremos viendo más adelante como se usa ésta y otras características propias de koa.

Los alias de koa

-¬ŅAlias...de qu√© hablas? apura que quiero ir a comer.

-Keep calm, vamos por la mitad recién.

Cuando nos referimos a alias estamos hablando de ciertas propiedades y métodos que pertenecen al objeto request y al objeto response que están como una vía de acceso rápido en el objeto context. En la siguiente imagen puedes apreciarlo mejor:

Accesores agregados a context

Nota que las propiedades est√°n accesibles directamente desde context, por ejemplo body, status, length, pertenecientes a response, y por otro lado query, headers, url que pertenecen a request.

Alias de request

La lista completa de accesores agregados de solicitud es la siguiente:

ctx.header
ctx.headers
ctx.method
ctx.method=
ctx.url
ctx.url=
ctx.originalUrl
ctx.origin
ctx.href
ctx.path
ctx.path=
ctx.query
ctx.query=
ctx.querystring
ctx.querystring=
ctx.host
ctx.hostname
ctx.fresh
ctx.stale
ctx.socket
ctx.protocol
ctx.secure
ctx.ip
ctx.ips
ctx.subdomains
ctx.is()
ctx.accepts()
ctx.acceptsEncodings()
ctx.acceptsCharsets()
ctx.acceptsLanguages()
ctx.get()
Alias de solicitud

Alias de response

La lista completa de accesores de respuesta es la siguiente:

ctx.body
ctx.body=
ctx.status
ctx.status=
ctx.message
ctx.message=
ctx.length=
ctx.length
ctx.type=
ctx.type
ctx.headerSent
ctx.redirect()
ctx.attachment()
ctx.set()
ctx.append()
ctx.remove()
ctx.lastModified=
ctx.etag=
Alias de respuesta

Como hacer que las cosas ocurran

Despu√©s de conocer las herramientas de nivel superior que tenemos disponibles en koa, en esta secci√≥n meteremos mano a diferentes casos y preguntas que com√ļnmente te vas a hacer utilizando este framework, ¬°empecemos!

¬ŅComo se usa el state en koa?

Como lo comentamos anteriormente el state es un objeto visible entre middlewares, entonces por tanto podemos meter lo que quisiéramos dentro de él y que persista mientras dure la petición, éste es un ejemplo super sencillo:

...
app.use(async (ctx, next) => {
  ctx.state.myCustomKey = Math.random();
  await next();
});

app.use(async (ctx, next) => {
  ctx.state.myCustomKey += ' - eldevsin.site';
  await next();
});

app.use(async ctx => {
  ctx.body = 'el valor de myCustomKey es: ' + ctx.state.myCustomKey;
});

app.listen(3000);

Estamos utilizando 3 middlewares, el primero, para crear en el state una propiedad llamada myCustomKey con un valor random, el segundo middleware se va a encargar de concatenar un texto (autobombo detected xD), y finalmente ese valor lo enviamos al cliente. El resultado ser√° algo similar a:

Resultado en el navegador

¬ŅComo manejar las cookies en koajs?

Este punto nuevamente es pan comido, para esto tenemos disponible el objeto cookies del contexto de koa(ctx.cookies), dicho objeto diene disponible la función set(), que sirve para crear cookies en el cliente de tu aplicación.

Cookies sin firmar

Como primer ejemplo crearemos una cookie sin firmar, el uso que le puedes dar a este tipo de cookie es para agregar alg√ļn indicador donde no tengas informaci√≥n confidencial o importante, como por ejemplo cuando necesitas saber si el usuario ya acepto las condiciones del servicio al entrar a tu web. Miremos el siguiente c√≥digo:

...
app.use(async (ctx, next) => {
  ctx.cookies.set('myCustomCookie', 'eldevsinsite');
  await next();

  ctx.body = 'algun valor';
});

app.listen(3000);

Al ingresar a tu servidor este crear√° una cookie y almacenar√° del lado del cliente, como lo podemos comprobar inspeccionando en el navegador:

Cookie almacenada en el navegador

La cookie que acabamos de crear no esconde/protege de ninguna manera el valor que tiene asignado.

Cookies firmados

Estas cookies son √ļtiles cuando quieres ocultar su informaci√≥n, como por ejemplo id de sesiones de usuario. En el siguiente ejemplo puedes como crearlos:

...
app.use(async (ctx, next) => {
  ctx.cookies.set('myCustomCookie', 'eldevsinsite', { signed: true });
  await next();

  ctx.body = 'algun valor';
});

app.listen(3000);

La diferencia radica en que en las opciones se pasa la propiedad signed: true, esto le indica a koa que debe firmar la cookie. El resultado es el siguiente:

Ops! Error 500.

¬°Ey! no te asustes, es normal...pero...¬Ņpor qu√© sucede esto?, bien koa esta intentando encriptar la cookie, sin embargo necesita una llave para hacerlo, para lograrlo debes especificarlo en app.keys, es decir a nivel de aplicaci√≥n, a esa propiedad debes asignarle un array con al menos un valor, pues actuara como semilla, de la siguiente manera:

app.keys = ['mySuperClave'];

Al inspeccionar nuevamente en el navegador veremos:

Cookie firmada localmente

Bien, logramos firmar la cookie, pero .. ¡se esta viendo la información!, en este momento tal vez piensas que te he fallado, pero no es así, te tengo noticias... lo que sucede es que este ejemplo se esta desarrollando localmente con http, por eso la información no viaja encriptada, pero en un servidor real con https se verá de la siguiente manera:

Cookie firmada en un servidor con https

Genial, no?, tu cookie fue firmada correctamente y la transmisión de la información es completamente segura.

Una vez creada la cookie, posteriormente será necesario acceder a esa información, para hacerlo existe la función get() dentro del objeto cookies, como en el siguiente ejemplo:

...
app.use(async (ctx, next) => {
  ctx.cookies.set('myCustomCookie', 'eldevsinsite', { signed: true });
  await next();
});

app.use(async (ctx, next) => {
  ctx.body =
    'siguenos en redes sociales - ' + ctx.cookies.get('myCustomCookie');
});

app.listen(3000);
Obteniendo una cookie

Si tu cookie esta firmada o no, es lo de menos, ahora puedes obtener el valor f√°cilmente.

Para hacerlo simplemente debes decirle a koa que el valor de la cookie myCustomCookie (o la que has definido) es nulo, así:

...
app.use(async (ctx, next) => {
  ctx.cookies.set('myCustomCookie', null, { signed: true });
  await next();
});

app.use(async (ctx, next) => {
  ctx.body =
    'siguenos en redes sociales - ' + ctx.cookies.get('myCustomCookie');
});

app.listen(3000);
Eliminando una cookie

Por tanto al inspeccionar el navegador:

Sin cookies en el navegador

¬ŅC√≥mo manejo los errores en koa?

Una de las cosas mas terror√≠ficas para los programadores es enterarse de un error 500 en la aplicaci√≥n, sobretodo en plena madrugada, y no es para menos, sin embargo, koa nos tiene una caracter√≠stica interesante para estar pendientes de este problema, pues f√°cilmente se puede centralizar los eventos de error, ¬Ņc√≥mo?, de la siguiente forma:

...
app.use(async (ctx, next) => {
  ctx.body = 'suscribete en la web - eldevsin.site';
  throw new Error('Wish we could turn back time'); // producimos el error
});

app.on('error', e => {
  console.log('error centralizado: ', e);
});

app.listen(3000);
Centralizando un error

Obtendremos la siguiente salida al consultar al servidor:

Error centralizado

Entonces podemos aprovechar ese evento para hace muchas cosas, como notificarte por email, por sms, por registrar esos errores en aws, o lo que se ocurra.

Ojo, pero hay un punto importante, existe una forma que evita que se ejecute el evento on('error'), y es cuando agregas un try/catch en un middleware, tomamos el mismo ejemplo pero con una versión extendida:

...

app.use(async (ctx, next) => {
  try {
    await next();
  } catch (e) {
    ctx.body = 'sucedio un error...pero lo atrapamos';
  }
});

app.use(async (ctx, next) => {
  ctx.body = 'suscribete en la web - eldevsin.site';
  throw new Error('Wish we could turn back time'); // producimos el error
});

app.on('error', e => {
  console.log('error centralizado: ', e);
});
...
Atrapando un error con try/catch

Miremos el navegador a ver el resultado:

Mensaje personalizado al cliente

Cuando sucede el error, el try/catch lo atrapa y evita que se ejecute el escuchador de eventos de error, en cambio env√≠a un mensaje personalizado al cliente, ¬Ņc√≥mo lo sabemos?, pues mira tu terminal...esta m√°s que limpia.

¬ŅC√≥mo obtenemos los valores de los par√°metros de consulta o querystring?

Estos casos con muy comunes cuando se utilizan filtros, por ejemplo imagina que quieres listar a todos los elefantes peque√Īos de color morado:

Enviando querystring

Para obtener esos valores en el backend debemos apalancarnos de ctx.query:

...
app.use(async (ctx, next) => {
  ctx.body = ctx.query;
});

app.listen(3000);

Lo que estamos haciendo aquí es mostrar lo que nos trae query enviándolo al cliente, ojo, es es solo por efectos del ejemplo, obteniendo el siguiente resultado:

Query enviado al frontend

Luego de eso podemos usar el objeto para hacer consultas a la base de datos de tu preferencia, obviamente luego de aplicar validaciones y otras cosas necesarias.

¬ŅC√≥mo definir rutas?

Cuando realizamos una aplicación es importante distribuir la información en diferentes lugares de la url para organizar los servicios, en koa podemos hacerlo de dos formas, la manera dura o de la manera mas sencilla.

La manera dura

Nos referimos a la manera dura porque b√°sicamente, se eval√ļa lo que trae ctx.path, el siguiente es un ejemplo super b√°sico:

...
app.use(async (ctx, next) => {
  const path = ctx.path;
  let response = 'Ruta no encontrada.';

  if (path.includes('home')) {
    response = 'Hola desde el home.';
  }

  if (path.includes('contact')) {
    response = 'Hola desde el contact.';
  }

  return (ctx.body = response);
});
...
Rutas en koa

Por defecto se devuelve que la ruta no fue encontrada, si embargo si la solicitud coincide con home o contact, se devolverá un saludo, así será el resultado:

Resultado en el navegador

Ahora bien, este ejemplo no sirve de mucho si pensamos en que en una ruta puedes posiblemente repetir palabras o tener incluso todas ellas, entonces para eso tendríamos que hacer coincidir las cosas con expresiones regulares y muchas más validaciones. Awww, shit, muchos dolores de cabeza si utilizas este camino.

La forma sencilla (y recomendada)

Para evitar una catástrofe en tu código, es mejor utilizar una librería que junto con koa la hacen linda, nos referimos a koa-router, con esta librería podemos construir módulos de rutas y devolver un middleware para inyectarlo a koa.

Una vez entendido esa parte, empezaremos instalando koa-router:

npm install koa-router --save // con npm
yarn add koa-router --save // con yarn

Tu package.json debería tener un aspecto similar a:

package.json

El siguiente punto es importar la dependencia y crear una instancia del enrutador:

const Koa = require('koa');
const Router = require('koa-router');

const router = new Router();
Definición de koa router

Creando las rutas

Los métodos mas utilizados para realizar las consultas http son: GET, POST, PUT, DELETE, existen otros más que por ahora no vamos a tener en cuenta, y todos ellos están disponibles en koa-router, bien, buscaremos obtener el mismo resultado que realizamos en la forma anterior, de la siguiente manera:

const Koa = require('koa');
const Router = require('koa-router');

const app = new Koa();
const router = new Router();

router.get('/home', async ctx => { // 1
  ctx.body = 'Hola desde el home.';
});

router.get('/contact', async ctx => { // 2
  ctx.body = 'Hola desde el contact.';
});

app.use(router.routes()); // 3

app.use(async ctx => { // 4
  return (ctx.body = 'Ruta no encontrada.');
});

app.listen(3000);
Definición de rutas

En resumen lo que estamos haciendo en el código anterior es:

Le decimos al enrutador(router) que queremos definir dos rutas /home(1) y /contact(2), ambos son accedidos mediante el m√©todo GET, luego el enrutador le pasa a koa todas las rutas mediante un middleware(3), finalmente, si no llega a coincidir ninguna ruta pasa al √ļltimo middleware que devuelve al cliente "ruta no encontrada"(4).

Si bien hemos visto implementar rutas solo con koa es una tarea complicada para nosotros la mayoría de mortales, la ayuda de koa-router es crucial, pues permite hacerlo de una forma muy intuitiva.

Por ahora dejaremos hasta allí el post para no hacerlo más extenso, ha sido una jornada maratónica pero con mucho valor por las maravillosas cosas que puedes empezar a hacer con koa y también considerarla como buena alternativa frente a express js, meteor js, totaljs, entre otras.


Via GIPHY

Si consideras que este post te ha aportado mucho valor, compártelo con tus amigos para que ellos también lo aprovechen, deja tu comentario pues amamos saber lo que piensas, y síguenos en nuestras redes sociales, nos vemos en una próxima entrega.