Imagen de  Fetch API

Fetch API: La Alternativa para Solicitudes HTTP

Cuando una aplicación web necesita hablar con un servidor para enviar o recibir datos, lo hace usando solicitudes HTTP. Antes, la forma común de hacerlo era con algo llamado XMLHttpRequest (XHR), más conocido como AJAX. Esto permitía cargar datos sin tener que recargar toda la página, algo que todavía se usa en algunos casos. Sin embargo, el proceso con AJAX significaba, escribir más código, manejar callbacks y lidiar con una sintaxis menos amigable.

Hoy en día, la Fetch API es una alternativa que hace lo mismo, pero de manera más simple y ordenada. Fetch usa promesas, lo que hace que manejar las respuestas y los errores sea más fácil de entender y trabajar. Esto no quiere decir que AJAX esté "mal" o que no se use, pero Fetch se ha convertido en la opción preferida por su claridad y porque encaja mejor con las prácticas actuales de desarrollo web.

 

Comparación entre AJAX y Fetch API

Aspecto AJAX (XHR) Fetch API
Sintaxis Sintaxis más antigua, basada en callbacks y métodos como open() y send(). Sintaxis moderna y más limpia basada en promesas.
Manejo de Respuestas Requiere verificar manualmente el estado con readyState y status. Simplifica el manejo de respuestas con .then() o async/await.
Manejo de Errores El manejo de errores es más manual y requiere más código. Permite manejar errores fácilmente con .catch() o try-catch.

Imaginemos que tenemos una página web donde los usuarios pueden buscar recetas. Cuando la página carga, quieres obtener una lista de recetas populares desde un servidor y mostrarlas en la página.
Así sería una petición desde Ajax

var xhr = new XMLHttpRequest();
xhr.open("GET", "https://api.ejemplo.com/recetas/populares", true);
xhr.onload = function() {
  if (xhr.status === 200) {
    var recetas = JSON.parse(xhr.responseText);
    console.log("Recetas populares:", recetas);
  } else {
    console.error("Error al cargar las recetas:", xhr.status);
  }
};
xhr.onerror = function() {
  console.error("Error en la solicitud");
};
xhr.send();

Usando Fetch API

fetch("https://api.ejemplo.com/recetas/populares")
  .then(response => {
    if (!response.ok) {
      throw new Error("Error en la solicitud: " + response.status);
    }
    return response.json();
  })
  .then(recetas => {
    console.log("Recetas populares:", recetas);
  })
  .catch(error => {
    console.error("Error al cargar las recetas:", error);
  });

Ambos ejemplos hacen lo mismo, pero con Fetch puedes trabajar de forma más fluida y sencilla.

Conceptos Básico

Cuando usamos Fetch API para hacer una solicitud, necesitamos saber qué información incluir y cómo configurarla. Fetch se basa en una función que recibe dos cosas: la URL a la que queremos conectarnos y, opcionalmente, un objeto con opciones que define cómo será esa solicitud.

Diagrama de Flujo simple sobre la api

Método (Method):
Indica el tipo de acción que queremos realizar. Los métodos más usados son:

  • GET: Para obtener información del servidor (por ejemplo, una lista de recetas).
  • POST: Para enviar datos al servidor (como guardar una nueva receta).
  • PUTPara reemplazar un recurso existente en el servidor con datos nuevos (como actualizar toda la información de una receta).
  • PATCH: Para modificar parcialmente un recurso en el servidor (como actualizar solo el tiempo de una receta).
  • DELETE: Para eliminar un recurso del servidor (como borrar una receta).

Ejemplo de configuración con un método:

fetch("https://api.ejemplo.com/recetas", {
  method: "POST",
});

Headers (Encabezados):

Aquí indicamos detalles adicionales sobre nuestra solicitud, como el tipo de contenido que estamos enviando. Por ejemplo, si estamos enviando datos en formato JSON, necesitamos incluir algo así:

