API Rest usando Typescript

En el post de hoy vamos a construir una API rest usando Typescript, Express y con el patrón de Inyección de Dependencia.

· 7 min de lectura
API Rest usando Typescript

En este artículo, vamos a construir una API REST con Node JS, Typescript y la API se construirá con POO (Programación Orientada a Objetos) y Programación Funcional. Además, vamos a hacer uso del patrón de inyección de dependencia comúnmente utilizado que nos permitirá crear APIs escalables y débilmente acopladas.

Quizá piense que ya hay muchos patrones de inicio disponibles. ¿Por qué tenemos que buscar esto? Todos los patrones/diseño por ahí son grandes.

Pero cuando estaba pasando, la implementación de la API mediante el consumo de OOP en la programación funcional hará que nuestra API poco escalable y nos permiten escribir código de calidad.

via GIPHY

Lo que necesitas


Para empezar, primero tienes que inicializar tu proyecto con:

  • Instalar NodeJS y NPM desde Node
  • Un editor de código de tu elección. Aquí estamos usando VS Code.
  • Para probar la API, usaré Postman. Puedes usar cualquier otra herramienta de tu elección.

Cómo empezar :


Primero crearemos un nuevo directorio y lo nombraremos ha node-restbolierplate. Abre la carpeta recién creada con VS code o cualquiera de su editor favorito.

$ mkdir node-restboilerplate
$ cd node-restboilerplate

Navega en el directorio recién creado y abre el terminal de tu editor y ejecuta el siguiente comando.

npm init

Se te harán una serie de preguntas sobre el proyecto. Puedes aceptar todos los valores predeterminados hasta que llegue al punto de entrada o bien puedes actualizar tus datos sobre el proyecto.

Tu salida de npm init se verá algo así.

Una vez que llegue al punto final pulsa "Enter". Se creará un nuevo archivo llamado package.json en el nivel raíz del directorio de su proyecto. Este archivo contendrá la información sobre las dependencias de tu proyecto.

Ahora vamos a instalar typescript globalmente ejecutando el siguiente comando. Añadir la bandera -g para instalar los paquetes globalmente asegura que Typescript esté disponible para cualquier proyecto Node.js.

npm i -g typescript

Vamos a crear el archivo tsconfig.json en el nivel raíz de nuestro directorio. Este archivo especifica los archivos raíz y las opciones del compilador necesarias para compilar el proyecto. Abre el terminal y ejecuta:

tsc --init

Esto creará un archivo tsconfig en la raíz del directorio de nuestro proyecto. Añade la siguiente configuración dentro del archivo tsconfig

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "pretty": true,
    "sourceMap": true,
    "outDir": "dist",
    "importHelpers": true,
    "strict": true,
    "noImplicitAny": false,
    "strictNullChecks": false,
    "noImplicitThis": false,
    "alwaysStrict": true,
    "noUnusedLocals": true,
    "noUnusedParameters": false,
    "noImplicitReturns": true,
    "noFallthroughCasesInSwitch": true,
    "moduleResolution": "node",
    "baseUrl": ".",
    "allowSyntheticDefaultImports": true,
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "resolveJsonModule": true,
    "paths": {}
  }
}

Vamos a crear una carpeta llamada dist en la raíz del proyecto. Cuando ejecutemos la aplicación, el compilador pondrá los archivos Javascript transpilados en la carpeta dist.

Ahora, vamos a instalar todas las dependencias que vamos a utilizar durante la construcción de la API REST.

npm i express axios ts-node routing-controllers reflect-metadata module-alias body-parser class-transformer class-validator typedi tslib --save
npm i typescript @types/node --save-dev

Tu package.json ahora se parece a esto.

package.json

Vamos a empezar...


Una vez instaladas todas las dependencias. Crea una carpeta api en la raíz del proyecto y dentro de la carpeta api crea un archivo app.ts .

Añade el siguiente código en el archivo app.ts.

