Bases de datos para SaaS: Postgres + Drizzle en términos simples
Guía para principiantes sobre PostgreSQL y Drizzle ORM para SaaS: tablas, filas, relaciones, por qué Postgres, esquema tipado en TypeScript, consultas, índices, seguridad de datos, migraciones y local vs producción.
Bases de datos para SaaS: Postgres + Drizzle en términos simples
Al construir un SaaS necesitas almacenar datos: usuarios, pedidos, posts, sesiones, etc. Esta guía explica bases de datos usando PostgreSQL (Postgres) como elección por defecto y muestra cómo Drizzle ORM te permite definir y consultar el esquema con TypeScript.
Aprenderás:
- Qué son tablas, filas, columnas y relaciones.
- Por qué Postgres es un gran default.
- Cómo Drizzle define esquemas y consultas tipadas.
- Por qué índices y constraints mantienen datos rápidos y limpios.
- Diferencias entre local y producción.
- Qué son las migraciones y por qué importan.
Tablas, filas y relaciones (sin jerga)
Piensa en una tabla como una hoja de cálculo:
- Tabla: colección de datos (por ejemplo
users). - Fila: una entrada (un usuario).
- Columna: un dato por fila (email, created_at, plan).
- Primary key: ID único por fila.
- Relación: un vínculo entre tablas, normalmente por ID.
Ejemplo: una tabla sessions puede incluir user_id que apunta a una fila de users. Esa es la relación. Por eso Postgres es relacional.
En simple: las tablas guardan tipos de cosas, las filas guardan cosas individuales y las relaciones conectan cosas.
Por qué PostgreSQL es un gran default
Postgres se recomienda mucho como opción por defecto [1] porque es:
- Confiable: décadas de uso y transacciones ACID.
- Generalista: sirve para SaaS, finanzas, contenido y más.
- Escalable: puedes empezar pequeño y crecer [1].
- Bien soportado: muchas opciones de hosting gestionado [2].
- Open source: sin lock‑in ni licencias.
- Compatible: otros sistemas hablan “Postgres” [2].
Salvo que tengas un caso muy especial, Postgres es una base sólida para un SaaS.
Drizzle ORM: esquema tipado en TypeScript
Un ORM (Object‑Relational Mapper) te permite hablar con la DB desde tu lenguaje en lugar de escribir SQL crudo. Drizzle es un ORM moderno en TypeScript que mantiene SQL cercano, con seguridad de tipos.
Por qué Drizzle es amigable:
- Fuente única de verdad: defines el esquema en TypeScript y lo reutilizas en consultas y migraciones [3].
- Type safety: si un campo no existe, TypeScript avisa.
- Sin strings mágicos: construyes queries con funciones, no con strings.
Ejemplo de esquema en Drizzle (sessions):
import { pgTable, varchar, text, timestamp, index, uniqueIndex } from "drizzle-orm/pg-core";
export const sessions = pgTable(
"sessions",
{
id: varchar({ length: 255 }).primaryKey(), // Primary key column, a string ID
user_id: varchar({ length: 255 }).notNull(), // ID of the user who owns this session (must have a value)
token: varchar({ length: 512 }).notNull(), // A unique session token (must have a value)
expires_at: timestamp({ withTimezone: true }).notNull(), // When the session expires
ip_address: varchar({ length: 255 }), // (Optional) IP address of the session
user_agent: text(), // (Optional) User agent string of the browser/device
created_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
updated_at: timestamp({ withTimezone: true }).notNull().defaultNow(),
},
(table) => [
// Indexes and constraints:
uniqueIndex("sessions_token_unique_idx").on(table.token), // Ensure no two sessions have the same token
index("sessions_user_id_idx").on(table.user_id), // Index to quickly look up sessions by user_id
]
);En términos simples:
sessionses el nombre de la tabla.id,user_id,token,expires_atson obligatorios.ip_addressyuser_agentson opcionales.created_atyupdated_atse rellenan automáticamente.- Índice único en
tokenevita duplicados. - Índice en
user_idacelera consultas.
El esquema en TypeScript permite detectar errores temprano y sirve como base para migraciones.
Consultar con Drizzle (índices y seguridad)
Drizzle ofrece una API fluida para select/insert/update/delete y genera SQL seguro.
Ejemplo SQL:
SELECT *
FROM credits
WHERE user_uuid = 'some-user-id'
ORDER BY created_at DESC;Ejemplo Drizzle:
import { db } from "@/db"; // our Drizzle database instance
import { credits as creditsTable } from "@/db/schema"; // the credits table schema we defined
const userId = "some-user-uuid";
const userCredits = await db.select().from(creditsTable)
.where(creditsTable.user_uuid.eq(userId))
.orderBy(creditsTable.created_at.desc());Por qué es más seguro:
- Type safety: nombres de columnas validados en compilación.
- Queries parametrizadas: reduce riesgo de SQL injection.
- Intención clara: el código refleja la consulta.
Índices, constraints y seguridad básica:
- Índices: aceleran búsquedas frecuentes.
- Constraints únicas: evitan duplicados (email único, token único).
- NOT NULL: evita datos incompletos.
- Tipos de datos: Postgres hace cumplir tipos; Drizzle los refleja.
- Transacciones: agrupan operaciones para éxito/fracaso conjunto.
- Backups: habilítalos en producción.
Base local vs base de producción
Desarrollo local:
- Corre en tu laptop o Docker.
- Puedes resetear sin riesgo.
- Usa
DATABASE_URLen.env. - Datos de prueba.
Producción:
- Datos reales; no experimentes aquí.
- DB gestionada (Neon, Supabase, RDS, etc.).
- Credenciales seguras, backups, monitoreo.
- Migraciones cuidadosas al desplegar [5].
Clave: separa dev y prod, y prueba migraciones en local antes de producción.
Preguntas comunes
P: ¿Necesito aprender SQL para usar Postgres y Drizzle?
R: No al principio, pero ayuda mucho a largo plazo. Un ORM evita escribir SQL crudo, pero SQL es útil para debug y performance [4].
Bases útiles:
- Modelado de datos: tablas y relaciones.
- Consultas básicas:
SELECT,INSERT,UPDATE,DELETE,WHERE. - Joins: combinar datos relacionados.
- Agregaciones: contar, sumar, promediar.
Drizzle no oculta SQL; te ayuda a usarlo de forma segura. Piensa en el ORM como rueditas: te acelera, pero SQL te hace más fuerte.
P: ¿Qué pasa si cambio una columna o tabla? (migraciones)
Cuando cambias el esquema en código, la base real no se actualiza sola. Las migraciones sincronizan ambos.
Cómo funcionan con Drizzle:
- Actualiza el esquema en TypeScript.
- Ejecuta
drizzle-kit generatepara crear migraciones [5]. - Revisa la migración (Drizzle puede preguntar si es rename).
- Aplica con
drizzle-kit migrate. - Versiona los archivos de migración.
Ejemplo: cambiar credits de INT a BIGINT requiere migración. En producción, haz backup y aplica migraciones en el deploy.
Siguientes pasos: prueba migraciones
Sigue el README de tu proyecto para generar y aplicar migraciones. Por ejemplo:
npm run db:generate # generates a migration based on schema differences
npm run db:migrate # applies the migration to the databaseLuego prueba:
- Insertar un dato de prueba.
- Consultarlo con Drizzle.
- Añadir una columna y volver a migrar.
Así verás cómo el esquema en TS se convierte en SQL y mantiene tu DB alineada.
Referencias
Frontend vs Backend vs Full‑Stack para principiantes SaaS
Guía amigable para entender frontend, backend y full‑stack en SaaS, con una analogía fácil y ejemplos de un stack moderno.
Guía de presupuesto SaaS — costes, hosting y lo que importa
Guía práctica sobre los costes reales de iniciar un SaaS: hosting, base de datos, pagos, email y en qué se va el dinero. Empieza gratis, valida rápido y paga solo al crecer.