ixiclinicDocs
DesarrolladoresGuías

Añadir una tabla al schema

Crear una tabla Drizzle, registrarla y generar/aplicar la migración en las bases de dev y de producción.

La base de datos es PostgreSQL con Drizzle ORM. Los esquemas viven en apps/api/src/db/schema/, organizados por dominio (un archivo por dominio). Drizzle Kit lee todos los archivos de esa carpeta:

// apps/api/drizzle.config.ts
export default defineConfig({
  schema: './src/db/schema/*',
  out: './src/db/migrations',
  dialect: 'postgresql',
  dbCredentials: { url: process.env.DATABASE_URL! },
});

Importante: drizzle.config.ts lee .env.local (vía dotenv), así que apunta a la base de dev. La de producción se migra aparte (ver el último paso).

Pasos

1. Define la tabla en apps/api/src/db/schema/

Crea un archivo nuevo (o edita el del dominio que corresponda). Sigue el estilo de apps/api/src/db/schema/tenants.ts: toda tabla con datos de cliente lleva id UUID, tenantId con FK a tenants (onDelete: "cascade") y timestamps.

// apps/api/src/db/schema/widgets.ts
import {
  pgTable,
  uuid,
  varchar,
  jsonb,
  timestamp,
} from "drizzle-orm/pg-core";
import { tenants } from "./tenants.js";

export const widgets = pgTable("widgets", {
  id: uuid("id").defaultRandom().primaryKey(),
  tenantId: uuid("tenant_id")
    .references(() => tenants.id, { onDelete: "cascade" })
    .notNull(),
  name: varchar("name", { length: 255 }).notNull(),
  color: varchar("color", { length: 50 }),
  settings: jsonb("settings").default({}).notNull(),
  createdAt: timestamp("created_at").defaultNow().notNull(),
  updatedAt: timestamp("updated_at").defaultNow().notNull(),
});

2. Regístrala en apps/api/src/db/index.ts

Drizzle Kit detecta el archivo por el glob, pero el cliente db en tiempo de ejecución arma su schema importando cada archivo explícitamente. Si no lo agregas aquí, las queries relacionales no conocerán la tabla:

// apps/api/src/db/index.ts
import * as widgetSchema from './schema/widgets.js';

export const db = drizzle(client, {
  schema: {
    // ...los demás...
    ...widgetSchema,
  },
});

3. Genera la migración

Desde la raíz del monorepo:

pnpm --filter @ixiclinic/api db:generate

Esto crea un .sql nuevo en apps/api/src/db/migrations/ con el CREATE TABLE.

Nota: db:generate puede arrastrar drift (cambios no relacionados que ya existen en la BD pero no en el historial de migraciones). Revisa el .sql generado y déjalo solo con lo nuevo de tu cambio; si entra drift, migrate.js puede romper el arranque de producción. Ver Migraciones.

4. Aplica la migración en dev

pnpm --filter @ixiclinic/api db:migrate

Para iterar rápido en dev (sin generar archivo) puedes usar db:push, pero no lo uses para cambios que vayan a producción — ahí siempre va por migración versionada.

5. Aplica el cambio en producción

⚠️ El esquema vive en DOS bases distintas:

  • Dev: Neon Serverless PostgreSQL.
  • Producción: PostgreSQL en Docker en el VPS (contenedor ixiclinic-postgres).

Un cambio de esquema debe aplicarse a ambas. Tras desplegar el código, corre la migración en prod siguiendo el procedimiento de Migraciones y Neon vs Docker. En prod la API corre las migraciones con db:migrate:prod (node dist/migrate.js) sobre el contenedor de Postgres.

Scripts db:* (en apps/api)

ScriptQué hace
db:generateGenera el .sql de migración desde el schema.
db:migrateAplica migraciones pendientes (dev, Neon).
db:migrate:prodAplica migraciones en prod (node dist/migrate.js).
db:pushEmpuja el schema directo a la BD — solo dev.
db:studioAbre Drizzle Studio.
db:seedCarga datos de prueba.

Checklist

  1. Tabla definida en apps/api/src/db/schema/<dominio>.ts (UUID + tenantId FK + timestamps).
  2. Registrada en el schema de apps/api/src/db/index.ts.
  3. pnpm --filter @ixiclinic/api db:generate y revisar el .sql (sin drift).
  4. pnpm --filter @ixiclinic/api db:migrate en dev (Neon).
  5. Migración aplicada también en producción (Docker Postgres del VPS).

Más detalle en Migraciones · Drizzle.

On this page