headers: {
  "Content-Type": "application/json",
}

Body (Cuerpo):

Es la información que enviamos al servidor en métodos como POST o PUT. Generalmente se usa para enviar datos en formato JSON, por lo que necesitamos convertirlos usando JSON.stringify().

body: JSON.stringify({ nombre: "Pizza Margherita", tiempo: "30 minutos" }),

Options (Opciones):

Este es el objeto donde ponemos todo lo anterior. Además, se pueden incluir otros ajustes como:

  • mode: Define si la solicitud es de tipo cors (para APIs externas), same-origin o no-cors.
  • credentials: Indica si queremos enviar cookies o autenticación (include, same-origin).  
  • cache: Controla cómo se maneja el almacenamiento en caché.
fetch("https://api.ejemplo.com/recetas", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ nombre: "Pizza Margherita", tiempo: "30 minutos" }),
});

Configuración de los métodos

GET:
Para obtener datos, no necesitamos incluir un body. Solo la URL y, si es necesario, algunos encabezados.

fetch("https://api.ejemplo.com/recetas", {
  method: "GET",
});

POST:
Enviamos datos al servidor, por ejemplo, para crear un nuevo registro.

fetch("https://api.ejemplo.com/recetas", {
  method: "POST",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ nombre: "Pizza", tiempo: "30 minutos" }),
});

PUT:
Para actualizar un recurso existente. La estructura es similar a POST.

fetch("https://api.ejemplo.com/recetas/1", {
  method: "PUT",
  headers: {
    "Content-Type": "application/json",
  },
  body: JSON.stringify({ nombre: "Pizza Actualizada", tiempo: "25 minutos" }),
});

DELETE:
Sirve para eliminar algo. Generalmente solo necesitas la URL y el método.

fetch("https://api.ejemplo.com/recetas/1", {
  method: "DELETE",
});

Promesas y .then() vs async/await

Cuando hablamos de trabajar con Fetch API, podemos manejar las respuestas de dos formas principales: usando .then() o con async/await. Ambas son buenas opciones, pero tienen diferencias que vale la pena conocer para saber cuál usar en cada caso.

Diagrama de uso de las promesas

Si usas .then(), el código puede empezar a complicarse un poco si tienes que hacer varias cosas encadenadas, porque terminas con muchos niveles de promesas. En cambio, con async/await, el código se siente más lineal, como si estuvieras escribiendo instrucciones normales, una tras otra. Esto hace que sea más fácil de leer, especialmente en casos más complejos.

Con .then(), los errores se manejan al final con un bloque .catch(), lo cual funciona bien, pero si tienes varios pasos, puede ser un poco menos intuitivo. Por otro lado, async/await usa try-catch, que es algo que probablemente ya conoces si has trabajado con excepciones en otros lenguajes. Este enfoque es más claro y organizado, especialmente si necesitas manejar errores en pasos específicos.


.then() es perfecto para tareas simples, donde solo necesitas manejar una o dos cosas y no hay mucha complicación. Sin embargo, si estás trabajando con algo más elaborado, donde necesitas realizar varios pasos o quieres mantener tu código limpio y fácil de entender, async/await es la mejor opción.

Fetch API con Promesas y .then()

Cuando usamos Fetch con .then(), las promesas nos permiten manejar las respuestas de manera encadenada. Este enfoque es útil, pero puede volverse un poco menos legible si las promesas anidadas se acumulan.

Ejemplo con .then():
Obtener una lista de recetas populares.

fetch("https://api.ejemplo.com/recetas/populares")
  .then(response => {
    // Verificamos si la respuesta fue exitosa
    if (!response.ok) {
      throw new Error(Error en la solicitud: ${response.status});
    }
    // Convertimos la respuesta a JSON
    return response.json();
  })
  .then(recetas => {
    // Trabajamos con los datos obtenidos
    recetas.forEach(receta => {
      console.log(Receta: ${receta.nombre}, Tiempo: ${receta.tiempo});
    });
  })
  .catch(error => {
    // Manejo de errores
    console.error("Ocurrió un error:", error.message);
  });

