¿Se imagina un contenedor de reciclaje en el que ha colocado claramente una etiqueta que dice "Solo botellas de plástico", pero cuando alguien tira un trozo de papel dentro, lo acepta silenciosamente sin una sola palabra de protesta?
Esta es la experiencia escalofriante que viven los desarrolladores cuando conocen el sistema de tipos de SQLite por primera vez.
Si está acostumbrado al estricto estilo de agente de aduanas de PostgreSQL (donde los tipos incorrectos se rechazan directamente), la informalidad de SQLite podría hacerle cuestionar su vida.
Aún más aterrador, cuando quiere modificar la estructura de una tabla, le dirá:
"La tabla no se puede modificar directamente. Por favor, construya una casa nueva, mude los muebles y luego vuele la casa vieja."
Bajo el capó, SQLite solo tiene 5 clases de almacenamiento
No importa qué nombres de tipo elegantes declare en CREATE TABLE (VARCHAR(255), BIGINT, DECIMAL), SQLite bajo el capó solo reconoce estas 5 clases de almacenamiento:
| Clase de almacenamiento | Descripción |
|---|---|
| NULL | Valor nulo |
| INTEGER | Entero (ocupa automáticamente de 1 a 8 bytes según la magnitud) |
| REAL | Número de coma flotante (fijo de 8 bytes) |
| TEXT | Cadena de texto (codificación UTF-8 predeterminada) |
| BLOB | Objeto binario grande (almacenado exactamente como se ingresó) |
Los tipos que establece en las columnas son simplemente "sugerencias" para SQLite, no "reglas obligatorias".
Esto se llama "Type Affinity" (Afinidad de tipos).
SQLiteintentará convertir sus datos al tipo sugerido, pero si no puede, simplemente meterá los datos originales de todos modos sin lanzar ningún error.
Puede declarar una columna age INTEGER en SQLite y luego insertar la cadena 'siempre dieciocho'; lo aceptará con gusto.
Tres trampas de tipos más fáciles en las que caer
Trampa 1: Sin Boolean nativo
SQLite not tiene tipo booleano. True y False solo pueden representarse mediante los enteros 1 y 0.
Cuando recupera datos de SQLite usando Node.js, obtendrá el número 1 o 0, no true o false.
Si realiza directamente una verificación como if (user.is_admin === true), nunca será verdadera.
Trampa 2: Sin tipo Date/Time
SQLite no tiene tipo de fecha/hora. Solo puede almacenar el tiempo como:
| Método de almacenamiento | Ejemplo | Pros y contras |
|---|---|---|
| TEXT (cadena ISO-8601) | '2026-05-19T18:00:00Z' |
Más recomendado, alta legibilidad, conversión perfecta al pasar a PostgreSQL en el futuro |
| INTEGER (Unix Timestamp) | 1747656000 |
Huella pequeña, pero no legible por humanos |
Nunca almacene fechas en formatos personalizados arbitrarios como 19/5/2026 6:00 PM, de lo contrario, la migración de datos en el futuro será un desastre.
Trampa 3: Insertar una cadena en una columna Integer no arrojará un error
En PostgreSQL, insertar una cadena en una columna INTEGER arroja un error de inmediato. But SQLite solo intentará convertirla silenciosamente, y si falla, la aceptará tal como está.
Esto significa que datos sucios podrían colarse silenciosamente en su base de datos hasta que un día su programa falle debido a la recepción de un tipo inesperado, solo entonces descubrirá el problema.
Programación defensiva: Tratar una base de datos informal con una actitud estricta
Frente a la informalidad de SQLite, debe construir mecanismos de defensa estrictos en el desarrollo con Node.js:
| Nivel de defensa | Herramienta | Rol |
|---|---|---|
| Guardia en tiempo de compilación | TypeScript | Capturar tipos incorrectos en la etapa de escritura del código |
| Validación de entrada de API | Zod | Validar los datos entrantes estrictamente (asegurar que age sea siempre un número) |
| Conversión de tipos implícita | Prisma / Drizzle ORM | Manejar automáticamente las diferencias de tipos entre SQLite y PostgreSQL |
Mover al "guardián de la validación de datos" desde la capa de la base de datos a la capa de la aplicación
Node.jses una estrategia clave para aprovechar la velocidad de desarrollo de SQLite y al mismo tiempo garantizar la escalabilidad futura.
Al usar un ORM, siempre que declare type: 'boolean' en su código, el ORM lo convierte automáticamente a 1/0 al guardar en SQLite, y lo vuelve a convertir a true/false al leer, ocultando perfectamente las diferencias de tipos subyacentes.
ALTER TABLE está a medias: Qué se puede y qué no se puede modificar
El soporte de SQLite para modificar estructuras de tablas es muy limitado:
| Operasi | Didukung |
|---|---|
Agregar columna (ADD COLUMN) |
Sí |
Renombrar columna (RENAME COLUMN) |
Sí |
Eliminar columna (DROP COLUMN) |
Sí (en versiones más nuevas) |
Renombrar tabla (RENAME TO) |
Sí |
| Cambiar tipo de columna | No |
Agregar/eliminar restricciones UNIQUE, NOT NULL |
No |
| Modificar clave primaria | No |
| Modificar clave foránea | No |
Una vez que necesite realizar cualquiera de las modificaciones "no soportadas",
SQLiterequiere que ejecute la estrategia de "recrear y mover".
Los cuatro pasos para recrear y mover: La forma de actualizar tablas de SQLite
Dado que no se puede modificar directamente, la práctica estándar recomendada por los documentos oficiales es construir una casa nueva, mudar los muebles, volar la casa vieja y colocar la placa nueva:
| Paso | Acción | Descripción |
|---|---|---|
| 1 | Crear tabla nueva | CREATE TABLE users_new (...) usando la estructura correcta |
| 2 | Copiar datos | INSERT INTO users_new SELECT ... FROM users |
| 3 | Eliminar tabla vieja | DROP TABLE users |
| 4 | Renombrar tabla | ALTER TABLE users_new RENAME TO users |
Estos cuatro pasos deben ejecutarse de un solo tirón; cualquier corte de energía o caída de la aplicación a mitad del camino resultará en la pérdida de datos.
Garantizar que las actualizaciones no pierdan datos: Dos líneas de defensa de seguridad
Línea de defensa 1: Defensa física, copiar el archivo directamente
SQLite es esencialmente solo un archivo. Antes de ejecutar cualquier cambio de esquema, simplemente haga una copia del archivo .db como respaldo.
const fs = require('fs');
fs.copyFileSync('my_project.db', 'my_project_backup.db');
Si las cosas salen mal, reemplazar el archivo restaura todo en un instante. Esta es una ventaja que otras bases de datos grandes no pueden ofrecer.
Línea de defensa 2: Contenedor de Transaction, la máquina del tiempo de la base de datos
Envuelva todos los pasos de migración dentro de una sola Transaction; si algún paso falla, todo el proceso se revertirá automáticamente (Rollback) como si nunca hubiera pasado nada.
const Database = require('better-sqlite3');
const db = new Database('my_project.db');
const migrateData = db.transaction(() => {
db.prepare(`
CREATE TABLE users_new (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
age INTEGER NOT NULL DEFAULT 18
)
`).run();
db.prepare(`
INSERT INTO users_new (id, name, age)
SELECT id, name, COALESCE(age, 18) FROM users
`).run();
db.prepare('DROP TABLE users').run();
db.prepare('ALTER TABLE users_new RENAME TO users').run();
});
try {
migrateData();
console.log('Tabla actualizada con éxito');
} catch (error) {
console.error('La actualización falló, datos restaurados de forma segura:', error.message);
}
"Archivo de respaldo + Vinculación de Transaction" es el airbag de la migración de su base de datos.
Controle la informalidad de SQLite con una actitud estricta
Domine la informalidad de
SQLitecon una arquitectura estricta en la capa de aplicación para disfrutar de su velocidad de desarrollo ultrarrápida y evitar deudas técnicas futuras.
El sistema de tipos de SQLite es muy informal, y ALTER TABLE tiene muchas limitaciones.
Sin embargo, siempre que realice comprobación de tipos con TypeScript + validación con Zod + abstracción con ORM en el lado de Node.js, combinado con la estrategia de seguridad de respaldo físico + Transaction, puede disfrutar con seguridad de la eficiencia de desarrollo que ofrece SQLite mientras pavimenta el camino para migrar a PostgreSQL en el futuro.