Un primer proyecto con Node.js
Introducción
La finalidad de este capítulo es poner en práctica los diferentes conceptos aprendidos en los capítulos anteriores del libro. El objetivo es desarrollar una API utilizando TypeScript y Node.js. Se construirá en varias etapas para poder explicar paso a paso los conceptos que se ponen en práctica.
Una API o application programming interface (interfaz de programación de aplicación) se caracteriza por un conjunto de clases/métodos/funciones que ofrecen servicios. El objetivo principal es reducir la complejidad de implementar estos servicios proporcionando una fachada que puedan utilizar aplicaciones de terceros. Estos servicios se implementan a través de puntos finales (endpoints).
Para desarrollar esta API, es necesario apoyarse en una arquitectura REST (REpresentational State Transfer). Este estilo de arquitectura se basa en verbos y códigos HTTP para simplificar la comprensión y el uso de una API.
Como parte de esta práctica, se creará un directorio empresarial para administrar los empleados de una sociedad.
Para manipular una API Rest, es preciso utilizar un cliente. No es necesario instalar software para ello; más adelante, en este capítulo, se configurará un explorador de API, que estará disponible directamente cuando se inicie el programa. Sin embargo, antes de configurar este explorador, realizará las primeras pruebas de llamada...
Configurar el proyecto
En esta sección, configurará la estructura del proyecto.
Cree un directorio de su elección que contendrá toda la aplicación.
Abra este directorio con Visual Studio Code.
Luego, cree un subdirectorio src.
En la carpeta src, agregue el archivo index.ts.
Desde Visual Studio Code, abra el panel de comandos (usando el método abreviado de teclado [Ctrl][Mayús] P en Linux/Windows y [Cmd][Mayús] P en macOS).
Abra la terminal de Visual Studio Code ejecutando el comando Toggle Terminal:

