Módulo 4·practica·6h
Objetivos de aprendizaje
- Entender qué es una API REST y el protocolo HTTP.
- Usar fetch() para peticiones GET, POST, PUT y DELETE.
- Manejar headers, Content-Type y autenticación básica.
- Distinguir errores de red de errores HTTP (status codes).
- Consumir una API pública real (OMDb) con async/await.
Consumo de APIs con Fetch
Una API REST expone datos y operaciones a través de URLs (endpoints) usando el protocolo HTTP. fetch() es la forma nativa de comunicarse con ellas desde JavaScript.
Repaso rápido de HTTP
| Método | Uso | Body |
|---|---|---|
GET | Leer datos | No |
POST | Crear recurso | Sí |
PUT | Reemplazar recurso | Sí |
PATCH | Actualizar parcialmente | Sí |
DELETE | Eliminar recurso | No |
| Status | Significado |
|---|---|
200 OK | Éxito |
201 Created | Recurso creado |
400 Bad Request | Error del cliente |
401 Unauthorized | Sin autenticación |
404 Not Found | No existe |
500 Internal Server Error | Error del servidor |
fetch() básico — GET
javascript
// fetch retorna una Promise<Response>
// .json() también retorna una Promise<datos>
async function obtenerUsuario(id) {
const response = await fetch(`https://jsonplaceholder.typicode.com/users/${id}`);
// ⚠️ fetch NO lanza error en 404 o 500 — solo en fallos de red
if (!response.ok) {
throw new Error(`HTTP ${response.status}: ${response.statusText}`);
}
const usuario = await response.json();
return usuario;
}
try {
const user = await obtenerUsuario(1);
console.log(user.name); // "Leanne Graham"
console.log(user.email); // "Sincere@april.biz"
} catch (error) {
console.error("Error:", error.message);
}POST — Crear un recurso
javascript
async function crearPost(titulo, cuerpo, userId) {
const response = await fetch("https://jsonplaceholder.typicode.com/posts", {
method: "POST",
headers: {
"Content-Type": "application/json",
// Si la API requiere autenticación:
// "Authorization": `Bearer ${token}`,
},
body: JSON.stringify({
title: titulo,
body: cuerpo,
userId,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(`Error ${response.status}: ${errorData.message ?? response.statusText}`);
}
return response.json(); // { id: 101, title: ..., body: ..., userId: ... }
}
const nuevoPost = await crearPost("Mi primer post", "Contenido del post", 1);
console.log(nuevoPost.id); // 101PUT y DELETE
javascript
// Actualizar completamente
async function actualizarPost(id, datos) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(datos),
});
if (!res.ok) throw new Error(`Error ${res.status}`);
return res.json();
}
// Eliminar
async function eliminarPost(id) {
const res = await fetch(`https://jsonplaceholder.typicode.com/posts/${id}`, {
method: "DELETE",
});
if (!res.ok) throw new Error(`Error ${res.status}`);
return true; // 200 con body vacío {}
}Manejo de errores robusto
Crea un wrapper que centralice la lógica de error:
javascript
class ApiError extends Error {
constructor(status, message) {
super(message);
this.name = "ApiError";
this.status = status;
}
}
async function apiFetch(url, opciones = {}) {
let response;
try {
response = await fetch(url, opciones);
} catch {
throw new ApiError(0, "Sin conexión a internet");
}
if (!response.ok) {
let mensaje = `HTTP ${response.status}`;
try {
const body = await response.json();
mensaje = body.message ?? body.error ?? mensaje;
} catch { /* body no es JSON */ }
throw new ApiError(response.status, mensaje);
}
return response.json();
}
// Uso
try {
const datos = await apiFetch("https://api.ejemplo.com/productos");
console.log(datos);
} catch (error) {
if (error instanceof ApiError) {
if (error.status === 401) redirigirALogin();
else mostrarError(error.message);
} else {
throw error;
}
}Proyecto: Buscador de Películas (OMDb API)
La OMDb API permite buscar películas y series. Requiere una API Key gratuita (registro en omdbapi.com).
Parámetros principales
| Parámetro | Uso |
|---|---|
s=texto | Buscar por título (lista) |
i=tt0372784 | Buscar por IMDb ID (detalle) |
t=Batman | Buscar por título exacto |
type=movie|series | Filtrar por tipo |
y=2008 | Filtrar por año |
page=2 | Paginación (búsqueda por s) |
Implementación
javascript
const API_KEY = "TU_API_KEY"; // Registro gratuito en omdbapi.com
const BASE_URL = "https://www.omdbapi.com";
async function buscarPeliculas(query, pagina = 1) {
const url = new URL(BASE_URL);
url.searchParams.set("apikey", API_KEY);
url.searchParams.set("s", query);
url.searchParams.set("type", "movie");
url.searchParams.set("page", pagina);
const data = await apiFetch(url.toString());
if (data.Response === "False") {
throw new Error(data.Error ?? "Sin resultados");
}
return {
peliculas: data.Search,
total: Number(data.totalResults),
paginas: Math.ceil(Number(data.totalResults) / 10),
};
}
async function obtenerDetalle(imdbId) {
const url = new URL(BASE_URL);
url.searchParams.set("apikey", API_KEY);
url.searchParams.set("i", imdbId);
url.searchParams.set("plot", "full");
const data = await apiFetch(url.toString());
if (data.Response === "False") throw new Error(data.Error);
return data;
}
// Renderizar resultados en el DOM
function renderizarPeliculas(peliculas) {
const grid = document.querySelector("#grid-peliculas");
grid.innerHTML = "";
peliculas.forEach(p => {
const article = document.createElement("article");
article.className = "pelicula-card";
article.dataset.imdbid = p.imdbID;
const img = document.createElement("img");
img.src = p.Poster !== "N/A" ? p.Poster : "/placeholder.png";
img.alt = `Poster de ${p.Title}`; // accesible
const h3 = document.createElement("h3");
h3.textContent = p.Title;
const span = document.createElement("span");
span.textContent = p.Year;
article.append(img, h3, span);
grid.appendChild(article);
});
}
// Evento de búsqueda
document.querySelector("#form-busqueda").addEventListener("submit", async e => {
e.preventDefault();
const query = e.target.querySelector("input").value.trim();
const status = document.querySelector("#status");
if (!query) return;
status.textContent = "Buscando…";
document.querySelector("#grid-peliculas").innerHTML = "";
try {
const { peliculas, total } = await buscarPeliculas(query);
renderizarPeliculas(peliculas);
status.textContent = `${total} resultados para "${query}"`;
} catch (error) {
status.textContent = `Error: ${error.message}`;
}
});CORS — Cross-Origin Resource Sharing
El navegador bloquea peticiones a dominios distintos del origen de la página salvo que el servidor envíe los headers correctos:
code
Access-Control-Allow-Origin: https://mi-app.comSoluciones comunes:
- La API lo soporta (OMDb, GitHub API) → funciona directo
- La API no lo soporta → crear un proxy en tu propio servidor (Node.js) que haga la petición
javascript
// proxy en Express (Node.js)
app.get("/api/peliculas", async (req, res) => {
const { q } = req.query;
const data = await fetch(`https://omdbapi.com/?s=${q}&apikey=${process.env.OMDB_KEY}`);
res.json(await data.json());
});Práctica
Práctica interactiva · JavaScript