Creando un esqueleto de API básico con Typescript y Koa - Parte 1

10 minuto(s) de lectura

Una vez tuve Node.js instalado, el siguiente objetivo que me marqué fue montar un esqueleto básico para poder hacer una pequeña API REST usando Typescript. Leí que se podía trabajar con Node.js nativo pero para no reinventar la rueda y acelerar la velocidad de desarrollo, tenía que elegir un framework que me facilitase las cosas.

Esto no es algo nuevo para una persona que desarrolla en PHP. Desde hace años estamos acostumbradas a elegir entre Laravel, Symfony, Laminas, Lumen, CakePHP, Yii, etc. para facilitar el desarrollo y no tener que “pegarnos” con temas ya solucionados como el enrutado. Con Node.js la cosa no es diferente y tenemos en el mercado diferentes frameworks: Express, Hapi, Fastify, Koa, etc.

¿Cual elegir? Pues bueno, como siempre las soluciones mágicas no existen y ahí toca que cada uno evalúe sus requisitos y necesidades antes de “casarse” con una solución concreta. Hay muchas cosas a tener en cuenta: rendimiento, cantidad de documentación disponible, tamaño de la comunidad, etc. o incluso algunas bastante importantes si la decisión es para desarrollar una solución para una empresa, como por ejemplo la facilidad de encontrar personas que desarrollen con ese framework o el posible soporte profesional por parte de una empresa. En mi caso elegí Koa porque empieza a tener una comunidad creciente, se actualiza a menudo y ha sido creado por parte del equipo original de Express (que realmente es el más usado y sería otra buena opción para empezar).

Arrancando el esqueleto

A la hora de crear un proyecto nuevo en Node, descubrí que no es muy diferente a la filosofía de los proyectos PHP que hacen uso de Composer. Si en PHP tienes el fichero composer.json que es el que utilizas para gestionar dependencias, scripts de construcción, etc. en Node.JS tienes el fichero package.json.

Por lo tanto, lo primero que tenemos que hacer es inicializar el fichero package.json desde el cual gestionaremos todas las dependencias del proyecto. Comenzaremos entonces por crear un directorio para nuestro proyecto:

mkdir koa-test-api
cd koa-test-api

y a continuación ejecutamos

npm init -y

Esta instrucción crea un fichero package.json con el los valores básicos por defecto y que será el que usaremos como base para definir las dependencias del proyecto y definir los scripts de construcción.

{
  "name": "koa-test-api",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

A continuación instalamos las dependencias del proyecto, lo cual no difiere mucho de cómo venía haciéndolo en PHP con composer:

npm install koa koa-bodyparser koa-router koa-helmet

Con esta instrucción estamos instalando:

  • koa: framework web ligero que nos facilitar la creación de un servidor http
  • koa-bodyparser: middleware que ofrece utilidades para la gestión del cuerpo de las peticiones
  • koa-router: middleware para la gestión de rutas
  • koa-helmet: middleware para añadir cabeceras de seguridad en las respuestas

Instalando el lenguaje Typescript

Por defecto cuando arrancamos un proyecto de Node.js la opción por defecto es trabajar con Javascritp directamente. Para usar Typescript como lenguaje en nuestro proyecto necesitamos instalarlo. La instalación se hace con npm y tenemos dos opciones, instalarlo globalmente (como un comando más del sistema operativo) o como dependencia del proyecto en el que estás trabajando.

La opción más recomendable para instalar Typescript es instalarlo como dependencia de cada proyecto para así mantener la versión más indicada para cada uno de ellos, de forma que sea fácil trabajar en la misma máquina de desarrollo con distintas versiones del lenguaje.

Typescript se instala realmente como una dependencia sólo de la fase de desarrollo. Esto es así porque el código Typescript se va a transpilar (algunos prefieren llamarlo compilar) a código Javascript y es este último el que se ejecuta realmente en Node. Para instalarlo utilizamos el comando npm pasándole como opción --save-dev para que se instale como dependencia sólo de desarrollo:

npm install --save-dev typescript

Como Typescript es un lenguaje tipado, para aquellas librerías que no ofrecen los tipos directamente, hay que instalar dependencias adicionales.

npm install --save-dev @types/koa @types/koa-bodyparser @types/koa-helmet @types/koa-router

La configuración del lenguaje se almacena en un fichero tsconfig.json (que sería el equivalente al php.ini en PHP). Existen configuraciones recomendadas según la plataforma usadas y que se pueden extender (lo cual facilita el setup). Por ejemplo si estamos trabajando con Node 14 (última LTS al escribir esto) instalaríamos la siguiente dependencia de desarrollo:

npm install --save-dev @tsconfig/node14

Y en nuestro proyecto comenzaríamos el fichero tsconfig.json de esta forma:

{
  "extends": "@tsconfig/node14/tsconfig.json"
}

A continuación podemos añadir aquellas configuraciones que necesitemos para nuestro proyecto:

{
  "extends": "@tsconfig/node14/tsconfig.json",
  "compilerOptions": {
    "declaration": true,
    "sourceMap": true,
    "outDir": "dist"
  },
  "include": ["src"],
  "exclude": ["node_modules"]
}

El fichero tsconfig.json es muy importante porque es el que le dice al comando tsc cómo debe generar el código Javascript, es decir, con qué versión del lenguaje (ES3, ES5, ES6…), cómo hacer la carga de módulos y dependencias, qué destino va a tener el código (si se va a ejecutar en Node o bien en el navegador), etc.

Una vez instalado y configurado Typescript, necesitamos añadir la instrucción para poder “transpilar” el código Typescript a Javascript. Para ello añadimos una tarea en el package.json:

"scripts": {
    "build": "npx tsc"
}

Configurando un linter para ayudarnos a verificar el formato de nuestro código

Es buena práctica utilizar un linter para ayudarnos a cumplir un estándar en el formato de nuestro código. Hasta hace poco el linter recomendado para Typescript era tslint pero para desarrollos nuevos en la propia documentación de Typescript recomiendan usar en su lugar eslint.

Para ello primero instalamos el linter:

npm install --save-dev eslint @typescript-eslint/parser @typescript-eslint/eslint-plugin

Después de instalar el linter, tenemos que fijar las reglas de verificación. Éstas se configuran en el fichero .eslintrc

{
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 2020,
          "sourceType": "module",
          "ecmaFeatures": {
            "jsx": true
        }
    },
    "extends": [
        "plugin:@typescript-eslint/recommended"
    ],
    "rules": {
    }
}