Fetch API con async/await

Con async/await, el código se vuelve más fácil de leer, ya que parece más "lineal". Usamos try-catch para manejar errores de manera clara.

Ejemplo con async/await:
Obtener una lista de recetas populares.

const obtenerRecetasPopulares = async () => {
  try {
    // Hacemos la solicitud
    const response = await fetch("https://api.ejemplo.com/recetas/populares");

    // Verificamos si la respuesta fue exitosa
    if (!response.ok) {
      throw new Error(Error en la solicitud: ${response.status});
    }

    // Convertimos la respuesta a JSON
    const recetas = await response.json();

    // Trabajamos con los datos obtenidos
    recetas.forEach(receta => {
      console.log(Receta: ${receta.nombre}, Tiempo: ${receta.tiempo});
    });
  } catch (error) {
    // Manejo de errores
    console.error("Ocurrió un error:", error.message);
  }
};

// Llamamos a la función
obtenerRecetasPopulares();

Configuración de una Solicitud HTTP

Cuando trabajas con Fetch API, es importante saber cómo configurar correctamente una solicitud para interactuar con un servidor, cómo:

  1. Agregar datos al body en solicitudes POST.
  2. Usar parámetros en la URL para personalizar solicitudes.
  3. Configurar opciones adicionales como tokens para autenticación y CORS para el control de acceso. (opcional)

1. Agregar datos al body en solicitudes POST

Cuando necesitas enviar datos al servidor (por ejemplo, al registrar un usuario), estos se incluyen en el body de la solicitud. Por lo general, se envían en formato JSON y se deben acompañar de un encabezado que indique el tipo de contenido.

fetch("https://api.ejemplo.com/usuarios", {
  method: "POST", // Método HTTP
  headers: {
    "Content-Type": "application/json" // Indicamos que el contenido es JSON
  },
  body: JSON.stringify({ // Convertimos el objeto a JSON
    nombre: "Juan Pérez",
    email: "[email protected]",
    password: "contraseña123"
  })
})
  .then(response => {
    if (!response.ok) {
      throw new Error("Error al crear usuario");
    }
    return response.json(); // Procesamos la respuesta
  })
  .then(data => console.log("Usuario creado:", data))
  .catch(error => console.error("Error:", error.message));

2. Usar parámetros en la URL para personalizar solicitudes

Los parámetros en la URL son útiles para enviar información rápida, como filtros o configuraciones. Esto es común en solicitudes GET. Ejemplo:

Supongamos que quieres obtener información de productos de una tienda en línea. La API permite filtrar los productos por categoría y ordenar los resultados por precio.

// Definimos los parámetros directamente
const categoria = "electronica";
const ordenar = "precio_asc";

fetch(https://api.ejemplo.com/productos?categoria=${categoria}&ordenar=${ordenar}, {
  method: "GET"
})
  .then(response => {
    if (!response.ok) {
      throw new Error("Error al obtener productos");
    }
    return response.json();
  })
  .then(data => console.log("Productos obtenidos:", data))
  .catch(error => console.error("Error:", error.message));

3. Autenticación con JWT y Uso de CORS

En las aplicaciones modernas, la combinación de autenticación con tokens JWT y la configuración de CORS en el backend es el estándar para manejar la seguridad y el control de acceso. Aquí aprenderás cómo configurar una solicitud HTTP con Fetch API utilizando estos enfoques.

¿Qué es JWT y por qué se usa?

Un JSON Web Token (JWT) es un estándar para intercambiar datos de forma segura entre un cliente (como tu aplicación web) y un servidor. El token se genera al autenticarse (por ejemplo, cuando un usuario inicia sesión) y se incluye en las solicitudes posteriores para validar la identidad del usuario.

