¿Tu API tarda demasiado porque una parte de los datos es lenta? ¿Tu frontend React o Vue tiene que esperar todo un json_encode() para empezar a renderizar?
En este artículo aprenderás a aplicar una idea inspirada en los JPEG progresivos y React Server Components, pero usando PHP, tanto con Laravel como con Symfony.
🧠 ¿Qué es JSON progresivo?
JSON progresivo es una estrategia para enviar datos por partes, permitiendo que el cliente empiece a trabajar con lo que ya está disponible, en lugar de esperar todo el bloque de JSON.
Imagina una respuesta típica de una API:
{
"user": { "name": "Ana" },
"profile": { "bio": "Desarrolladora", "projects": [...] },
"footer": "Gracias por visitar"
}
Si projects tarda 2 segundos en consultarse, la respuesta completa se retarda 2 segundos, aunque user y footer ya estén listos.
🎯 Objetivo
Vamos a implementar una técnica para enviar los datos progresivamente, con estructuras como:
{
"user": "$1",
"profile": "$2",
"footer": "$3"
}
/* $1 */
{ "name": "Ana" }
/* $3 */
"Gracias por visitar"
/* $2 */
{ "bio": "Desarrolladora", "projects": "$4" }
/* $4 */
["Proyecto A", "Proyecto B"]
Y en el frontend, podrías reconstruir el JSON reemplazando las referencias $n con sus valores conforme llegan.
🛠️ Caso 1: Laravel — Streaming Progresivo con StreamedResponse
Laravel permite enviar respuestas por partes usando StreamedResponse. Vamos a ver un ejemplo.
✅ Código ejemplo en Laravel
use Symfony\Component\HttpFoundation\StreamedResponse;
Route::get('/api/progressive', function () {
return new StreamedResponse(function () {
echo json_encode([
'user' => '$1',
'profile' => '$2',
'footer' => '$3'
]) . "\n";
// Simula datos rápidos
echo "/* \$1 */\n";
echo json_encode(['name' => 'Ana']) . "\n";
echo "/* \$3 */\n";
echo json_encode("Gracias por visitar") . "\n";
// Simula operación costosa
sleep(2);
echo "/* \$2 */\n";
echo json_encode([
'bio' => 'Desarrolladora',
'projects' => '$4'
]) . "\n";
// Simula otra espera
sleep(1);
echo "/* \$4 */\n";
echo json_encode(['Proyecto A', 'Proyecto B']) . "\n";
}, 200, [
'Content-Type' => 'application/json',
'Transfer-Encoding' => 'chunked',
]);
});
Este endpoint responde en streaming y permite al frontend empezar a procesar los datos de forma progresiva.
🛠️ Caso 2: Symfony — Usando StreamedResponse para APIs reactivas
En Symfony, también puedes usar StreamedResponse de forma muy similar.
✅ Código ejemplo en Symfony
use Symfony\Component\HttpFoundation\StreamedResponse;
use Symfony\Component\Routing\Annotation\Route;
class ApiController extends AbstractController
{
#[Route('/api/progressive', name: 'api_progressive')]
public function progressive(): StreamedResponse
{
$response = new StreamedResponse(function () {
echo json_encode([
'user' => '$1',
'profile' => '$2',
'footer' => '$3'
]) . "\n";
echo "/* \$1 */\n";
echo json_encode(['name' => 'Ana']) . "\n";
echo "/* \$3 */\n";
echo json_encode("Gracias por visitar") . "\n";
sleep(2);
echo "/* \$2 */\n";
echo json_encode([
'bio' => 'Desarrolladora',
'projects' => '$4'
]) . "\n";
sleep(1);
echo "/* \$4 */\n";
echo json_encode(['Proyecto A', 'Proyecto B']) . "\n";
});
$response->headers->set('Content-Type', 'application/json');
$response->headers->set('Transfer-Encoding', 'chunked');
return $response;
}
}
⚙️ Cómo consumirlo en el frontend
El frontend debe reconstruir el JSON progresivamente. Puedes usar ReadableStream en el navegador, o una librería como rxjs o incluso fetch con reader.
Ejemplo (simplificado en JS):
const decoder = new TextDecoder();
const response = await fetch('/api/progressive');
const reader = response.body.getReader();
let buffer = '';
const chunks = {};
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
while (buffer.includes("\n")) {
const [line, ...rest] = buffer.split("\n");
buffer = rest.join("\n");
if (line.startsWith('/* $')) {
const key = line.match(/\$[0-9]+/)[0];
const valueLine = buffer.split("\n")[0];
chunks[key] = JSON.parse(valueLine);
buffer = buffer.split("\n").slice(1).join("\n");
}
}
}
// Reconstruir árbol final...
🧠 Conclusiones
- Puedes usar streaming de JSON en PHP usando
StreamedResponsetanto en Laravel como Symfony. - Este enfoque es ideal cuando algunas partes de la respuesta son lentas, pero otras están disponibles inmediatamente.
- Necesitas una forma en el cliente para reconstruir y manejar los datos progresivamente (con placeholders como Promises o similar).
❓ ¿Cuándo aplicar esto?
✅ Ideal para:
- APIs de contenido que se genera dinámicamente.
- Interfaces que usan React, Vue o similares con capacidad de streaming.
- Sistemas donde un cuello de botella puede retrasar toda la respuesta.
❌ Evitar en:
- APIs pequeñas o muy rápidas.
- Casos donde el cliente no puede manejar respuestas parciales.
Artículo basado en Progressive JSON por Dan Abramov