Fetch y APIs REST
La Fetch API es la forma nativa de hacer peticiones HTTP desde el navegador. Reemplaza al antiguo XMLHttpRequest con una interfaz limpia basada en promesas.
GET básico
fetch('https://jsonplaceholder.typicode.com/posts/1')
.then((res) => res.json())
.then((data) => console.log(data));
// Con async/await
async function obtenerPost(id) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`);
const data = await res.json();
return data;
}
El objeto Response
fetch() devuelve una promesa con el objeto Response. Este objeto no es directamente el cuerpo — hay que leerlo con un método apropiado:
const res = await fetch('/api/datos');
res.ok // true si status 200–299
res.status // número HTTP: 200, 404, 500...
res.statusText // 'OK', 'Not Found'...
res.headers // objeto Headers con las cabeceras de respuesta
res.url // URL final (después de posibles redirects)
// Métodos para leer el cuerpo (solo uno, no se puede leer dos veces)
await res.json() // JSON → objeto JavaScript
await res.text() // texto plano, HTML, CSV...
await res.blob() // datos binarios (imágenes, archivos)
await res.formData() // FormData
Manejo correcto de errores
Atención:
fetch()no lanza un error en respuestas 4xx o 5xx. Solo rechaza la promesa si hay un fallo de red (sin conexión, DNS inexistente…). Debes comprobarres.okmanualmente:
async function obtenerDatos(url) {
try {
const res = await fetch(url);
if (!res.ok) {
throw new Error(`Error HTTP: ${res.status}`);
}
return await res.json();
} catch (error) {
// Aquí llegan tanto errores de red como el throw de arriba
console.error('Fallo en la petición:', error.message);
throw error; // re-lanzar para que el llamador lo maneje
}
}
POST con cuerpo JSON
async function crearPost(datos) {
const res = await fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(datos),
});
if (!res.ok) throw new Error(`HTTP ${res.status}`);
return res.json();
}
const nuevo = await crearPost({
title: 'Mi post',
body: 'Contenido del post',
userId: 1,
});
console.log(nuevo); // { id: 101, title: 'Mi post', ... }
Métodos HTTP habituales
// GET — obtener datos
fetch('/api/posts')
// POST — crear un recurso
fetch('/api/posts', { method: 'POST', headers: {...}, body: JSON.stringify(datos) })
// PUT — reemplazar un recurso completo
fetch('/api/posts/1', { method: 'PUT', headers: {...}, body: JSON.stringify(datos) })
// PATCH — actualizar campos concretos
fetch('/api/posts/1', { method: 'PATCH', headers: {...}, body: JSON.stringify({ title: 'Nuevo título' }) })
// DELETE — eliminar
fetch('/api/posts/1', { method: 'DELETE' })
Cabeceras de autenticación
const token = localStorage.getItem('token');
const res = await fetch('/api/perfil', {
headers: {
'Authorization': `Bearer ${token}`,
'Content-Type': 'application/json',
}
});
Enviar formularios
// FormData encapsula los campos del formulario
const form = document.querySelector('#mi-formulario');
form.addEventListener('submit', async (e) => {
e.preventDefault();
const formData = new FormData(form);
// Enviar como multipart/form-data (necesario para archivos)
const res = await fetch('/api/subir', {
method: 'POST',
body: formData, // NO pongas Content-Type — el navegador lo hace solo con el boundary
});
const resultado = await res.json();
console.log(resultado);
});
AbortController — cancelar peticiones
const controller = new AbortController();
// Cancelar si pasan más de 5 segundos
const timeout = setTimeout(() => controller.abort(), 5000);
try {
const res = await fetch('/api/datos-lentos', {
signal: controller.signal, // vincula el fetch al controller
});
clearTimeout(timeout);
return await res.json();
} catch (error) {
if (error.name === 'AbortError') {
console.log('Petición cancelada');
} else {
throw error;
}
}
Cancelar también es útil en React/Astro cuando el componente se desmonta antes de que termine la petición.
Patrón: función fetch reutilizable
async function api(endpoint, opciones = {}) {
const base = 'https://api.ejemplo.com';
const token = localStorage.getItem('token');
const res = await fetch(`${base}${endpoint}`, {
headers: {
'Content-Type': 'application/json',
...(token ? { Authorization: `Bearer ${token}` } : {}),
...opciones.headers,
},
...opciones,
});
if (!res.ok) {
const cuerpo = await res.json().catch(() => ({}));
throw new Error(cuerpo.message || `HTTP ${res.status}`);
}
// 204 No Content — respuesta vacía
if (res.status === 204) return null;
return res.json();
}
// Uso
const posts = await api('/posts');
const nuevo = await api('/posts', {
method: 'POST',
body: JSON.stringify({ title: 'Test' }),
});
Conceptos REST básicos
Una API REST organiza los recursos como URLs y usa los métodos HTTP para las operaciones:
GET /api/posts → lista de posts
GET /api/posts/1 → un post concreto
POST /api/posts → crear un post
PUT /api/posts/1 → reemplazar el post 1
PATCH /api/posts/1 → actualizar campos del post 1
DELETE /api/posts/1 → eliminar el post 1
Los códigos de estado más comunes:
200 OK — éxito general
201 Created — recurso creado (respuesta a POST)
204 No Content — éxito sin cuerpo (respuesta a DELETE)
400 Bad Request — los datos enviados son incorrectos
401 Unauthorized — no autenticado
403 Forbidden — autenticado pero sin permiso
404 Not Found — el recurso no existe
422 Unprocessable — datos válidos pero fallan validación de negocio
500 Internal Server Error — error en el servidor
En la siguiente lección aprendemos a organizar el código en módulos: import, export, y cómo el sistema de módulos de JavaScript hace el código más mantenible.