Concepto clave
En esta lección, integraremos todos los conceptos de SvelteKit para construir una aplicación de tareas en tiempo real. Imagina que estás construyendo un tablero Kanban digital donde múltiples usuarios pueden mover tarjetas simultáneamente, como en Trello o Jira. La clave está en combinar Server Actions para operaciones seguras en el servidor con WebSockets para actualizaciones en tiempo real, manteniendo la interfaz responsiva.
El patrón fundamental es: cuando un usuario realiza una acción (como crear o mover una tarea), primero se valida y procesa en el servidor mediante una Server Action, luego se propaga el cambio a todos los clientes conectados a través de WebSockets. Esto garantiza consistencia de datos y evita conflictos, similar a cómo un equipo de construcción coordina cambios en planos arquitectónicos: un arquitecto aprueba la modificación (servidor) y luego notifica a todos los trabajadores (clientes).
Cómo funciona en la práctica
Vamos a implementar el flujo completo paso a paso. Primero, configuraremos un endpoint de WebSocket en SvelteKit usando el hook handle. Luego, crearemos Server Actions para manejar las operaciones CRUD de tareas. Finalmente, conectaremos el frontend para escuchar actualizaciones y reflejarlas instantáneamente.
- Configura el servidor WebSocket en
src/hooks.server.jspara manejar conexiones y broadcast de mensajes. - Crea Server Actions en
src/routes/+page.server.jspara crear, actualizar y eliminar tareas, validando datos y accediendo a la base de datos. - En el frontend (
src/routes/+page.svelte), usaonMountpara conectar al WebSocket y actualizar el estado local cuando lleguen mensajes. - Implementa un sistema de reconexión automática para manejar fallos de red, similar a cómo una app de mensajería se reconecta si pierde internet.
Codigo en accion
Aquí tienes un ejemplo funcional del endpoint WebSocket y una Server Action. Antes de refactorizar, el código podría mezclar lógica de WebSocket con la de rutas, lo que dificulta el mantenimiento. Después, separamos responsabilidades claramente.
Antes: WebSocket manejado directamente en una ruta, sin estructura modular.
// src/routes/api/websocket/+server.js - NO RECOMENDADO
export function GET({ request }) {
// Lógica de WebSocket mezclada con endpoint HTTP
const upgrade = request.headers.get('upgrade');
if (upgrade !== 'websocket') return new Response(null, { status: 426 });
// ... código complejo aquí
}Después: WebSocket en hook separado para reutilización y claridad.
// src/hooks.server.js - MEJOR PRÁCTICA
import { handleWebSocket } from '$lib/websocket';
export const handle = async ({ event, resolve }) => {
if (event.url.pathname === '/ws') {
return handleWebSocket(event);
}
return resolve(event);
};
// src/lib/websocket.js
export function handleWebSocket(event) {
const upgrade = event.request.headers.get('upgrade');
if (upgrade !== 'websocket') {
return new Response(null, { status: 426 });
}
const { socket, response } = Deno.upgradeWebSocket(event.request);
const clients = new Set();
socket.onopen = () => {
clients.add(socket);
console.log('Cliente conectado');
};
socket.onmessage = (event) => {
// Broadcast a todos los clientes
clients.forEach(client => {
if (client !== socket && client.readyState === WebSocket.OPEN) {
client.send(event.data);
}
});
};
socket.onclose = () => clients.delete(socket);
return response;
}Server Action para crear una tarea:
// src/routes/+page.server.js
export const actions = {
createTask: async ({ request }) => {
const data = await request.formData();
const title = data.get('title');
const description = data.get('description');
if (!title || title.length < 3) {
return { success: false, error: 'Título debe tener al menos 3 caracteres' };
}
// Insertar en base de datos (ejemplo con supabase)
const { data: task, error } = await supabase
.from('tasks')
.insert([{ title, description, status: 'todo', created_at: new Date() }])
.select();
if (error) {
return { success: false, error: error.message };
}
// Notificar a WebSocket para broadcast
broadcastToClients({ type: 'task_created', task: task[0] });
return { success: true, task: task[0] };
}
};Errores comunes
- No validar datos en Server Actions: Confiar solo en validación frontend permite inyecciones o datos corruptos. Siempre valida en el servidor, como revisar permisos antes de entrar a un edificio seguro.
- Olvidar manejar reconexión de WebSocket: Si la conexión se cae, la app deja de recibir actualizaciones. Implementa un loop de reconexión con backoff exponencial.
- Broadcast a todos los clientes sin filtrar: Enviar mensajes innecesarios gasta ancho de banda. Filtra por room o usuario, como notificar solo a miembros de un proyecto.
- Mezclar estado local y servidor: Sincronizar mal puede causar duplicados o pérdida de datos. Usa un store centralizado (como Svelte stores) que se actualice desde WebSocket.
- Ignorar errores de concurrencia: Si dos usuarios editan la misma tarea, puede haber conflictos. Usa timestamps o locks optimistas en la base de datos.
Checklist de dominio
- Configuré un endpoint WebSocket en SvelteKit que maneja múltiples conexiones y broadcast.
- Implementé al menos 3 Server Actions (crear, actualizar, eliminar) con validación robusta.
- Conecté el frontend al WebSocket y actualicé la UI en tiempo real sin recargar la página.
- Agregué manejo de errores para fallos de red y reconexión automática.
- Probé la app con dos navegadores simultáneos para verificar sincronización.
- Optimicé el rendimiento limitando re-renders innecesarios en componentes Svelte.
- Desplegué la app en una plataforma como Vercel o Railway y verifiqué funcionalidad en producción.
Implementa una funcionalidad de chat en tiempo real para la app de tareas
Extiende la app de tareas para incluir un chat en tiempo real donde los usuarios puedan comentar sobre tareas específicas. Sigue estos pasos:
- Crea una nueva tabla en tu base de datos para mensajes de chat, con campos: id, task_id, user_id, content, created_at.
- En el servidor, modifica el WebSocket para soportar rooms por task_id, de modo que los mensajes se envíen solo a usuarios viendo esa tarea.
- Añade una Server Action
sendMessageque valide el contenido, inserte en la base de datos y notifique al WebSocket. - En el frontend, agrega un componente de chat en la página de detalle de tarea, que se conecte al room correspondiente y muestre mensajes en tiempo real.
- Implementa indicadores de "usuario escribiendo" usando eventos de WebSocket adicionales.
- Prueba con múltiples usuarios en diferentes tareas para asegurar que los mensajes no se mezclen.
- Usa el parámetro task_id en la URL para determinar el room de WebSocket.
- Para "usuario escribiendo", envía un evento WebSocket cuando el usuario empiece a teclear y otro cuando pare, con debouncing para optimizar.
- Considera usar Supabase Realtime o similar si prefieres no manejar WebSocket manualmente, pero implementa al menos una versión básica propia.
Evalua tu comprension
Completa el quiz interactivo de arriba para ganar XP.