Los distintos comandos que se detallan en las siguientes secciones se pueden introducir en cualquier terminal. Sin embargo, se recomienda utilizar la terminal integrado de Visual Studio Code, ya que es más cómodo.
1. Crear el archivo package.json
Para configurar una aplicación Node.js, es necesario utilizar el comando NPMinit para crear un archivo package.json en la raíz del proyecto. Este archivo contiene cierta información específica de la aplicación (nombre, descripción, autor, versión, licencia, dependencias...).
En la terminal integrada de Visual Studio Code, introduzca el siguiente comando:
npm init -y
A partir de este paso, cualquier comando que se deba ejecutar en la terminal integrada de Visual Studio Code se debe ubicar en la raíz del proyecto.
Abra el archivo package.json y cambie la propiedad name a employee-directory:
"name": "employee-directory"
Luego agregue la siguiente propiedad:
"private": true
Este campo es imprescindible para evitar cualquier publicación de la aplicación. Así, limita el riesgo de manipulación accidental.
Antes de continuar, verifique que el archivo package.json contenga la siguiente información:
{
"name": "employee-directory",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author":...
Crear el framework MVC
El objetivo de esta sección es utilizar ciertos conceptos vistos en capítulos anteriores de este libro (como decoradores, tipos literales...). Sin embargo, para un proyecto empresarial, no se recomienda crear un framework MVC propio, sino utilizar una biblioteca de terceros (por ejemplo, Nest.js, Loopback, Sails.js, FoalTS...).
Model-View-Controller (MVC) es un patrón de diseño muy popular para desarrollar aplicaciones web. En una aplicación MVC, las responsabilidades se dividen entre los siguientes elementos:
-
El modelo: solo contiene los datos de la aplicación y no tiene lógica.
-
La vista: presenta los datos contenidos en la parte del modelo.
-
El controlador: contiene la lógica de manipulación de datos y reacciona a las acciones del usuario para actualizar el modelo.
Estas partes interactúan según el siguiente diagrama:

En esta sección, creará la parte del controlador. La pieza del modelo se colocará más adelante.
1. Registro de rutas
El registro de rutas sirve para crear todos los controladores y sus acciones. Esto permite montar todas las rutas de la aplicación con el enrutador proporcionado por Fastify.
En el directorio src, cree un directorio core.
En el directorio core, cree un directorio mvc.
En el directorio mvc, cree el archivo types.ts.
Dentro del archivo, agregue el alias de tipo HttpVerb que contiene el tipo literal "get" y expórtelo:
export type HttpVerb = "get";
Otros verbos HTTP se definirán más adelante, cuando sean necesarios para el correcto funcionamiento de la aplicación.
A continuación, agregue una interfaz que defina la información sobre una ruta. Permite recuperar el nombre del controlador, el de la acción que se ha de ejecutar, así como el verbo http y la ruta que permite acceder a este en el contexto de una aplicación web. Esta información es útil para crear rutas en el enrutador de Fastify:
export interface IRoute {
controller: string;
action: string;
httpVerb: HttpVerb;
path: string;
}
En el directorio mvc, cree el archivo routeCollection.ts.
Importe la interfaz IRoute desde el archivo types.ts:
import type { IRoute } from "./types";...
Validación y OpenApi
En esta parte, se trata de configurar la creación de empleados en el directorio de la empresa. Para hacer esto, es necesario crear una acción que se ejecutará durante una llamada HTTP de tipo POST. Los datos proporcionados en el cuerpo de la solicitud se validan mediante un esquema JSON. Fastify implementa de forma nativa la validación de documentos JSON a través de la especificación JSON Schema (https://json-schema.org/).
Una de las ventajas de confiar en la validación de esquemas es que también permite, a través de otras bibliotecas del ecosistema Fastify, configurar una descripción y un explorador API de tipo OpenApi (https://www.openapis.org/).
1. Tipado de esquemas
La validación de esquemas es una característica que requiere varias partes. Para comenzar, definiremos los elementos utilizados para crear el esquema JSON para validar un modelo.
En el directorio core, cree un directorio schema.
En el directorio schema, cree un archivo types.ts.
Agregue el alias de tipo SchemaBaseProperty. Este tipo define la estructura básica de una propiedad de esquema. Agregue una propiedad description de tipo string. Será útil para mostrar la información de la propiedad en el explorador de API.
type SchemaBaseProperty = {
description: string;
};
A continuación, defina un alias de tipo SchemaIntegerProperty para validar un valor de tipo Integer. Agregue una propiedad tipo que tenga el tipo "integer" y una propiedad minimum opcional para validar que el número entero sea mayor o igual a un valor determinado.
type SchemaIntegerProperty = {
type: "integer";
minimum?: number;
};
Ahora defina un alias de tipo SchemaStringProperty para validar un valor de tipo String. Agregue una propiedad type con tipo "string" y una propiedad pattern opcional para validar que la cadena de caracteres corresponde a una expresión regular.
type SchemaStringProperty = {
type: "string";
pattern?: string;
};
Ahora que estos tres tipos están definidos, cree un nuevo alias SchemaPropertyOptions correspondiente a la intersección del tipo SchemaBaseProperty...
Acceso a datos y su persistencia
Acceder a los datos y conservarlos directamente desde un controlador se considera una mala práctica porque multiplica las responsabilidades del controlador y no permite la reutilización de ese código.
Se trata, por tanto, de implementar una verdadera capa de acceso a los datos utilizando el patrón de diseño Repository. Este permite desacoplar la persistencia de los datos del resto de la aplicación al ofrecer una capa de abstracción (correspondiente a un repositorio de datos) y se puede utilizar independientemente del modo de persistencia de los datos (base de datos SQL, archivo plano, datos en memoria...). Este patrón de diseño aumenta la capacidad de prueba y mantenimiento del programa. Gracias a la genericidad, centraliza la forma de manejar los diferentes objetos de negocio de la aplicación, evitando así la duplicación de código.
Este patrón de diseño no debe confundirse con herramientas llamadas ORM (Object Relational Mapping o Mapeo Relacional de Objetos). Los ORM facilitan el uso de una base de datos SQL en una aplicación mediante programación orientada a objetos. Pueden generar automáticamente consultas SQL. A diferencia de los ORM, el patrón Repository no está vinculado a un modo de persistencia.

1. Tipado de las entidades de trabajo
En el directorio core, cree un directorio data.
En el directorio data, cree el archivo types.ts.
En el archivo types.ts, agregue una interfaz IEntity. Esta interfaz define una propiedad id que será común a todas las entidades del proyecto:
export interface IEntity {
id: string;
}
En el directorio src, cree un directorio entities.
En el directorio entities, cree un archivo employee.ts.
En el archivo employee.ts, importe la interfaz IEntity.
import type { IEntity } from "../core/data/types";
Agregue la clase Employee y expórtela. Luego implemente la interfaz IEntity:
export class Employee implements IEntity {
id!: string;
}
Agregue las propiedades firstName, lastName, email de tipo string y salary de tipo number a la clase Employee.
firstName!: string;
lastName!: string;
email!: string;
salary!: number;
Antes de continuar...
Inversión de dependencias
En la sección anterior, la propia clase Repository crea una instancia de la clase MemoryStorage, para poder usarla como una abstracción en el resto de la clase (definida por la interfaz IStorage). Esta implementación no respeta el principio SOLID de inversión de dependencias (consulte el capítulo Programación orientada a objetos).
En esta sección se trata de configurar la inyección de dependencias para respetar este principio y, por lo tanto, desacoplar Repository del almacenamiento de datos. Además, definirá una implementación de la interfaz IStorage para conservar datos en un archivo. Luego, esta implementación se puede cambiar en el nivel de un contenedor de dependencias para inyectarla en el repositorio.
Al igual que con los frameworks MVC u ORM, existen bibliotecas en TypeScript que permiten administrar dependencias (por ejemplo, InversifyJS). El objetivo de esta sección es poner en práctica algunos de los conceptos vistos en capítulos anteriores del libro, pero no reemplazar dichas bibliotecas.
1. El contenedor de dependencias
En el directorio core, cree un directorio llamado ioc.
El acrónimo IoC significa Inversion of Control (inversión de control en español). Es un patrón que permite implementar la inversión de dependencias a través de un tercero dedicado a la gestión de dependencias (más comúnmente llamado contenedor de dependencias).
En el directorio ioc, cree un archivo llamado dependencyContainer.ts.
En el archivo dependencyContainer.ts, agregue una clase DependencyContainer y expórtela.
export class DependencyContainer {
}
Defina una propiedad privada estática #instance en la clase DependencyContainer. Esta propiedad almacenará la única instancia de la clase:
static #instance: DependencyContainer;
A continuación, agregue un constructor privado.
private constructor() {
}
Finalmente, agregue un método estático getInstance que cree la instancia en la clase, si no está definida en la propiedad #instance, y luego la devuelva.
static getInstance(): DependencyContainer {
if (!this.#instance) {
this.#instance...
Para ir más lejos
¡Felicidades, ha llegado al final de este ejercicio y ha podido implementar muchas de las funciones descritas en este libro!
Para continuar con el proyecto y, especialmente, con la práctica del lenguaje TypeScript, aquí tiene una lista (no exhaustiva) de ideas de mejoras con las que puede experimentar:
-
Abstraer e inyectar la clase Repository.
-
Añadir una descripción de Swagger a la API.
-
Administrar esquemas de retorno de acciones para tener la descripción de las respuestas HTTP.
-
Persistir los datos en una base de datos.
-
Crear una aplicación frontend que utilice la API Rest (por ejemplo, con React, Angular o Vue).
-
Agregar el verbo HTTP PUT y manejar la actualización de los datos.
-
Gestionar la copia de los datos contenidos en los parámetros de consulta y de ruta en los modelos.
-
…