Realización de solicitudes HTTP en Node.js con node-fetch

En este tutorial, enviaremos solicitudes GET y POST en Node.js usando el paquete de recuperación de nodos de NPM. Es diferente de la función window.fetch() del lado del cliente y ofrece una funcionalidad similar.

Introducción

Una aplicación web a menudo necesita comunicarse con servidores web para obtener varios recursos. Es posible que deba obtener datos o publicar datos en un servidor web externo o API.

Usando JavaScript del lado del cliente, esto se puede lograr usando la API de búsqueda y la función window.fetch(). En NodeJS, varios paquetes/bibliotecas pueden lograr el mismo resultado. Uno de ellos es el paquete búsqueda de nodo.

node-fetch es un módulo liviano que nos permite usar la función fetch() en NodeJS, con una funcionalidad muy similar a window.fetch() en JavaScript nativo, pero con [pocas diferencias](https: //github.com/node-fetch/node-fetch/blob/master/docs/v3-LIMITS.md).

Primeros pasos con node-fetch

Para usar node-fetch en su proyecto, cd en el directorio de su proyecto y ejecute:

1
$ npm install node-fetch

{.icon aria-hidden=“true”}

A partir de la versión 3.0, node-fetch es un módulo exclusivo de ESM; no puede importarlo con require(). Si no usa ESM usted mismo, se recomienda permanecer en la versión 2.0 en lugar de la última, en cuyo caso puede usar la sintaxis estándar require().

Para usar el módulo en código (para versiones anteriores a la versión 3.0), use:

1
const fetch = require('node-fetch');

Si está utilizando ESM, importará el módulo de una manera diferente:

1
import fetch from 'node-fetch';

{.icon aria-hidden=“true”}

Nota: La API entre node-fetch 3.0 y 2.0 es la misma, solo difiere la importación.

Para instalar una versión específica del módulo, puede usar npm:

1
$ npm install [correo electrónico protegido]

Como se mencionó anteriormente, la función fetch() en el módulo node-fetch se comporta de manera muy similar a la función nativa window.fetch(). Su firma es:

1
fetch(url[, options]);

El parámetro url es simplemente la URL directa al recurso que deseamos obtener. Tiene que ser una URL absoluta o la función arrojará un error. El parámetro opcional options se usa cuando queremos usar fetch() para cualquier cosa que no sea una simple solicitud GET, pero hablaremos de eso más a fondo más adelante.

La función devuelve un objeto Response que contiene funciones útiles e información sobre la respuesta HTTP, como:

  • text() - devuelve el cuerpo de la respuesta como una cadena
  • json() - analiza el cuerpo de la respuesta en un objeto JSON y arroja un error si el cuerpo no se puede analizar
  • status y statusText: contienen información sobre el código de estado HTTP
  • ok - es igual a true si status es un código de estado 2xx (una solicitud exitosa)
  • headers: un objeto que contiene encabezados de respuesta; se puede acceder a un encabezado específico mediante la función get().

Envío de solicitudes GET usando node-fetch

Hay dos casos de uso comunes para obtener datos de un servidor web. Es posible que desee recuperar texto del servidor web, una página web completa o datos mediante el uso de la API REST. El paquete node-fetch te permite hacer todo eso.

Cree un directorio para su proyecto, cd en el directorio e inicialice un proyecto de Nodo con la configuración predeterminada:

1
$ npm init -y

Esto creará un archivo package.json en el directorio. A continuación, instale node-fetch como se muestra arriba y agregue un archivo index.js.

Obtener texto o páginas web

Hagamos una simple solicitud GET a la página de inicio de Google:

1
2
3
fetch('https://google.com')
    .then(res => res.text())
    .then(text => console.log(text));

En el código anterior, cargamos el módulo node-fetch y luego buscamos la página de inicio de Google. El único parámetro que hemos agregado a la función fetch() es la URL del servidor al que estamos haciendo una solicitud HTTP. Debido a que node-fetch está basado en promesas, estamos encadenando un par de funciones .then() para ayudarnos a administrar la respuesta y los datos de nuestra solicitud.

En esta línea, estamos esperando recibir la respuesta del servidor web de Google y convertirla a formato de texto:

1
.then(res => res.text());

Aquí estamos esperando el resultado de la conversión anterior e imprimiéndolo en la consola:

1
.then(text => console.log(text));

Si ejecutamos el código anterior desde la consola:

1
$ node index.js

Obtendremos el marcado HTML completo de la página de inicio de Google registrado en la consola:

1
2
3
4
5
6
<!doctype html>
<html itemscope="" itemtype="http://schema.org/WebPage" lang="en-RS">
    <head>
        <meta charset="UTF-8">
        <meta content="origin" name="referrer">
        <!-- Rest of the page -->

Obtención de datos JSON de la API REST

Otro caso de uso común para el módulo node-fetch es obtener datos usando la API REST.

Recuperaremos datos de usuario falsos de la API REST JSONMarcador de posición. Como antes, la función fetch() toma la URL del servidor y espera una respuesta.

Veamos cómo funciona eso:

1
2
3
4
5
6
7
8
fetch('https://jsonplaceholder.typicode.com/users')
    .then(res => res.json())
    .then(json => {
        console.log("First user in the array:");
        console.log(json[0]);
        console.log("Name of the first user in the array:");
        console.log(json[0].name);
})

El cuerpo de la respuesta HTTP contiene datos con formato JSON, es decir, una matriz que contiene información sobre los usuarios. Con esto en mente, usamos la función .json(), y esto nos permitió acceder fácilmente a elementos individuales y sus campos.

Ejecutar este programa nos daría:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
First element in the array:
{
  id: 1,
  name: 'Leanne Graham',
  username: 'Bret',
  email: '[correo electrónico protegido]',
  address: {
    street: 'Kulas Light',
    suite: 'Apt. 556',
    city: 'Gwenborough',
    zipcode: '92998-3874',
    geo: { lat: '-37.3159', lng: '81.1496' }
  },
  phone: '1-770-736-8031 x56442',
  website: 'hildegard.org',
  company: {
    name: 'Romaguera-Crona',
    catchPhrase: 'Multi-layered client-server neural-net',
    bs: 'harness real-time e-markets'
  }
}

Name of the first person in the array:
Leanne Graham

También podríamos haber impreso todo el JSON devuelto por res.json().

Envío de solicitudes POST mediante node-fetch

También podemos usar la función fetch() para publicar datos en lugar de recuperarlos. Como mencionamos anteriormente, la función fetch() permite agregar un parámetro adicional para realizar solicitudes POST a un servidor web. Sin este parámetro opcional, nuestra solicitud es una solicitud GET, por defecto.

Hay muchas opciones posibles que podemos configurar usando este parámetro, pero las únicas que usaremos en este artículo son método, cuerpo y encabezados.

Estos campos tienen significados directos: método establece qué tipo de solicitud HTTP estamos usando (POST en nuestro caso), cuerpo contiene el cuerpo/datos de nuestra solicitud y encabezados contiene todos los encabezados necesarios, que en nuestro caso es solo el Content-Type para que no haya ninguna confusión al analizar nuestra solicitud.

Para obtener una lista completa de opciones, puede visitar la documentación.

Demostraremos cómo funciona esto agregando un nuevo elemento a Tareas pendientes de JSONPlaceholder. Agreguemos un nuevo elemento a esa lista para el usuario cuyo id es igual a 123. Primero, necesitamos crear un objeto todo y luego convertirlo a JSON al agregarlo al campo cuerpo:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
let todo = {
    userId: 123,
    title: "loren impsum doloris",
    completed: false
};

fetch('https://jsonplaceholder.typicode.com/todos', {
    method: 'POST',
    body: JSON.stringify(todo),
    headers: { 'Content-Type': 'application/json' }
}).then(res => res.json())
  .then(json => console.log(json));

El proceso es muy similar a realizar una solicitud GET. Llamamos a la función fetch(), con la URL apropiada y configuramos las opciones necesarias usando el parámetro opcional de la función fetch(). Usamos JSON.stringify() para convertir nuestro objeto en una cadena con formato JSON antes de enviarlo al servidor web. Luego, al igual que con la recuperación de datos, esperamos la respuesta, la convertimos a JSON y la imprimimos en la consola.

Ejecutar el código nos da la salida:

1
2
3
4
5
6
{
  userId: 123,
  title: 'loren impsum doloris',
  completed: false,
  id: 201
}

Manejo de excepciones y errores {#manejo de excepciones y errores}

Nuestras solicitudes a veces pueden fallar, por una variedad de razones: un error que ocurre en la función fetch(), problemas de Internet, errores internos del servidor y otros. Necesitamos una forma de manejar estas situaciones, o al menos ser capaces de ver que ocurrieron.

Podemos manejar las excepciones de tiempo de ejecución agregando catch() al final de la cadena de promesas. Agreguemos una función catch() simple a nuestro programa anterior:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
let todo = {
    userId: 123,
    title: "loren impsum doloris",
    completed: false
}

fetch('https://jsonplaceholder.typicode.com/todos', {
    method: 'POST',
    body: JSON.stringify(todo),
    headers: { 'Content-Type': 'application/json' }
}).then(res => res.json())
  .then(json => console.log(json))
  .catch(err => console.log(err));

Idealmente, no debería simplemente ignorar e imprimir errores, sino tener un sistema para manejarlos.

Debemos tener en cuenta que si nuestra respuesta tiene un código de estado 3xx/4xx/5xx, la solicitud falló o el cliente debe tomar medidas adicionales.

Es decir, los códigos de estado HTTP 3xx indican que el cliente debe tomar medidas adicionales, los códigos 4xx indican una solicitud no válida y los códigos 5xx indican errores del servidor. Todos estos códigos de estado nos dicen que nuestra solicitud no tuvo éxito en términos prácticos.

catch() no registrará ninguno de estos casos porque la comunicación con el servidor fue bien, es decir, hicimos una solicitud y obtuvimos una respuesta exitosa. Esto significa que debemos tomar medidas adicionales para asegurarnos de cubrir la situación en la que la comunicación entre el cliente y el servidor fue exitosa, pero no recibimos ninguno de los códigos de estado HTTP exitosos (2xx).

Una forma común de asegurarse de que las solicitudes fallidas generen un error es crear una función que verifique el estado HTTP de la respuesta del servidor. En esa función, si el código de estado no indica éxito, podemos arrojar un error y catch() lo detectará.

Podemos usar el campo ‘ok’ mencionado anteriormente de los objetos ‘Respuesta’, que es igual a ‘verdadero’ si el código de estado es 2xx.

Veamos cómo funciona esto:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
function checkResponseStatus(res) {
    if(res.ok){
        return res
    } else {
        throw new Error(`The HTTP status of the reponse: ${res.status} (${res.statusText})`);
    }
}

fetch('https://jsonplaceholder.typicode.com/MissingResource')
    .then(checkResponseStatus);
    .then(res => res.json());
    .then(json => console.log(json));
    .catch(err => console.log(err));

Usamos la función al comienzo de la cadena de promesas (antes de analizar el cuerpo de la respuesta) para ver si encontramos un problema. También puede lanzar un error personalizado en su lugar.

Nuevamente, debe tener una estrategia para manejar errores como este en lugar de simplemente imprimir en la consola.

Si todo salió como se esperaba y el código de estado indicó éxito, el programa continuará como antes.

Conclusión

Hacer solicitudes a los servidores web es una tarea común de desarrollo web y en este artículo hemos visto cómo podemos hacerlo de manera efectiva usando node-fetch, una biblioteca que hace que la API nativa de búsqueda del navegador sea compatible con NodeJS.

Además de eso, también hemos analizado cómo manejar los errores que pueden ocurrir con las solicitudes HTTP.