import 'reflect-metadata';
import 'module-alias/register';
import * as express from 'express';
import * as bodyParser from 'body-parser';
import Container from 'typedi';
import { ENV_CONFIG } from '../app/config';
import { Logger } from '../libs/logs/logger';
import { useExpressServer, useContainer as routingContainer } from 'routing-controllers';
import * as http from 'http';

const baseDir = __dirname;
const expressApp = express();

// Handling the DependencyInjection across the entire application
routingContainer(Container);

// Loads all the Controllers from the directories and provides the routing facility
useExpressServer(expressApp, {
  routePrefix: ENV_CONFIG.app.apiRoot,
  defaultErrorHandler: false,
  controllers: [baseDir + `/**/controllers/*{.js,.ts}`]
});

expressApp.use(bodyParser.urlencoded({ extended: false }));
expressApp.use(bodyParser.json());

const server = http.createServer(expressApp);
server.listen(ENV_CONFIG.app.port, () => {
  Logger.info('Server', 'Application running on', `${ENV_CONFIG.app.hostname}:${ENV_CONFIG.app.port}`);
});

// Handling the unHandledRejection errors
process.on('unhandledRejection', (error, promise) => {
  Logger.error('Server', 'unhandledRejectionError :', `${error}`);
});
Nota: Actualmente se ha creado una clase logger para manejar los registros. En el archivo anterior lo hemos importado. Por eso podemos ver el mensaje de registro en la terminal. Puedes reemplazar el Logger con console.log para ver la salida en la terminal.

Crear una carpeta app en el nivel raíz del proyecto y dentro de esa carpeta crear el archivo config.ts Aquí definiremos todas las cosas relacionadas con la configuración del proyecto.

Añade el siguiente código en el archivo config.ts

export const ENV_CONFIG = {
  app: {
    port: 8500,
    hostname: 'http://localhost',
    apiRoot: '/v1',
  }
};

A partir de aquí, iremos añadiendo más cosas nuevas a nuestro proyecto. Cada vez que hagamos cambios tendremos que reiniciar el servidor, lo que será una actividad tediosa. 😩

Así que vamos a utilizar una librería llamada nodemon, que reduce mucho el tiempo de desarrollo reiniciando automáticamente nuestra aplicación cada vez que cualquier archivo relacionado con nuestro proyecto es alterado y guardado.

Ejecuta el siguiente comando para instalarlo.

$ npm i nodemon --save-dev

Una vez instalado, haremos algunos cambios en el archivo package.json

Añade la siguiente línea de código dentro de la sección de script.

“scripts”: {
   "dev": "tsc & nodemon api/app.ts"
 },

Guarda el archivo package.json y ejecuta el siguiente comando.

$ npm run dev

Deberías poder ver la siguiente salida en su terminal.

Nodemon Server Started

Ahora intenta hacer algunos cambios en el archivo app.js, el nodemon reiniciará automáticamente el servidor por nosotros.

Si tu quieres ampliar este conocimiento te invito a que puedas checar el video que te dejo a continuación

El controlador


Ahora necesitamos el punto de entrada de nuestro HTTP para realizar algunas de las acciones de la API. Bajo la carpeta api, crea una subcarpeta llamada userProfile y dentro de ella crea las carpetas controller, model, service.

import { JsonController, Get, QueryParam } from "routing-controllers";
import { Service } from 'typedi';
import { URL_INFO } from '../userApiInfo';
import { Logger } from '../../../libs/logs/logger';
import { userInfoSvc } from "../services/userInfoSvc";

@JsonController(URL_INFO.contextPath + '/userInfo')
@Service()
export class UserInfoController {
  constructor(public _userInfoSvc: userInfoSvc) { }

  @Get('/getUserInfo')
  public async getUserInfo(): Promise<any> {
    try {
      const resp = await this._userInfoSvc.userInfoExecuter();
      Logger.info('Controller: getUserInfo', 'response:' + JSON.stringify(resp));
      return Promise.resolve(resp);
    } catch (error) {
      Logger.error('Controller: getUserInfo', 'errorInfo:' + JSON.stringify(error));
      return Promise.reject(error);
    }
  }

  @Get('/getUserInfoById')
  public async getUserInfoById(@QueryParam('id') userId: number): Promise<any> {
    try {
      const resp = await this._userInfoSvc.userInfoExecuterById(userId);
      Logger.info('Controller: getUserInfoById', 'response:' + JSON.stringify(resp));
      return Promise.resolve(resp);
    } catch (error) {
      Logger.error('Controller: getUserInfoById', 'errorInfo:' + JSON.stringify(error));
      return Promise.reject(error);
    }
  }
}

Así que vamos a ver lo que tenemos aquí :

@JsonController: Esto indicará que nuestros datos devueltos por las acciones de nuestro controlador siempre serán transformados a JSON.
@Service: Es un decorador, utilizado para crear instancias para clases desconocidas.
@Get: Indica el método Http que está vinculado a nuestro método.

El modelo


Vamos a crear el objeto de petición y respuesta de nuestra API REST.

export interface IApiInfo {
  contextPath: string;
}

export interface UserPosts {
  data?: Array<UserObject>
}

export interface UserPost {
  data?: UserObject
}

export interface UserObject {
  id?: number;
  user_id?: number;
  title?: string;
  body?: string
}
view raw

Los servicios


Aquí realizaremos nuestra actividad de obtención de datos. Si estamos obteniendo los datos de la base de datos o de la API externa. Todas esas actividades deben tener lugar aquí. Actualmente vamos a utilizar API's externas para obtener los datos.

import axios from 'axios';
import { Logger } from '../../../libs/logs/logger';
import { Service } from 'typedi';
import { UserPosts, UserObject, UserPost } from '../models/iUserInfo';

@Service()
export class userInfoSvc {
  constructor() {}

  async userInfoExecuter(): Promise<Array<UserObject>> {
    try {
      const userPostResponseList: UserPosts = await axios.get('https://gorest.co.in/public/v2/posts');
      return Promise.resolve(userPostResponseList.data);
    } catch (error) {
      Logger.error('Service: userInfoExecuter', 'errorInfo:' + JSON.stringify(error));
      return Promise.reject(error);
    }
  }

  async userInfoExecuterById(userId?: any): Promise<UserObject> {
    try {
      const userPostResponse: UserPost = await axios.get(`https://gorest.co.in/public/v2/posts/${userId}`);
      return Promise.resolve(userPostResponse.data);
    } catch (error) {
      Logger.error('Service: userInfoExecuterById', 'errorInfo:' + JSON.stringify(error));
      return Promise.reject(error);
    }
  }
}

¡Vamos a probar!


Es el momento de golpear nuestro punto final en el cartero. Asegúrate de que el servicio está en marcha. Hagamos un ping a nuestros endpoints.

¡Boom! Funciona. Si le das a la petición en postman y compruebas el terminal. Podrás ver la respuesta registrada.

Server log 

La estructura de carpetas:

Application Structure

La de arriba es la estructura de carpetas de la plantilla.

Aquí he utilizado la clase para generar los registros. Es solo un ejemplo. Tenemos un paquete llamado Winston, podemos usarlo también. Si tienes alguna idea mejor, puedes implementar ese enfoque también.

Cuando la aplicación sigue creciendo, podemos crear una carpeta individual para los diferentes casos de uso. Aquí hemos creado sólo una carpeta. Si necesitamos realizar (DTO o masajear la solicitud o respuesta), podemos crear una carpeta de clase en la parte superior de la carpeta del controlador y allí podemos lograr lo que sea de la manera que queremos.

Toda la lógica debe ser manejada en el archivo controller Las llamadas a la base de datos o la invocación de la API deben producirse en el archivo service Todo el punto final debe ser configurado en el archivo config.ts  Aquí, con el propósito de aprender, hemos mantenido en el archivo service

via GIPHY

Conclusión:


A través de este tutorial, tu has participado en la construcción de la API RESTful. Como hemos mencionado en la parte superior, es una plantilla de arranque para crear la estructura de esqueleto para nuestra aplicación. Para obtener más información sobre la creación de la clase basada en el enfoque que puedes ir al routing-controller.

Fuente

Plataforma de cursos gratis sobre programación