Ejemplos de Uso
Aprende a conectar tu frontend con cualquier backend usando el BFF de Humalight
Los ejemplos funcionan con Strapi, APIs REST custom y más
Nota Importante
Los ejemplos a continuación son ilustrativos. Adapta las rutas y estructuras de datos según tu backend. Si usas Strapi, reemplaza tu-content-type con el nombre de tus content types reales.
Search API - Búsqueda unificada
API de búsqueda rápida basada en Typesense. Indexa places, events y posts. El modo minimal devuelve payloads compactos ideales para autosuggest y cards.
Búsqueda en todas las colecciones:
// Desde el frontend
const response = await fetch(
'/api/search?q=fiesta&collection=all&limit=5',
{
headers: { 'x-api-key': process.env.NEXT_PUBLIC_BFF_API_KEY! },
cache: 'no-store'
}
);
const { data } = await response.json();
console.log(data); // minimal por defectoFiltrar por colección específica:
// Solo eventos
const events = await fetch(
'/api/search?q=rock&collection=events&limit=5',
{ headers: { 'x-api-key': process.env.NEXT_PUBLIC_BFF_API_KEY! }}
);
// Solo lugares
const places = await fetch(
'/api/search?q=restaurante&collection=places',
{ headers: { 'x-api-key': process.env.NEXT_PUBLIC_BFF_API_KEY! }}
);Implementar autosuggest en tu componente:
import { useState, useEffect } from 'react';
function SearchInput() {
const [query, setQuery] = useState('');
const [results, setResults] = useState([]);
useEffect(() => {
if (!query) return;
const timer = setTimeout(async () => {
const res = await fetch(
'/api/search?q=' + encodeURIComponent(query) + '&limit=5',
{ headers: { 'x-api-key': process.env.NEXT_PUBLIC_BFF_API_KEY! }}
);
const { data } = await res.json();
setResults(data);
}, 300); // debounce 300ms
return () => clearTimeout(timer);
}, [query]);
return (
<div>
<input
value={query}
onChange={(e) => setQuery(e.target.value)}
placeholder="Buscar lugares o eventos..."
/>
{results.map(item => (
<div key={item.documentId}>
{item.title} - {item.collection}
</div>
))}
</div>
);
}Actualizar índice de búsqueda (sync):
// Ejecuta después de agregar/editar contenido en tu base de datos
const response = await fetch('/api/search/sync', {
method: 'POST',
headers: { 'x-api-key': process.env.NEXT_PUBLIC_BFF_API_KEY! }
});
const result = await response.json();
console.log(result.message);Respuesta minimal (places)
{
collection: "places",
documentId: "...",
title: "...",
portada: {
url: "...",
thumbnail: "...",
medium: "...",
large: "...",
original: "..."
},
categories: [...],
tags: [...],
address: "...",
phone: "...",
horario: "..."
}Respuesta minimal (events)
{
collection: "events",
documentId: "...",
title: "...",
portada: { ... },
address: "...",
fechasYHoras: "...",
tieneCosto: true,
costo: 100
}Obtener Datos (GET)
Desde el Frontend:
// Llamar al BFF desde tu aplicación cliente
const response = await fetch('/api/strapi/tu-content-type');
const data = await response.json();
console.log(data.data);Desde un Server Component:
import { httpClient } from '@/lib/http';
async function getData() {
const response = await httpClient.get('/api/tu-endpoint');
return response.data;
}Con parámetros (ej: Strapi populate):
const response = await httpClient.get( '/api/tu-content-type?populate=*' );
Users API - Usuarios con Metadata
API personalizada que combina datos de usuarios con sus metadatos (usersmetas), incluyendo imágenes de perfil, direcciones y roles. Todos los datos PHP están deserializados y listos para usar.
Listar todos los usuarios:
// Obtener todos los usuarios con metadata procesada
const response = await fetch('/api/users');
const { data } = await response.json();
// Cada usuario incluye:
// - id, username, email, displayName, status
// - profileImage: { thumbnail, medium, large, original }
// - addresses: [{ tipo, calle, numero, colonia, ciudad, estado, cp }]
// - role: { id, name, description }
// - metas: { key: value } (todos los metadatos deserializados)Filtrar por userId:
// Obtener usuario específico por ID
const response = await fetch('/api/users?userId=123');
const { data } = await response.json();
// Devuelve un solo usuario con toda su informaciónFiltrar por username o email:
// Por username
const response = await fetch('/api/users?username=johndoe');
// Por email
const response = await fetch('/api/users?email=john@example.com');Filtrar por metaKey específica:
// Solo usuarios con foto de perfil
const response = await fetch('/api/users?metaKey=foto-de-perfil');
// Solo usuarios con dirección
const response = await fetch('/api/users?metaKey=direccion-principal');Modo minimal (para listas):
// Respuesta optimizada sin metadatos completos
const response = await fetch('/api/users?minimal=true');
const { data } = await response.json();
// Solo incluye: id, username, email, displayName, profileImage (thumbnail)Estructura de profileImage:
const { data: user } = await response.json();
// profileImage con múltiples variantes
console.log(user.profileImage);
/*
{
thumbnail: "https://...150x150.jpg",
medium: "https://...300x300.jpg",
large: "https://...1024x1024.jpg",
original: "https://...original.jpg"
}
*/Componente React con usuarios:
async function UsersList() {
const res = await fetch('/api/users?minimal=true', {
next: { revalidate: 120 } // Cache por 2 minutos
});
const { data: users } = await res.json();
return (
<div className="grid grid-cols-4 gap-4">
{users.map((user) => (
<UserCard
key={user.id}
username={user.username}
email={user.email}
avatar={user.profileImage?.thumbnail}
displayName={user.displayName}
/>
))}
</div>
);
}✨ Características del Users API:
- Deserialización automática de metadatos PHP
- Extracción de variantes de imagen de perfil (thumbnail, medium, large)
- Normalización de direcciones desde múltiples formatos
- Filtrado flexible por ID, username, email o metaKey
- Modo minimal optimizado para listas de usuarios
- Incluye información de roles y permisos
Places API - Lectura de Lugares
API personalizada de lectura. Agrega datos de múltiples colecciones de Strapi (wx-posts + wx-postmetas) para generar lugares unificados.
GETListar y Consultar Lugares
Listar todos los lugares (datos mínimos para Cards):
// Respuesta optimizada para Cards
const response = await fetch('/api/places?minimal=true');
const { data } = await response.json();
// Cada lugar incluye:
// - documentId, post_type, post_name, title
// - portada: { thumbnail, medium, large, original, url }
// - categories: [{ name, slug }]
// - tags: [{ name }]
// - address, phone, horario (parseados y listos para usar)Filtrar por tipo de post:
// Solo lugares (no eventos)
const response = await fetch('/api/places?postType=lugares&minimal=true');
// Solo eventos
const response = await fetch('/api/places?postType=eventos&minimal=true');Obtener un lugar específico (por documentId o slug):
// Por documentId
const response = await fetch(
'/api/places/fbjs6raaipdo07jbkvprrb7s?minimal=true'
);
// Por slug (post_name)
const response = await fetch(
'/api/places/mi-bodega-aurrera-flamingos?minimal=true'
);
const { data } = await response.json();
// data contiene un solo lugar con todos sus datos✅ Características del Places API:
- GET: Lectura de lugares con datos agregados
- Deserialización automática de metadatos
- Información de ubicación, horarios y costos
- Imágenes destacadas y galerías
- Filtrado por categorías y términos
Events API - Eventos con Cache
API de eventos que agrega datos de WordPress/Strapi con información completa: fechas, ubicación, costos, imágenes y más.
Listar eventos (versión minimal para cards):
// Obtener eventos con datos optimizados para tarjetas
const response = await fetch('/api/events?minimal=true');
const { data, count } = await response.json();
// Cada evento incluye (minimal):
// - documentId: identificador único
// - title: título del evento
// - portada: { id, name, thumbnail, medium, large, original, url }
// - address: dirección completa del evento
// - fechasYHoras: fechas y horarios formateados
// - tieneCosto: boolean si tiene costo
// - costo: precio del evento (number)
console.log(data[0]);
/*
{
documentId: "abc123",
title: "Concierto de Rock",
portada: {
id: 45,
name: "concierto.jpg",
medium: "https://...medium.jpg",
large: "https://...large.jpg"
},
address: "Av. Principal 123, Centro, CDMX",
fechasYHoras: "2025-12-20 19:00, 2025-12-21 19:00",
tieneCosto: true,
costo: 350.50
}
*/Obtener evento específico:
// Por documentId o slug
const response = await fetch('/api/events/concierto-rock-2025?minimal=true');
const { data } = await response.json();
// Devuelve un solo evento con estructura minimalDatos completos (sin minimal):
// Respuesta completa con todos los campos
const response = await fetch('/api/events');
const { data } = await response.json();
// Incluye campos adicionales:
// - id, slug, excerpt, content, status, type
// - createdAt, updatedAt
// - author: { id, username, email }
// - logotipo: { id, name, thumbnail, medium, large, ... }
// - galeria: [{ id, name, thumbnail, medium, ... }]
// - categories: [{ id, documentId, name, slug }]Filtrar por categoría/término:
// Solo eventos de música const response = await fetch( '/api/events?termFilter=musica&minimal=true' );
Componente React con eventos:
async function EventsList() {
const res = await fetch('/api/events?minimal=true', {
next: { revalidate: 300 } // Cache por 5 minutos
});
const { data: events } = await res.json();
return (
<div className="grid grid-cols-3 gap-4">
{events.map((event) => (
<EventCard
key={event.documentId}
title={event.title}
image={event.portada?.large}
address={event.address}
dates={event.fechasYHoras}
price={event.tieneCosto ? `$${event.costo}` : 'Gratis'}
/>
))}
</div>
);
}✨ Características del Events API:
- Deserialización automática de datos PHP de WordPress
- Extracción de múltiples variantes de imágenes (thumbnail, medium, large)
- Soporte para eventos gratuitos y de pago
- Fechas y horarios formateados listos para mostrar
- Cache de 5 minutos (configurable)
- Versión minimal optimizada para listas y cards
Recent API - Posts Recientes con Redis
API que obtiene los posts más recientes de cualquier post_type. Reutiliza los agregadores de eventos y lugares. Cache con Redis (TTL: 5 minutos).
Obtener posts recientes (todos los tipos):
// 10 posts más recientes (default)
const response = await fetch('/api/recent');
const { data, count } = await response.json();
// Respuesta incluye posts de cualquier tipo
// Para eventos y lugares, usa sus agregadores específicos
// Para otros tipos (post, page), devuelve estructura genéricaFiltrar por post_types específicos:
// Solo eventos más recientes
const response = await fetch('/api/recent?postTypes=eventos&limit=5');
// Eventos y lugares
const response = await fetch('/api/recent?postTypes=eventos,lugares&limit=10');
// Con modo minimal para cards
const response = await fetch(
'/api/recent?postTypes=eventos,lugares&minimal=true&limit=15'
);Invalidar cache desde el frontend:
// 1. Invalidar todo el cache
await fetch('/api/recent', { method: 'POST' });
// 2. Invalidar solo eventos (todas las variaciones)
await fetch('/api/recent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
postTypes: ['eventos']
})
});
// 3. Invalidar key específica
await fetch('/api/recent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
postTypes: ['eventos'],
limit: 10,
minimal: true
})
});Ejemplo: Invalidar cache al crear post:
async function handleCreatePost(postType, postData) {
try {
// Crear el post
const res = await fetch('/api/strapi/wx-posts', {
method: 'POST',
body: JSON.stringify(postData)
});
if (res.ok) {
// Invalidar cache de recent para ese postType
await fetch('/api/recent', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
postTypes: [postType]
})
});
console.log(`✅ Cache invalidado para ${postType}`);
}
} catch (error) {
console.error('Error:', error);
}
}
// Uso
await handleCreatePost('eventos', { title: 'Nuevo Evento', ... });Componente React con recent posts:
async function RecentPostsList() {
const res = await fetch(
'/api/recent?postTypes=eventos,lugares&limit=10&minimal=true',
{ next: { revalidate: 300 } } // Cache por 5 minutos
);
const { data: posts } = await res.json();
return (
<div className="grid grid-cols-2 gap-4">
{posts.map((post) => (
<PostCard
key={post.documentId}
title={post.title}
image={post.portada?.large}
type={post.post_type || 'post'}
/>
))}
</div>
);
}🚀 Características del Recent API:
- Reutiliza agregadores de eventos y lugares (datos completos)
- Soporte para múltiples post_types simultáneos
- Cache granular en Redis por combinación de parámetros
- 3 niveles de invalidación: total, por tipo, o específica
- Límite configurable (default: 10, max: 50)
- Ordenamiento por fecha de creación (más reciente primero)
- Perfecto para feeds de noticias y dashboards
Menus API - Menús de Navegación con Redis
API de menús con cache en Redis para respuestas ultra rápidas. Agrega datos de WordPress/Strapi y construye árboles jerárquicos de navegación.
Listar todos los menús disponibles:
// Obtener lista de todos los menús
const response = await fetch('/api/menus');
const { data, count } = await response.json();
// Cada menú incluye:
// - documentId: identificador único
// - name: nombre del menú
// - slug: slug normalizado
// - items: árbol jerárquico de elementos
console.log(data);
/*
[
{
documentId: "abc123",
name: "Desktop Header",
slug: "desktop-header",
items: [
{
documentId: "item1",
title: "Inicio",
url: "/",
order: 1,
children: [...],
metas: { menu_icon_url: "https://..." }
}
]
}
]
*/Obtener un menú específico por nombre:
// Obtener menú por nombre o slug
const response = await fetch('/api/menus/desktop-header');
const { data } = await response.json();
// Menú con estructura jerárquica completa
console.log(data.items); // Array de items raíz con children anidadosCon array plano de items (útil para búsquedas):
// Incluir flatItems además del árbol jerárquico
const response = await fetch('/api/menus/mobile-menu?includeFlat=true');
const { data } = await response.json();
// data.items: árbol jerárquico
// data.flatItems: array plano con todos los items
const allUrls = data.flatItems.map(item => item.url);Deshabilitar cache Redis (forzar fetch):
// Omitir cache y obtener datos frescos desde Strapi
const response = await fetch('/api/menus/desktop-header?disableCache=true');
const { data } = await response.json();Invalidar cache (para webhooks de Strapi):
// Llamar cuando se actualice un menú en Strapi
const response = await fetch('/api/menus', {
method: 'POST'
});
const { success, message } = await response.json();
// { success: true, message: "Menu cache invalidated successfully" }Ejemplo de componente React con menú:
'use client';
import { useEffect, useState } from 'react';
export function NavigationMenu() {
const [menu, setMenu] = useState(null);
const [loading, setLoading] = useState(true);
useEffect(() => {
async function loadMenu() {
const res = await fetch('/api/menus/desktop-header');
const { data } = await res.json();
setMenu(data);
setLoading(false);
}
loadMenu();
}, []);
if (loading) return <div>Cargando menú...</div>;
if (!menu) return null;
return (
<nav>
{menu.items.map(item => (
<a key={item.documentId} href={item.url}>
{item.title}
{/* Renderizar children recursivamente */}
</a>
))}
</nav>
);
}⚡ Ventajas del Cache con Redis:
- Respuestas < 10ms desde Redis (vs ~200ms desde Strapi)
- Reduce carga en el servidor Strapi
- TTL configurable (default: 1 hora)
- Invalidación manual vía POST /api/menus
Endpoint Personalizado - Places
// El BFF combina múltiples colecciones de Strapi automáticamente
const response = await fetch('/api/places?postType=place&debug=true');
const { data, meta } = await response.json();
// Cada place incluye:
// - Datos del post (título, contenido, fecha)
// - Términos asociados (categorías/tags)
// - Metadatos (dirección, teléfono)
// - Imágenes (portada + galería)
console.log(data[0]);
/*
{
documentId: "abc123",
title: "Mi Lugar",
address: "Calle Principal 123",
phone: "+52 123 456 7890",
featuredImageUrl: "https://...",
galleryUrls: ["https://...", ...],
terms: [{ name: "Restaurante", slug: "restaurante" }],
...
}
*/Autenticación - Login
const response = await fetch('/api/auth/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
identifier: 'user@example.com', // o username
password: 'password123'
})
});
const { jwt, user } = await response.json();
// Guardar el token para usar en peticiones futuras
localStorage.setItem('token', jwt);Registro de Usuario
const response = await fetch('/api/auth/register', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
username: 'johndoe',
email: 'john@example.com',
password: 'password123'
})
});
const { jwt, user } = await response.json();
localStorage.setItem('token', jwt);Peticiones Autenticadas
// Obtener datos del usuario actual
const token = localStorage.getItem('token');
const response = await fetch('/api/user/me', {
headers: {
'Authorization': `Bearer ${token}`
}
});
const { data: userData } = await response.json();
console.log(userData.username, userData.email);Recuperar Contraseña
Paso 1: Solicitar código
await fetch('/api/auth/forgot-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
email: 'user@example.com'
})
});
// El usuario recibirá un email con el códigoPaso 2: Restablecer con código
await fetch('/api/auth/reset-password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
code: 'codigo_del_email',
password: 'nuevaPassword123',
passwordConfirmation: 'nuevaPassword123'
})
});Endpoints del BFF
🔐 Autenticación
/api/auth/login/api/auth/register/api/auth/verify/api/auth/forgot-password/api/auth/reset-password/api/auth/change-password👤 Usuario
/api/user/me/api/user/update🎯 Endpoints Personalizados
/api/users→ Usuarios con metadata/api/places→ Listar lugares/api/places/[documentId]→ Obtener lugar/api/events→ Eventos con cache/api/events/[id]→ Evento específico/api/menus→ Menús con Redis/api/menus/[name]→ Menú específico/api/recent→ Posts recientes con Redis📡 Proxy Strapi
/api/strapi/[tu-content-type]/api/strapi/[tu-content-type]/api/strapi/[tu-content-type]/[id]/api/strapi/[tu-content-type]/[id]Próximos Pasos
- Configura tu backend en
.env.local - Si usas Strapi: crea content types y configura permisos
- Adapta las rutas de los ejemplos según tu API
- Implementa login/registro en tu frontend
- Prueba los endpoints con el health check