Indice Tutorial >>
- Tutorial de Tauri - Capítulo 1: Qué es Tauri y por qué deberías aprenderlo
- Tutorial de Tauri - Capítulo 2: Configuración del Entorno de Desarrollo
- Tutorial de Tauri - Capítulo 3: Tu primera aplicación Tauri
- Tauri 2.0: Aplicaciones de Escritorio con React y Rust — Ya disponible en Amazon
- Tauri vs Electron: ¿cuál elegir para tu aplicación de escritorio en 2026?
- Tutorial de Tauri - Capítulo 4: Estructura de un proyecto Tauri
- Tutorial de Tauri - Capítulo 5: Integración con React
- Tutorial de Tauri - Capítulo 6: Rust básico para desarrolladores web
- Tutorial de Tauri - Capítulo 7: Commands - Comunicación de JavaScript a Rust
- Tutorial de Tauri - Capítulo 8: Eventos - Comunicación bidireccional
- Tutorial de Tauri - Capítulo 9: Acceso al sistema de archivos
- Tutorial de Tauri - Capítulo 10: Bases de datos con SQLite
- Tutorial de Tauri - Capítulo 11: Menús, diálogos y bandejas del sistema
- Tutorial de Tauri - Capítulo 12: Proyecto práctico - Aplicación de notas
Guardar datos en archivos de texto funciona para cosas simples, como la nota del capítulo anterior. Pero cuando necesitas almacenar muchos datos con estructura (usuarios, tareas, productos…), buscar entre ellos y filtrarlos, necesitas una base de datos.
SQLite es perfecta para aplicaciones de escritorio: es ligera, no necesita instalar ningún servidor, y toda la base de datos se guarda en un único archivo. Es la misma base de datos que usan WhatsApp, Firefox y muchas apps móviles.
SQL en 2 minutos
Si nunca has usado SQL, aquí va lo mínimo que necesitas saber. Una base de datos es como una hoja de cálculo: tiene tablas (como las hojas), y cada tabla tiene columnas (los campos) y filas (los datos). Para trabajar con los datos usas estas 4 operaciones:
| Operación | SQL | Equivalente |
|---|---|---|
| Crear | INSERT INTO | Añadir una fila nueva |
| Leer | SELECT | Buscar y obtener filas |
| Actualizar | UPDATE | Modificar una fila existente |
| Eliminar | DELETE | Borrar una fila |
Con esto es suficiente. Verás cada una en la práctica a medida que construimos el proyecto.
Crear el proyecto
Si tienes la aplicación anterior ejecutándose, párala con Ctrl+C. Vamos a crear un proyecto nuevo para hacer una lista de tareas con base de datos.
npm create tauri-app@latest
Responde a las preguntas:
- Project name: tauri-tareas
- Identifier: com.tutorial.tauritareas
- Frontend language: TypeScript / JavaScript
- Package manager: npm
- UI template: React
- UI flavor: JavaScript
Entra en el proyecto e instala las dependencias:
cd tauri-tareas
npm install
Instalar el plugin SQL
Instala el plugin de SQL con este comando:
npm run tauri add sql
Ahora necesitas verificar que la dependencia de Rust incluya el soporte para SQLite. Abre src-tauri/Cargo.toml y busca la línea de tauri-plugin-sql. Asegúrate de que tenga features = ["sqlite"]:
tauri-plugin-sql = { version = "2", features = ["sqlite"] }
Si solo pone tauri-plugin-sql = "2" sin los features, cámbialo a la línea de arriba.
Ahora verifica que el plugin esté registrado en src-tauri/src/lib.rs. Debería tener esta línea dentro de la función run():
.plugin(tauri_plugin_sql::Builder::default().build())
Si no la ves, añádela. Tu función run() debería quedar parecida a esto:
pub fn run() {
tauri::Builder::default()
.plugin(tauri_plugin_sql::Builder::default().build())
.plugin(tauri_plugin_opener::init())
.run(tauri::generate_context!())
.expect("error while running tauri application");
}
Configurar permisos
Abre src-tauri/capabilities/default.json y comprueba que el permiso "sql:default" esté en la lista de permissions. Si el comando anterior lo añadió automáticamente, ya lo tendrás. Si no, añádelo tú. Además, necesitas añadir dos permisos extra: "sql:allow-execute" para poder crear tablas e insertar datos, y "sql:allow-select" para poder consultar datos. El archivo debería quedar así:
{
"identifier": "default",
"windows": ["main"],
"permissions": [
"core:default",
"opener:default",
"sql:default",
"sql:allow-execute",
"sql:allow-select"
]
}
Construir la lista de tareas
Vamos a construir una lista de tareas que permita:
- Añadir tareas nuevas
- Marcar tareas como completadas (y desmarcarlas)
- Eliminar tareas
- Que los datos se guarden permanentemente en la base de datos
Todo el código va en un solo componente para que sea fácil de seguir. Crea el archivo src/ListaTareas.jsx:
import { useState, useEffect } from "react";
import Database from "@tauri-apps/plugin-sql";
function ListaTareas() {
const [db, setDb] = useState(null);
const [tareas, setTareas] = useState([]);
const [nuevoTitulo, setNuevoTitulo] = useState("");
// Al abrir la app, conectar a la base de datos
useEffect(() => {
let cancelado = false;
async function iniciarDB() {
// Conectar a SQLite (crea el archivo si no existe)
const database = await Database.load("sqlite:tareas.db");
// Crear la tabla si no existe
await database.execute(`
CREATE TABLE IF NOT EXISTS tareas (
id INTEGER PRIMARY KEY AUTOINCREMENT,
titulo TEXT NOT NULL,
completada INTEGER DEFAULT 0
)
`);
if (cancelado) return;
setDb(database);
// Cargar las tareas existentes
const resultado = await database.select(
"SELECT * FROM tareas ORDER BY id DESC"
);
if (!cancelado) setTareas(resultado);
}
iniciarDB();
return () => { cancelado = true; };
}, []);
async function cargarTareas() {
if (!db) return;
const resultado = await db.select(
"SELECT * FROM tareas ORDER BY id DESC"
);
setTareas(resultado);
}
async function agregarTarea(e) {
e.preventDefault();
if (!db || !nuevoTitulo.trim()) return;
await db.execute(
"INSERT INTO tareas (titulo) VALUES ($1)",
[nuevoTitulo]
);
setNuevoTitulo("");
await cargarTareas();
}
async function toggleTarea(id, completadaActual) {
if (!db) return;
await db.execute(
"UPDATE tareas SET completada = $1 WHERE id = $2",
[completadaActual ? 0 : 1, id]
);
await cargarTareas();
}
async function eliminarTarea(id) {
if (!db) return;
await db.execute(
"DELETE FROM tareas WHERE id = $1",
[id]
);
await cargarTareas();
}
if (!db) return <p>Cargando base de datos...</p>;
return (
<div style={{ maxWidth: "600px", margin: "0 auto", padding: "2rem" }}>
<h1>Lista de Tareas</h1>
<form onSubmit={agregarTarea} style={{ display: "flex", gap: "0.5rem", marginBottom: "1rem" }}>
<input
value={nuevoTitulo}
onChange={(e) => setNuevoTitulo(e.target.value)}
placeholder="Nueva tarea..."
style={{
flex: 1,
padding: "0.8rem",
borderRadius: "8px",
border: "1px solid #444",
background: "#2a2a2a",
color: "white",
fontSize: "1rem",
}}
/>
<button
type="submit"
style={{
padding: "0.8rem 1.5rem",
background: "#24c8db",
color: "#1a1a1a",
border: "none",
borderRadius: "8px",
fontWeight: "bold",
cursor: "pointer",
}}
>
Añadir
</button>
</form>
{tareas.length === 0 ? (
<p style={{ color: "#888" }}>No hay tareas. Añade una para empezar.</p>
) : (
<ul style={{ listStyle: "none", padding: 0 }}>
{tareas.map((tarea) => (
<li
key={tarea.id}
style={{
display: "flex",
alignItems: "center",
gap: "0.5rem",
padding: "0.8rem",
background: "#2a2a2a",
borderRadius: "8px",
marginBottom: "0.5rem",
}}
>
<span
onClick={() => toggleTarea(tarea.id, tarea.completada)}
style={{
flex: 1,
textDecoration: tarea.completada ? "line-through" : "none",
color: tarea.completada ? "#888" : "white",
cursor: "pointer",
}}
>
{tarea.completada ? "☑" : "☐"} {tarea.titulo}
</span>
<button
onClick={() => eliminarTarea(tarea.id)}
style={{
background: "#e74c3c",
color: "white",
border: "none",
borderRadius: "4px",
padding: "0.3rem 0.8rem",
cursor: "pointer",
}}
>
Eliminar
</button>
</li>
))}
</ul>
)}
<p style={{ color: "#888", fontSize: "0.9rem" }}>
{tareas.length} tarea{tareas.length !== 1 ? "s" : ""} en total
</p>
</div>
);
}
export default ListaTareas;
Es bastante código, pero todo tiene sentido. Vamos a ver qué hace cada parte:
Conexión y creación de la tabla
let cancelado = falseyreturn () => { cancelado = true; }: esto evita que React ejecute la inicialización dos veces en modo desarrollo. Es un patrón estándar de React para limpiar efectos.Database.load("sqlite:tareas.db"): conecta a la base de datos SQLite. El prefijosqlite:indica el tipo. Si el archivotareas.dbno existe, lo crea automáticamente en la carpeta de datos de tu aplicación.CREATE TABLE IF NOT EXISTS tareas (...): crea la tabla si no existe. Así la app funciona tanto la primera vez (tabla nueva) como las siguientes (tabla existente con datos).id INTEGER PRIMARY KEY AUTOINCREMENT: cada tarea tiene un ID numérico que se genera solo.titulo TEXT NOT NULL: el texto de la tarea, obligatorio.completada INTEGER DEFAULT 0: 0 = pendiente, 1 = completada. Por defecto, pendiente.
Operaciones con datos
db.execute("INSERT INTO tareas (titulo) VALUES ($1)", [nuevoTitulo]): añade una tarea nueva. El$1es un parámetro que se sustituye por el valor del array. Usar parámetros en vez de concatenar strings previene ataques de inyección SQL.db.select("SELECT * FROM tareas ORDER BY id DESC"): obtiene todas las tareas ordenadas de más nueva a más antigua. Devuelve un array de objetos.db.execute("UPDATE tareas SET completada = $1 WHERE id = $2", [valor, id]): cambia el estado de completada de la tarea con ese ID.db.execute("DELETE FROM tareas WHERE id = $1", [id]): elimina la tarea con ese ID.
La interfaz
- El formulario llama a
agregarTareaal pulsar «Añadir» o presionar Enter. - Al hacer clic en el texto de una tarea, se marca/desmarca como completada (
toggleTarea). Las completadas aparecen tachadas. - El botón «Eliminar» borra la tarea de la base de datos.
- Después de cada operación, se llama a
cargarTareas()para refrescar la lista desde la base de datos.
Ahora abre src/App.jsx y reemplaza todo su contenido por:
import ListaTareas from "./ListaTareas";
function App() {
return <ListaTareas />;
}
export default App;
Ejecuta la aplicación:
npm run tauri dev
Añade algunas tareas, marca alguna como completada, elimina otra. Ahora cierra la aplicación y vuelve a abrirla — todas las tareas siguen ahí, guardadas en la base de datos SQLite.

