Svelte en el Backend: Integración con Node.js y Express para Aplicaciones Full-Stack Ligeras 🚀
Este tutorial te guiará paso a paso en la creación de una aplicación full-stack utilizando Svelte en el frontend y Node.js con Express en el backend. Explorarás cómo configurar un proyecto, conectar ambos lados y realizar operaciones CRUD para construir una aplicación ligera y eficiente. Ideal para desarrolladores que buscan simplificar el desarrollo web con Svelte.
Introducción: El Poder de Svelte en Full-Stack ✨
Svelte se ha ganado un lugar prominente en el desarrollo frontend por su enfoque innovador de compilación en lugar de tiempo de ejecución. Esto se traduce en aplicaciones extremadamente rápidas y con un bundle size mínimo. Tradicionalmente, Svelte se asocia con el frontend, pero ¿qué pasa si queremos construir una aplicación completa, con persistencia de datos y lógica de servidor? Aquí es donde entra en juego la integración con un backend robusto como Node.js y Express.
Este tutorial te mostrará cómo combinar lo mejor de ambos mundos: la reactividad y ligereza de Svelte para la interfaz de usuario, y la potencia y flexibilidad de Node.js/Express para la lógica de servidor y la API REST. Construiremos una aplicación de lista de tareas (To-Do List) sencilla pero completa, que te servirá como base para proyectos más ambiciosos.
Requisitos Previos 🛠️
Antes de sumergirnos en el código, asegúrate de tener instaladas las siguientes herramientas en tu sistema:
- Node.js: Versión 14 o superior. Puedes descargarlo desde nodejs.org.
- npm o Yarn: Gestores de paquetes que vienen incluidos con Node.js. Usaremos
npmen este tutorial. - Un editor de código: Recomendamos VS Code por su excelente soporte para JavaScript y Svelte.
1. Configuración del Proyecto: Frontend y Backend 📂
Comenzaremos creando una estructura de carpetas para nuestra aplicación full-stack. Tendremos una carpeta para el frontend (Svelte) y otra para el backend (Node.js/Express).
1.1. Estructura de Carpetas
Crea un directorio principal para tu proyecto y dentro de él, dos subdirectorios: client para Svelte y server para Node.js.
mkdir svelte-express-todo
cd svelte-express-todo
mkdir client server
1.2. Configuración del Frontend (Svelte con Vite) ⚛️
Nos dirigiremos a la carpeta client para inicializar nuestro proyecto Svelte. Usaremos Vite, que es un bundler extremadamente rápido y ligero, ideal para Svelte.
cd client
npm create vite@latest .
Cuando se te pida, selecciona svelte como framework y svelte como variant (o svelte-ts si prefieres TypeScript).
Instala las dependencias y verifica que el proyecto se ejecute correctamente:
npm install
npm run dev
Deberías ver tu aplicación Svelte ejecutándose en http://localhost:5173 (o un puerto similar). Abre tu navegador y compruébalo. Luego, detén el servidor de desarrollo (Ctrl + C).
1.3. Configuración del Backend (Node.js con Express) 🌐
Ahora, nos moveremos a la carpeta server para configurar nuestra API REST con Node.js y Express.
cd ../server
npm init -y
Esto creará un archivo package.json con la configuración por defecto. A continuación, instalaremos Express y cors (para manejar las solicitudes de Cross-Origin Resource Sharing desde nuestro frontend).
npm install express cors
Crea un archivo llamado server.js en la raíz de la carpeta server.
// server/server.js
const express = require('express');
const cors = require('cors');
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors()); // Habilitar CORS para permitir solicitudes desde el frontend
app.use(express.json()); // Habilitar el parsing de JSON para el cuerpo de las solicitudes
// Ruta de prueba
app.get('/', (req, res) => {
res.send('¡Servidor Express funcionando!');
});
app.listen(PORT, () => {
console.log(`Servidor Express escuchando en el puerto ${PORT}`);
});
Para ejecutar este servidor, añade un script start a tu package.json en la carpeta server:
// server/package.json
{
"name": "server",
"version": "1.0.0",
"description": "",
"main": "server.js",
"scripts": {
"start": "node server.js"
},
"keywords": [],
"author": "",
"license": "ISC",
"dependencies": {
"cors": "^2.8.5",
"express": "^4.19.2"
}
}
Ahora, desde la carpeta server, ejecuta:
npm start
Deberías ver Servidor Express escuchando en el puerto 3000 en tu terminal. Abre tu navegador y visita http://localhost:3000. Deberías ver el mensaje "¡Servidor Express funcionando!".
2. Desarrollando la API REST de Tareas (Backend) 🎯
Vamos a crear una API REST simple para gestionar nuestras tareas. Utilizaremos un array en memoria como base de datos, lo que es suficiente para este tutorial. En un proyecto real, integrarías una base de datos como MongoDB, PostgreSQL o SQLite.
Edita el archivo server/server.js para añadir las rutas CRUD (Crear, Leer, Actualizar, Borrar) para nuestras tareas.
// server/server.js (código completo)
const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid'); // Para generar IDs únicos
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
// --- Base de datos en memoria (para propósitos del tutorial) ---
let todos = [
{ id: uuidv4(), text: 'Aprender Svelte', completed: false },
{ id: uuidv4(), text: 'Construir API REST con Express', completed: true },
{ id: uuidv4(), text: 'Integrar Svelte y Express', completed: false }
];
// --- Rutas de la API ---
// GET /api/todos: Obtener todas las tareas
app.get('/api/todos', (req, res) => {
res.json(todos);
});
// POST /api/todos: Crear una nueva tarea
app.post('/api/todos', (req, res) => {
const { text } = req.body;
if (!text) {
return res.status(400).json({ message: 'El campo "text" es requerido.' });
}
const newTodo = { id: uuidv4(), text, completed: false };
todos.push(newTodo);
res.status(201).json(newTodo);
});
// PUT /api/todos/:id: Actualizar una tarea existente
app.put('/api/todos/:id', (req, res) => {
const { id } = req.params;
const { text, completed } = req.body;
const todoIndex = todos.findIndex(todo => todo.id === id);
if (todoIndex === -1) {
return res.status(404).json({ message: 'Tarea no encontrada.' });
}
// Actualizar solo los campos proporcionados
if (text !== undefined) todos[todoIndex].text = text;
if (completed !== undefined) todos[todoIndex].completed = completed;
res.json(todos[todoIndex]);
});
// DELETE /api/todos/:id: Eliminar una tarea
app.delete('/api/todos/:id', (req, res) => {
const { id } = req.params;
const initialLength = todos.length;
todos = todos.filter(todo => todo.id !== id);
if (todos.length === initialLength) {
return res.status(404).json({ message: 'Tarea no encontrada.' });
}
res.status(204).send(); // No Content
});
app.listen(PORT, () => {
console.log(`Servidor Express escuchando en el puerto ${PORT}`);
});
Necesitamos instalar uuid para generar IDs únicos. Detén el servidor (Ctrl + C) e instálalo:
npm install uuid
Luego, reinicia el servidor:
npm start
Puedes probar tu API usando herramientas como Postman, Insomnia o curl.
# Obtener todas las tareas
curl http://localhost:3000/api/todos
# Crear una nueva tarea
curl -X POST -H "Content-Type: application/json" -d '{"text": "Comprar leche"}' http://localhost:3000/api/todos
3. Conectando Svelte con el Backend (Frontend) 🔗
Ahora que nuestro backend está listo, vamos a construir la interfaz de usuario con Svelte para interactuar con esta API.
Dirígete a la carpeta client.
3.1. Estructura de Componentes
Simplificaremos la estructura por ahora. Editaremos el archivo client/src/App.svelte y crearemos un componente TodoItem.svelte.
3.2. App.svelte: Lógica Principal y Fetching de Datos 📖
En App.svelte, implementaremos la lógica para obtener, añadir, actualizar y eliminar tareas. Usaremos el hook onMount para cargar las tareas iniciales y funciones asíncronas para interactuar con la API.
<!-- client/src/App.svelte -->
<script>
import { onMount } from 'svelte';
import TodoItem from './TodoItem.svelte';
let todos = [];
let newTodoText = '';
const API_URL = 'http://localhost:3000/api/todos';
// Función para obtener todas las tareas
async function fetchTodos() {
try {
const response = await fetch(API_URL);
if (!response.ok) throw new Error('Error al obtener las tareas');
todos = await response.json();
} catch (error) {
console.error('Fetch error:', error);
}
}
// Función para añadir una nueva tarea
async function addTodo() {
if (!newTodoText.trim()) return;
try {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ text: newTodoText }),
});
if (!response.ok) throw new Error('Error al añadir la tarea');
const addedTodo = await response.json();
todos = [...todos, addedTodo]; // Añadir la nueva tarea al estado reactivo
newTodoText = ''; // Limpiar el input
} catch (error) {
console.error('Add todo error:', error);
}
}
// Función para cambiar el estado 'completed' de una tarea
async function toggleTodoCompleted(id, completed) {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ completed }),
});
if (!response.ok) throw new Error('Error al actualizar la tarea');
// Actualizar el estado local directamente para una reactividad instantánea
todos = todos.map(todo =>
todo.id === id ? { ...todo, completed } : todo
);
} catch (error) {
console.error('Toggle completed error:', error);
}
}
// Función para eliminar una tarea
async function deleteTodo(id) {
try {
const response = await fetch(`${API_URL}/${id}`, {
method: 'DELETE',
});
if (!response.ok) throw new Error('Error al eliminar la tarea');
// Filtrar la tarea eliminada del estado local
todos = todos.filter(todo => todo.id !== id);
} catch (error) {
console.error('Delete todo error:', error);
}
}
onMount(fetchTodos); // Cargar las tareas al montar el componente
</script>
<main>
<h1>Lista de Tareas Svelte + Express</h1>
<form on:submit|preventDefault={addTodo}>
<input
type="text"
bind:value={newTodoText}
placeholder="Añadir nueva tarea"
/>
<button type="submit">Añadir</button>
</form>
{#if todos.length > 0}
<ul>
{#each todos as todo (todo.id)}
<TodoItem
todo={todo}
on:toggleCompleted={event => toggleTodoCompleted(todo.id, event.detail)}
on:deleteTodo={() => deleteTodo(todo.id)}
/>
{/each}
</ul>
{:else}
<p>No hay tareas. ¡Añade una nueva!</p>
{/if}
</main>
<style>
main {
max-width: 600px;
margin: 40px auto;
padding: 20px;
background-color: #f9f9f9;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
font-family: 'Arial', sans-serif;
}
h1 {
color: #333;
text-align: center;
margin-bottom: 30px;
}
form {
display: flex;
margin-bottom: 20px;
}
input[type="text"] {
flex-grow: 1;
padding: 10px 15px;
border: 1px solid #ddd;
border-radius: 4px 0 0 4px;
font-size: 16px;
}
button[type="submit"] {
padding: 10px 20px;
background-color: #007bff;
color: white;
border: none;
border-radius: 0 4px 4px 0;
cursor: pointer;
font-size: 16px;
transition: background-color 0.2s ease;
}
button[type="submit"]:hover {
background-color: #0056b3;
}
ul {
list-style: none;
padding: 0;
}
p {
text-align: center;
color: #666;
}
</style>
3.3. TodoItem.svelte: Componente Individual de Tarea 📝
Este componente se encargará de mostrar una sola tarea y de emitir eventos cuando se quiera cambiar su estado o eliminarla.
Crea el archivo client/src/TodoItem.svelte:
<!-- client/src/TodoItem.svelte -->
<script>
import { createEventDispatcher } from 'svelte';
export let todo;
const dispatch = createEventDispatcher();
function handleToggle() {
dispatch('toggleCompleted', !todo.completed);
}
function handleDelete() {
dispatch('deleteTodo', todo.id);
}
</script>
<li class={todo.completed ? 'completed' : ''}>
<input
type="checkbox"
checked={todo.completed}
on:change={handleToggle}
/>
<span>{todo.text}</span>
<button on:click={handleDelete}>🗑️</button>
</li>
<style>
li {
display: flex;
align-items: center;
padding: 12px 15px;
margin-bottom: 8px;
background-color: white;
border: 1px solid #eee;
border-radius: 6px;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
transition: all 0.2s ease;
}
li:hover {
transform: translateY(-2px);
box-shadow: 0 3px 6px rgba(0, 0, 0, 0.08);
}
li.completed {
background-color: #e6ffe6; /* Fondo verde claro para completadas */
}
li.completed span {
text-decoration: line-through;
color: #888;
}
input[type="checkbox"] {
margin-right: 15px;
width: 20px;
height: 20px;
cursor: pointer;
accent-color: #28a745; /* Color del checkbox */
}
span {
flex-grow: 1;
font-size: 17px;
color: #444;
}
button {
background: none;
border: none;
font-size: 18px;
cursor: pointer;
color: #dc3545;
padding: 5px 8px;
border-radius: 4px;
transition: background-color 0.2s ease;
}
button:hover {
background-color: #f8d7da;
}
</style>
3.4. Ejecutando la Aplicación Full-Stack ✅
Ahora, asegúrate de que ambos servidores estén ejecutándose:
- En una terminal, desde la carpeta
server, ejecuta:npm start - En otra terminal, desde la carpeta
client, ejecuta:npm run dev
Abre tu navegador y visita http://localhost:5173. Deberías ver la aplicación de lista de tareas, capaz de interactuar con el backend: añadir nuevas tareas, marcar como completadas y eliminar.
4. Consideraciones de Despliegue y Producción 🚀
Desplegar una aplicación Svelte + Express requiere algunas consideraciones adicionales en comparación con el desarrollo local.
4.1. Construcción del Frontend
Para la producción, debes construir tu aplicación Svelte. Esto generará archivos estáticos (HTML, CSS, JS) optimizados para el navegador.
Desde la carpeta client:
npm run build
Esto creará una carpeta dist dentro de client con todos los archivos de producción. El siguiente paso es que nuestro servidor Express sirva estos archivos.
4.2. Servir Archivos Estáticos con Express
Modifica tu server/server.js para servir los archivos estáticos generados por Svelte. Necesitarás la ruta path de Node.js.
// server/server.js (adaptado para servir archivos estáticos)
const express = require('express');
const cors = require('cors');
const { v4: uuidv4 } = require('uuid');
const path = require('path'); // Importar el módulo path
const app = express();
const PORT = process.env.PORT || 3000;
app.use(cors());
app.use(express.json());
// --- Base de datos en memoria (para propósitos del tutorial) ---
let todos = [
{ id: uuidv4(), text: 'Aprender Svelte', completed: false },
{ id: uuidv4(), text: 'Construir API REST con Express', completed: true },
{ id: uuidv4(), text: 'Integrar Svelte y Express', completed: false }
];
// --- Rutas de la API ---
// ... (las mismas rutas GET, POST, PUT, DELETE de /api/todos) ...
// Asegúrate de que las rutas de la API estén antes de servir los archivos estáticos
// Servir archivos estáticos de Svelte para producción
// Apunta a la carpeta 'dist' que Vite crea dentro de 'client'
app.use(express.static(path.join(__dirname, '../client/dist')));
// Para cualquier otra ruta que no sea de la API, servir el index.html de Svelte
// Esto es crucial para las aplicaciones de una sola página (SPA) donde Svelte
// maneja el enrutamiento del lado del cliente.
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, '../client/dist/index.html'));
});
app.listen(PORT, () => {
console.log(`Servidor Express escuchando en el puerto ${PORT}`);
});
Ahora, cuando ejecutes npm start en la carpeta server, el servidor Express no solo proporcionará la API, sino que también servirá el frontend de Svelte. Tu aplicación completa estará accesible desde http://localhost:3000.
4.3. Despliegue a Producción
Para el despliegue real, puedes usar plataformas como Heroku, Vercel (solo para el frontend si usas SvelteKit), Netlify (similar a Vercel), DigitalOcean, AWS, etc.
- Ejemplo con Heroku: Podrías configurar un
Procfileen la raíz de tu proyecto para indicar a Heroku cómo iniciar tu servidor Express (que ahora sirve también el frontend).
web: node server/server.js
Asegúrate de que el `package.json` de la raíz o de la carpeta `server` tenga un script `start` adecuado.
- Variables de Entorno: En un entorno de producción, es crucial usar variables de entorno para la URL de la API, puertos de base de datos, credenciales, etc. Para Node.js, se usa
process.env.PORToprocess.env.NODE_ENV.
// server/server.js
const PORT = process.env.PORT || 3000;
// ...
// En Svelte (Vite) puedes usar import.meta.env.VITE_API_URL
// configurado en un archivo .env.development o .env.production
¿Por qué el CORS es importante?
CORS (Cross-Origin Resource Sharing) es un mecanismo de seguridad del navegador que restringe las solicitudes HTTP realizadas a dominios diferentes del que sirvió la página web inicial. Sin `cors()` en nuestro servidor Express, el navegador bloquearía las solicitudes de Svelte (ejecutándose en `localhost:5173`) a Express (ejecutándose en `localhost:3000`) durante el desarrollo. En producción, si ambos se sirven desde el mismo dominio, CORS no sería estrictamente necesario para las peticiones al propio backend, pero es una buena práctica incluirlo si la API pudiera ser consumida por otros clientes o dominios en el futuro.5. Próximos Pasos y Mejoras 📈
Este tutorial proporciona una base sólida para una aplicación full-stack con Svelte y Express. Aquí hay algunas ideas para llevar tu proyecto al siguiente nivel:
- Base de Datos Real: Integra una base de datos persistente como MongoDB (con Mongoose), PostgreSQL (con Sequelize o Knex), o SQLite (con
better-sqlite3). - Autenticación y Autorización: Añade JWT (JSON Web Tokens) o sesiones para manejar usuarios registrados y proteger rutas.
- Validación de Datos: Usa librerías como
Joioexpress-validatoren el backend para validar la entrada de datos. - Manejo de Errores Avanzado: Implementa un middleware de manejo de errores global en Express.
- Svelte Stores: Utiliza Svelte stores para una gestión de estado más compleja en el frontend, especialmente útil a medida que tu aplicación crece.
- Enrutamiento del Lado del Servidor: Aunque Svelte con Vite es una SPA por defecto, puedes explorar renderizado del lado del servidor (SSR) si el SEO o el tiempo de carga inicial son críticos, aunque esto te llevaría más hacia SvelteKit.
- Testing: Añade pruebas unitarias y de integración para tu API de Express (ej. con
MochayChai) y para tus componentes Svelte (ej. conVitestoCypress). - WebSockets: Para aplicaciones en tiempo real (chats, notificaciones), integra WebSockets (ej. con
Socket.IO) en tu backend Express y un cliente Svelte.
Conclusión 🎉
Has aprendido a construir una aplicación full-stack combinando la elegancia y eficiencia de Svelte en el frontend con la robustez de Node.js y Express en el backend. Esta arquitectura te permite desarrollar aplicaciones web completas y de alto rendimiento, aprovechando la simplicidad de Svelte para la interfaz de usuario y la flexibilidad de Node.js para la lógica del servidor.
La clave de esta integración reside en la comunicación a través de una API REST. Al dominar este patrón, estás bien equipado para construir una amplia variedad de aplicaciones, desde pequeños proyectos personales hasta sistemas empresariales más complejos. ¡Experimenta, construye y sigue aprendiendo! El mundo del desarrollo web es vasto y Svelte es una herramienta fantástica para explorarlo.
Tutoriales relacionados
- Svelte y Fetching de Datos: Estrategias para una Experiencia Reactivaintermediate15 min
- Svelte y Web Components: Encapsulando Componentes Reutilizables y el DOM Sombra 🎩intermediate15 min
- SvelteKit y la Generación de Sitios Estáticos (SSG): Despliegue Rápido y Eficiente 🚀intermediate20 min
- Gestión de Estado Reactiva con Svelte Stores: Una Guía Completaintermediate20 min
- SvelteKit y Server-Side Rendering (SSR): Optimizando la Carga y SEO 🚀intermediate15 min
Comentarios (0)
Aún no hay comentarios. ¡Sé el primero!