Ventajas de usar JWT:

  • Portabilidad: Puede enviarse fácilmente en los encabezados HTTP.
  • Independencia: No depende del navegador ni de las cookies.
  • Seguridad: Al estar firmado digitalmente, el servidor puede verificar su autenticidad sin consultar una base de datos.

¿Qué es CORS y cómo funciona?

CORS (Cross-Origin Resource Sharing) permite que un navegador solicite recursos de un dominio distinto al de la página web que lo hizo. Por ejemplo, una aplicación en https://miapp.com puede solicitar datos desde https://api.miapp.com.

El backend controla el acceso configurando encabezados como:

  • Access-Control-Allow-Origin: Define qué dominios pueden acceder a los recursos.
  • Access-Control-Allow-Methods: Especifica los métodos HTTP permitidos (GET, POST, etc.).
  • Access-Control-Allow-Headers: Indica qué encabezados personalizados pueden enviarse.

Ejemplo:

 Iniciar sesión y obtener un JWT

Cuando un usuario se autentica, el servidor genera un token JWT que se devuelve al cliente. Este token se usa para solicitudes futuras.

fetch("https://api.ejemplo.com/login", {
  method: "POST",
  headers: {
    "Content-Type": "application/json"
  },
  body: JSON.stringify({
    username: "usuario",
    password: "contraseña123"
  })
})
  .then(response => {
    if (!response.ok) {
      throw new Error("Error en la autenticación");
    }
    return response.json();
  })
  .then(data => {
    console.log("Token recibido:", data.token);
    localStorage.setItem("token", data.token); // Almacenamos el token
  })
  .catch(error => console.error("Error:", error.message));

Usar el token JWT en solicitudes protegidas

En solicitudes futuras, el token JWT se envía en el encabezado Authorization.

const token = localStorage.getItem("token"); // Recuperamos el token almacenado

fetch("https://api.ejemplo.com/datos-protegidos", {
  method: "GET",
  headers: {
    "Authorization": Bearer ${token}, // Incluimos el token
    "Content-Type": "application/json"
  }
})
  .then(response => {
    if (!response.ok) {
      throw new Error("Acceso no autorizado");
    }
    return response.json();
  })
  .then(data => console.log("Datos protegidos:", data))
  .catch(error => console.error("Error:", error.message));

CORS (Cross-Origin Resource Sharing) configuración. 

El navegador bloquea solicitudes entre dominios distintos si no están configurados correctamente en el servidor. El backend debe permitir accesos seguros configurando CORS.

Configuración en el servidor (Node.js con Express):

const cors = require("cors");
const express = require("express");
const app = express();

app.use(cors({
  origin: "https://miapp.com", // Dominio permitido
  methods: ["GET", "POST", "PUT", "DELETE"], // Métodos permitidos
  allowedHeaders: ["Content-Type", "Authorization"] // Encabezados permitidos
}));

app.listen(3000, () => console.log("Servidor corriendo en puerto 3000"));

La Fetch API es una opción moderna y práctica para realizar solicitudes HTTP en aplicaciones web. Aunque AJAX sigue siendo válido en ciertos escenarios, Fetch proporciona una sintaxis más limpia y una forma más sencilla de manejar respuestas y errores, especialmente con async/await. Esto no significa que sea la única opción, pero encaja bien con muchas de las prácticas actuales de desarrollo.

En este artículo, exploramos cómo usar Fetch para tareas comunes como enviar datos al servidor, manejar parámetros en URLs y configurar aspectos como autenticación con JWT y CORS. Estas configuraciones son útiles en el desarrollo de aplicaciones modernas, pero siempre es importante adaptar las herramientas a las necesidades específicas de cada proyecto.

Si estás explorando cómo comunicar tu frontend con un servidor, Fetch API es una alternativa que vale la pena considerar por su simplicidad y flexibilidad. Al final, como siempre, la elección de la herramienta dependerá de tu proyecto y de lo que quieras lograr.

 

 

Code In Progress with ❤️ by Juan Pablo Amador