Referencia rápida de SQL
Estas son las operaciones SQL que puedes usar con el plugin. Todas se ejecutan con db.execute() (para escribir) o db.select() (para leer):
// Insertar
await db.execute(
"INSERT INTO tabla (campo1, campo2) VALUES ($1, $2)",
["valor1", "valor2"]
);
// Seleccionar todo
const filas = await db.select("SELECT * FROM tabla");
// Seleccionar con filtro
const filtradas = await db.select(
"SELECT * FROM tabla WHERE campo1 = $1",
["valor"]
);
// Actualizar
await db.execute(
"UPDATE tabla SET campo1 = $1 WHERE id = $2",
["nuevo valor", id]
);
// Eliminar
await db.execute(
"DELETE FROM tabla WHERE id = $1",
[id]
);
// Contar filas
const resultado = await db.select(
"SELECT COUNT(*) as total FROM tabla"
);
const total = resultado[0].total;
Resumen
En este capítulo has aprendido a:
- Instalar el plugin SQL con
npm run tauri add sql - Conectar a una base de datos SQLite con
Database.load() - Crear tablas con
CREATE TABLE IF NOT EXISTS - Insertar datos con
INSERT INTO - Consultar datos con
SELECT - Actualizar datos con
UPDATE - Eliminar datos con
DELETE - Usar parámetros (
$1, $2) para prevenir inyección SQL
En el próximo capítulo aprenderás a crear menús nativos, bandejas del sistema y diálogos.
Nos vemos en el Capítulo 11.
¿Te está gustando este tutorial?
Este tutorial forma parte del libro Tauri 2.0: Aplicaciones de Escritorio con React y Rust, disponible en Amazon en formato papel y ebook. El libro incluye todos los capítulos, tres proyectos completos paso a paso y contenido exclusivo que no encontrarás en el blog.
Un saludo, y si aún no lo has hecho no olvides suscribirte a mi blog para no perderte los próximos posts :-),También puedes seguirme en Twitter en @revi_apps y no olvides que me ayudas mucho si compartes este post en las redes sociales.
