Creando una libreria para NodeJS

Creando una libreria para NodeJS

Muchas veces nos encontramos resolviendo lo mismo, de la misma forma, en cada nuevo proyecto que enfrentamos. Algunas veces son simples objetos de configuracion para no exponer credenciales sensibles, u otras, como este caso, un objeto de mensajes customizados para las respuestas HTTP.

Requisitos

Lo primero que vamos a realizar, es dicha libreria, que no es mas que un proyecto de Node el cual posteriormente se subira al gestor de paquetes anteriormente mencionado (npm).

Comenzando a codear

Creamos un proyecto de Node ejecutando npm init -y en la carpeta que desees (si es una carpeta vacia, mejor), y pasamos a definir nuestra estructura de archivos y carpetas.

-馃搧src - index.ts
       - 馃搧 interfaces
       - 馃搧 messages
- .eslintrc.json
- .gitignore
- .prettierrc
- nodemon.json
- package-lock.json
- package.json
- tsconfig.json

En este punto, solo tendremos un codigo en los archivos de package que se han generado automaticamente al ejecutar el comando anterior.

Instalacion de depencencias

Para instalar todas las dependencias necesarias, ejecute los siguiendes comandos.

npm install ts-node
npm install --save-dev rimraf
npm install --save-dev nodemon
npm install --save-dev typescript
npm install --save-dev @types/node

Configuraciones

En esta seccion nos centraremos principalmente en las configuraciones necesarias del proyecto para que trabaje con types de Typescript de forma correcta. Y ademas, podamos hacer cambio sin necesidad de reiniciar la aplicaci贸n.

En nuestro package.json

{
  "main": "build/index.js",
  "scripts": {
    "build": "rimraf ./build && tsc",
    "dev": "nodemon",
    "start": "npm run build && node build/index.js"
  },
}

En nuestro tsconfig.json

{
  "compilerOptions": {
    "skipLibCheck": true,
    "target": "es6",
    "module": "commonjs",
    "moduleResolution": "node",
    "allowJs": true,
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "outDir": "./build",
    "rootDir": "src",
    "types": ["node"]
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"],
}

En nuestro nodemon.json

{
    "watch": [
        "src"
    ],
    "ext": ".ts,.js",
    "ignore": [],
    "exec": "ts-node --files ./src/index.ts"
}

En nuestro .eslintrc.json

{
    "extends": [
        "eslint:recommended",
        "plugin:@typescript-eslint/recommended",
        "prettier"
    ],
    "parser": "@typescript-eslint/parser",
    "plugins": [
        "@typescript-eslint",
        "prettier"
    ],
    "env": {
        "node": true,
        "es2021": true
    },
    "parserOptions": {
        "ecmaVersion": 2021,
        "sourceType": "module"
    },
    "rules": {
        "arrow-spacing": [
            "warn",
            {
                "before": true,
                "after": true
            }
        ],
        "comma-dangle": [
            "error",
            "always-multiline"
        ],
        "comma-spacing": "error",
        "comma-style": "error",
        "dot-location": [
            "error",
            "property"
        ],
        "handle-callback-err": "off",
        "indent": [
            "off",
            "tab"
        ],
        "keyword-spacing": "error",
        "max-nested-callbacks": [
            "error",
            {
                "max": 4
            }
        ],
        "max-statements-per-line": [
            "error",
            {
                "max": 2
            }
        ],
        "no-console": "off",
        "no-empty-function": "error",
        "no-floating-decimal": "error",
        "no-inline-comments": "error",
        "no-lonely-if": "error",
        "no-multi-spaces": "error",
        "no-multiple-empty-lines": [
            "error",
            {
                "max": 2,
                "maxEOF": 1,
                "maxBOF": 0
            }
        ],
        "no-shadow": [
            "error",
            {
                "allow": [
                    "err",
                    "resolve",
                    "reject"
                ]
            }
        ],
        "no-trailing-spaces": [
            "error"
        ],
        "no-var": "error",
        "object-curly-spacing": [
            "error",
            "always"
        ],
        "prefer-const": "error",
        "semi": [
            "error",
            "always"
        ],
        "space-before-blocks": "error",
        "space-before-function-paren": [
            "error",
            {
                "anonymous": "never",
                "named": "never",
                "asyncArrow": "always"
            }
        ],
        "space-in-parens": "error",
        "space-infix-ops": "error",
        "space-unary-ops": "error",
        "spaced-comment": "error",
        "yoda": "error",
        "@typescript-eslint/explicit-module-boundary-types": "off"
    }
}

En nuestro .prettierrc

{
    "parser": "typescript",
    "printWidth": 120,
    "singleQuote": true,
    "trailingComma": "all",
    "useTabs": true
}

Y hemos finalizado nuestras excesivas configuraciones, que si bien, no son del todo necesarias por ahora.

La logica de nuestra aplicaci贸n

En esta mini secci贸n, definiremos el codigo que ira dentro de la carpeta src y subyacentes, siguiendo el mismo formato de la secci贸n anterior.

En nuestro index.ts

import { SimpsonResponse } from './messages/simpson-response.message';

export default SimpsonResponse;

Dentro de la carpeta messages, en nuestro archivo simpson-response.message.ts

import { ISimpsonResponse } from '../interfaces/simpson-response.interface';

export const SimpsonResponse: ISimpsonResponse = {
    OK: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/200_xjzaks.png',
        code: 200,
        message: 'OK',
    },
    MOVED_PERMANENTLY: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/301_uk0qah.png',
        code: 301,
        message: 'Moved Permanently',
    },
    UNAUTHORIZED: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/401_pbvzql.png',
        code: 401,
        message: 'Unauthorized',
    },
    FORBIDDEN: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/403_w7pk5n.png',
        code: 403,
        message: 'Forbidden',
    },
    NOT_FOUND: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/404_bzafrw.png',
        code: 404,
        message: 'Not Found',
    },
    INTERNAL_SERVER_ERROR: {
        url: 'https://res.cloudinary.com/dzxzhwcjv/image/upload/v1669169546/thesimpsons/500_gomppl.png',
        code: 500,
        message: 'Internal Server Error',
    },
};