Añadiendo pretierr

Además del linter es recomendable tener alguna herramienta para formatear nuestro código y dejarlo con el estilo con el que más nos sintamos cómodos, o bien, para dejar el código según la guía de estilo de nuestra empresa.

Uno de los recomendados es prettier que se integra bien con eslint. El primer paso sería, como con cualquier otra dependencia de desarrollo, instalarlo. Para instalar prettier junto con el soporte para integrarse con nuestro linter ejecutaremos:

npm install --save-dev prettier eslint-config-prettier eslint-plugin-prettier

Como otras tantas herramientas de desarrollo, prettier usa un fichero de configuración llamado .prettierrc:

{
  "semi": true,
  "trailingComma": "all",
  "singleQuote": true,
  "printWidth": 120,
  "tabWidth": 2
}

Para integrarlo con eslint modificaremos nuestro fichero .eslintrc para incluir las reglas recomendadas:

{
    "parser": "@typescript-eslint/parser",
    "parserOptions": {
        "ecmaVersion": 2020,
          "sourceType": "module",
          "ecmaFeatures": {
            "jsx": true
        }
    },
    "extends": [
        "plugin:@typescript-eslint/recommended",
        "prettier",
        "plugin:prettier/recommended"
    ],
    "rules": {
    }
}

A continuación es recomendable añadir un script nuevo en nuestro package.json para poder ejecutar el linter a mano cuando lo necesitemos:

"scripts": {
    "build": "npx tsc",
    "lint": "npx eslint 'src/**/*.{js,ts,tsx}' --quiet --fix"
}

Empezando a escribir código

Vale, ya tenemos el entorno base para poder compilar Typescript ahora podemos empezar a escribir algo de código. Vamos a empezar por (como no podía ser de otra manera) con un pequeño “Hola Mundo” en formato API JSON, de forma que cuando llamemos al raíz del servidor nos devuelva el siguiente JSON:

{"hello":"Koa"}

Para necesitamos:

  • Arrancar Koa
  • Definir la ruta raíz
  • Hacer el código necesario para devolver el JSON que queremos
  • Arrancar el servidor

Como este es un ejemplo muy sencillo y básico, vamos a meter todo el código (que tampoco es demasiado) en un único fichero src/app.ts que va a tener el siguiente contenido:

import Koa from 'koa';
import Router from 'koa-router';
import BodyParser from 'koa-bodyparser';

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

router.get('/', async (ctx, next) => {
  ctx.body = { hello: 'Koa' };

  await next();
});

application.use(router.routes()).use(router.allowedMethods());
application.use(BodyParser());

application.listen(3000);

Aunque vengamos de otro lenguaje de programación, como en mi caso PHP, la estructura de este código nos resultará familiar y fácilmente entendible, aunque, vamos a analizarlo un poco para entenderlo.

Las primeras 3 líneas son la importación de las dependencias, es decir decir qué recursos de las librerías necesitamos para nuestro código:

import Koa from 'koa';
import Router from 'koa-router';
import BodyParser from 'koa-bodyparser';

Para las personas que desarrollamos PHP en las versiones más actuales nos resultará fácil identificar estos import con los use de PHP como por ejemplo:

<?php

use Slim\App;
use Slim\Middleware\ErrorMiddleware;

A continuación tenemos la creación de los objetos que necesitamos:

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

En este caso tenemos creamos dos objetos application y router. El primero es el framework en sí mismo y el segundo el “enrutador” que nos va a facilitar definir las rutas que nuestro API va a ofrecer. Éstas se definen de la siguiente forma:

router.get('/', async (ctx, next) => {
  ctx.body = { hello: 'Koa' };

  await next();
});

Aquí le estamos diciendo al enrutador que cuando se reciba una petición HTTP a la ruta raíz se derive la ejecución al callback. Ésta función callback lo único que hace es cambiar el valor del cuerpo de la respuesta al objeto que nos interesa.

¿Y como le decimos a Koa que existen esas rutas y que lo que tiene que devolver es un JSON?. Pues bien, Koa se basa en la utilización de middlewares para delegar ciertas funcionalidades y para ello sólo hay que decirle a Koa que los use:

application.use(router.routes()).use(router.allowedMethods());
application.use(BodyParser());

En la primera línea estamos inyectando en Koa el middleware de enrutado y en la segunda uno que nos permite manejar de forma fácil la salida en los formatos más estándar. En este caso la función de BodyParser() es la que permite pasar a la propiedad ctx.body un objeto y que en la salida tengamos un JSON bien formado y con sus cabeceras.

Ya, por último, nos queda arrancar el servidor. Si habéis leído mi entrada anterior y venís del mundo PHP recordaréis que no necesitamos un servidor web como Apache o Nginx para ejecutar nuestro código en Node, si no que tanto Node como los framework como Koa son capaces de manejar tráfico HTTP por ellos mismos. Para eso tenemos la última línea de nuestro código

application.listen(3000);

Aquí le estamos diciendo que se ponga a escuchar en el puerto TCP 3000, con lo que ya estamos en disposición de ir a la consola y “transpilar” nuestro código. Para ello ejecutamos:

npm run build

Con lo que veremos algo similar a esto en la salida

javiercaride@beast:~/workspace/koa-test-api$ npm run build

> [email protected] build /home/javiercaride/workspace/koa-test-api
> npx tsc

javiercaride@beast:~/workspace/koa-test-api$ 

Bien, ya estamos en disposición de ejecutar nuestro código, pero antes debemos añadir en el package.json una tarea más para facilitar el arranque del servidor, concretamente la línea start del objeto scripts:

  "scripts": {
    "build": "npx tsc",
    "lint": "npx eslint 'src/**/*.{js,ts,tsx}' --quiet --fix",
    "start": "node dist/app.js"
  }

Si volvemos a la consola y ejecutamos npm start veremos que se ejecuta algo pero que no devuelve el control al terminal:

javiercaride@beast:~/workspace/koa-test-api$ npm start

> [email protected] start /home/javiercaride/workspace/koa-test-api
> node dist/app.js

Esto significa que nuestro primer programa en Typescript con Koa se ha puesto a escuchar en el puerto 3000. Vamos a probarlo ejecutando en otra consola el siguiente curl:

curl -i -X GET 'http://localhost:3000/'

Y como por arte de magia veremos lo siguiente:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 15
Date: Sat, 19 Jun 2021 15:32:48 GMT
Connection: keep-alive
Keep-Alive: timeout=5

{"hello":"Koa"}

Como podéis comprobar nuestras 17 líneas de código son capaces de responder una petición HTTP GET, devolver un objeto JSON y las cabeceras correctas para interpretar la respuesta.

Si hiciésemos una solicitud con método POST, por ejemplo:

curl -i -X POST \
   -H "Content-Type:application/json" \
   -d \
'{}' \
 'http://localhost:3000/'

Veríamos como nos respondería con un código de respuesta 405 (Method not allowed):

HTTP/1.1 405 Method Not Allowed
Allow: HEAD, GET
Content-Type: text/plain; charset=utf-8
Content-Length: 18
Date: Sat, 19 Jun 2021 15:36:36 GMT
Connection: keep-alive
Keep-Alive: timeout=5

HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 9
Date: Sat, 19 Jun 2021 15:38:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Not Found

Y si hiciésemos la petición por GET, pero a una ruta diferente:

curl -i -X GET 'http://localhost:3000/user'

Obtendríamos un 404 (Not Found):

HTTP/1.1 404 Not Found
Content-Type: text/plain; charset=utf-8
Content-Length: 9
Date: Sat, 19 Jun 2021 15:38:32 GMT
Connection: keep-alive
Keep-Alive: timeout=5

Not Found

Conclusión

Aunque los comandos, nombre de los ficheros de configuración, frameworks, etc. son completamente distintos, los conceptos básicos de montar un esqueleto para un API en NodeJS con Typescript y Koa no son muy diferentes a hacerlo en PHP con cualquier framework ligero, lo cual hace que el proceso de cambiar de stack tecnológico no tenga una curva de aprendizaje demasiado difícil.

Próximos pasos

Ahora que tengo un esqueleto muy básico de un API (pero muy muy básico) los siguientes puntos que quiero abordar son:

  • Aprender antes de nada como desarrollar tests, para lo cual me adentraré en el framework Mocha
  • Establecer una estructura de proyecto completamente agnóstica de Koa de forma que el código con la lógica de negocio pueda reutilizarse con otro framework (por ejemplo Express)

Actualizado: