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 con post_meta JSON) 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⭐Reviews y Ratings
Obtén reviews y calificaciones de los lugares. Los reviews se extraen de la colección wx-reviews de Strapi.
Incluir resumen de ratings:
// Obtener lugar con resumen de calificaciones
const response = await fetch(
'/api/places/la-chosita-del-marisco?includeRatings=true'
);
const { data } = await response.json();
console.log(data.rating);
/*
{
average: 3.7,
total: 3,
distribution: {
5: 1,
4: 1,
3: 0,
2: 1,
1: 0
}
}
*/Incluir reviews completas:
// Obtener lugar con todas las reviews
const response = await fetch(
'/api/places/la-chosita-del-marisco?includeReviews=true'
);
const { data } = await response.json();
console.log(data.reviews);
/*
[
{
id: 16,
documentId: "wn0e8tvzl8urat2oh1qll6wy",
title: "Excelente servicio",
content: "La comida estuvo deliciosa...",
rating: 5,
source: "web",
date: "2026-02-25T00:44:19.107Z",
approved: true,
likes: 2,
dislikes: 0,
author: {
id: 4,
username: "usuario123",
avatar: "https://..."
}
},
...
]
*/Combinar ratings + reviews:
// Obtener lugar completo con ratings y reviews
const response = await fetch(
'/api/places/la-chosita-del-marisco?includeRatings=true&includeReviews=true'
);
const { data } = await response.json();
// data ahora incluye:
// - rating: { average, total, distribution }
// - reviews: [{ id, title, content, rating, ... }]Componente React con ratings:
function PlaceWithRatings({ documentId }) {
const [place, setPlace] = useState(null);
useEffect(() => {
fetch(`/api/places/${documentId}?includeRatings=true&includeReviews=true`)
.then(res => res.json())
.then(({ data }) => setPlace(data));
}, [documentId]);
if (!place) return <div>Cargando...</div>;
return (
<div>
<h1>{place.title}</h1>
{/* Rating Summary */}
{place.rating && (
<div className="flex items-center gap-2">
<span className="text-2xl font-bold">{place.rating.average}</span>
<div className="flex">
{[1,2,3,4,5].map(star => (
<span key={star} className={
star <= Math.round(place.rating.average)
? 'text-yellow-400'
: 'text-gray-300'
}>★</span>
))}
</div>
<span className="text-gray-500">
({place.rating.total} reviews)
</span>
</div>
)}
{/* Distribution */}
{place.rating && (
<div className="mt-4">
{[5,4,3,2,1].map(stars => (
<div key={stars} className="flex items-center gap-2">
<span>{stars}★</span>
<div className="flex-1 bg-gray-200 rounded h-2">
<div
className="bg-yellow-400 h-2 rounded"
style={{
width: `${(place.rating.distribution[stars] / place.rating.total) * 100}%`
}}
/>
</div>
<span>{place.rating.distribution[stars]}</span>
</div>
))}
</div>
)}
{/* Reviews List */}
{place.reviews?.map(review => (
<div key={review.documentId} className="border-t pt-4 mt-4">
<div className="flex items-center gap-2">
<span className="font-bold">{review.author?.username || 'Anónimo'}</span>
<span className="text-yellow-400">{review.rating}★</span>
</div>
<h3 className="font-semibold">{review.title}</h3>
<p className="text-gray-600">{review.content}</p>
<div className="flex gap-4 text-sm text-gray-500">
<span>👍 {review.likes}</span>
<span>👎 {review.dislikes}</span>
</div>
</div>
))}
</div>
);
}📊 Estructura de Rating:
average: Promedio de calificaciones (0-5, 1 decimal)total: Total de reviews aprobadasdistribution: Cantidad por estrella (1-5)
✅ 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
✏️CRUD de Menús - Gestión desde el Frontend
Ahora puedes crear, actualizar y eliminar items de menú directamente desde tu frontend. El cache de Redis se invalida automáticamente tras cada operación.
POSTCrear nuevo item de menú:
// Crear un nuevo item en el menú "desktop-header"
const response = await fetch('/api/menus/desktop-header/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': 'Bearer ' + userToken
},
body: JSON.stringify({
post: {
post_title: 'Contacto',
post_name: 'contacto',
menu_order: 5
},
metas: {
_menu_item_url: '/contacto',
_menu_item_type: 'custom',
_menu_item_target: '_self',
_menu_item_classes: 'btn btn-primary'
}
})
});
const { success, data, meta } = await response.json();
// data: menú actualizado con el nuevo item
// meta.itemDocumentId: documentId del item creadoPUTActualizar item existente:
// Actualizar un item del menú "desktop-header"
const response = await fetch('/api/menus/desktop-header', {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': 'Bearer ' + userToken
},
body: JSON.stringify({
itemDocumentId: 'abc123xyz', // documentId del item a actualizar
post: {
post_title: 'Nuevo Título',
menu_order: 10
},
metas: {
_menu_item_url: '/nueva-url',
_menu_item_target: '_blank'
}
})
});
const { success, data } = await response.json();
// data: menú actualizado
// Cache de Redis invalidado automáticamenteDELETEEliminar item de menú:
// Eliminar un item del menú
const response = await fetch('/api/menus/desktop-header', {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': 'Bearer ' + userToken
},
body: JSON.stringify({
itemDocumentId: 'abc123xyz'
})
});
const { success, data, meta } = await response.json();
// data: menú actualizado sin el item eliminado
// meta.deleted: trueEjemplo completo: Componente de gestión de menús
'use client';
import { useState } from 'react';
export function MenuEditor({ menuName, userToken }) {
const [menu, setMenu] = useState(null);
const [loading, setLoading] = useState(false);
async function handleCreateItem(itemData) {
setLoading(true);
const res = await fetch(`/api/menus/${menuName}/items`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify(itemData)
});
const { success, data } = await res.json();
if (success) {
setMenu(data); // Menú actualizado
alert('✅ Item creado exitosamente');
}
setLoading(false);
}
async function handleUpdateItem(itemDocId, updates) {
const res = await fetch(`/api/menus/${menuName}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify({ itemDocumentId: itemDocId, ...updates })
});
const { success, data } = await res.json();
if (success) setMenu(data);
}
async function handleDeleteItem(itemDocId) {
const confirmed = confirm('¿Eliminar este item?');
if (!confirmed) return;
const res = await fetch(`/api/menus/${menuName}`, {
method: 'DELETE',
headers: {
'Content-Type': 'application/json',
'X-API-Key': process.env.NEXT_PUBLIC_BFF_API_KEY,
'Authorization': `Bearer ${userToken}`
},
body: JSON.stringify({ itemDocumentId: itemDocId })
});
const { success, data } = await res.json();
if (success) {
setMenu(data);
alert('🗑️ Item eliminado');
}
}
return (
<div>
<button onClick={() => handleCreateItem({
post: { post_title: 'Nuevo Item' },
metas: { _menu_item_url: '/nueva-pagina' }
})}>
Crear Item
</button>
{/* Renderizar items con botones de editar/eliminar */}
</div>
);
}🔐 Requisitos de Autenticación:
- API Key: Header
X-API-Key - Token JWT: Header
Authorization: Bearer ... - Cache: Se invalida automáticamente tras cada operación
- Respuesta: Incluye el menú completo actualizado
📋Metadatos Comunes de Items de Menú:
_menu_item_urlURL del enlace
_menu_item_typecustom, post_type, taxonomy
_menu_item_target_blank, _self
_menu_item_classesClases CSS
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