De esta forma, estamos creando un objeto custom con sus respectivas imagenes para algunos de los codigos de respuesta HTTP m谩s comunes. Dichas imagenes, se alojan en un tier gratuito de Cloudinary.

Dentro de la carpeta interfaces en nuestro archivo message.interface.ts

export interface IMessage {
    url: string;
    code: number;
    message: string;
}

Dentro de la carpeta interfaces en nuestro archivo simpson-response.interface.ts

import { IMessage } from './message.interface';

export interface ISimpsonResponse {
    [key: string]: IMessage;
}

Y hemos finalizado una mini libreria de NodeJs con exito! 馃敟 馃槑 Pero, 驴C贸mo la publicamos al gestor de paquetes de Node, y ademas, como hacemos uso de ella?

Subiendo nuestro codigo a git

Debemos enlazar nuestro repositorio remoto con nuestro repositorio local, pero antes, necesitamos crearlos, de la siguiente forma:

Creamos nuestro repositorio remoto de la siguiente forma:

  • Nos dirigimos a github y creamos nuestro repositorio

  • Damos clic en Create repository con la configuracion por defecto.

Creamos nuestro repositorio local de la siguiente forma:

  • Dentro de la carpeta que veniamos trabajando agregando el codigo, ejecutamos:
git init
  • Procedemos a agregar al stage y realizar un commit
git add .
git commit -m "node-library commit example"

Y por ultimo, a enlazar dicho repositorio local, con nuestro repositorio remoto, copiando y pegando la secuencia de comandos que nos provee github:

Y por ultimo, subimos el codigo a dicho repositorio ejecutando

git push

Es posible, que en este paso la consola nos devuelva un comando con un upstream, en caso de que asi sea, ejecute ese comando para efectuar el push al repositorio remoto.

Publicando en npm

Ya tenemos nuestro proyecto en github, y solo falta subirlo a npm para que cualquier usuario pueda utilizarlo sin necesidad de clonarse o forkear su repositorio.

Es importante mencionar, que para poder publicarla, debemos tener npm actualizado y de forma global, por lo que, para asegurarnos podemos ejecutar:

npm install npm@latest -g

Continuando desde la consola, debemos loguearnos en nuestra cuenta de npm

npm login

Esto nos pedira, en primera instancia nuestro usuario (de npm) y nuestra contrase帽a.

Nota al margen, es que no se mostraran caracteres mientras escribamos dicha contrase帽a, pero si estaras escribiendo la misma. Presiona enter y entraras en la consola interactiva de npm.

Por ultimo, solo falta publicar.

npm publish

Y aprovecho a comentar, que el nombre que la libreria tomara es el nombre que se le ha asignado al proyecto en nuestro package.json

Y listo, hemos publicado nuestro primer paquete de Node en NPM! 馃コ

Utilizando nuestra libreria

Por ultimo, los invito a utilizar dicha libreria titulada "Simpson-Response" en honor a las imagenes de Los Simpsons que representan fielmente algunos de los codigos de respuesta HTTP mas comunes.

Suponiendo que, o se tiene otro proyecto o se ha creado un server de prueba, puede instalar la libreria siguiendo el comando que le indique npm en su respectivo paquete.

En mi caso, el comando a ejecutar es

npm i simpson-response

Y para aquellos proyectos en typescript, la instalacion de types es

npm i --save-dev @types/simpson-response

Mientras que el uso es bastante sencillo 馃檶

// Usage
import SimpsonResponse from 'simpson-response'

app.get('/', (req: Request, res: Response) => {
    res.send(SimpsonResponse.default.UNAUTHORIZED);
});

// Example
{
  "url": "SimpsonImage",
  "code": 401,
  "message": "Unauthorized"
}

Libreria en NPM.

Repositorio de Github.

Despedida

Espero se hayan entretenido, les guste y est茅n esperando el pr贸ximo.

隆Hasta pronto 馃憢!