Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/DrDigett/Babel/llms.txt

Use this file to discover all available pages before exploring further.

BaBel+ persists all data in PostgreSQL using Drizzle ORM with the pg driver. Migrations run automatically every time the server starts, so you never need to apply them manually in normal operation. On first launch, if the nodes table is empty, a seed script populates roughly 20 example nodes — books, films, people, and concepts drawn from philosophy and political cinema — along with their relations and a default list named “Todos los nodos”.

Schema overview

The database contains four tables. The entity-relationship flow is: nodesrelationsnodes, and listslist_nodesnodes.

nodes

The primary content table. Every book, film, concept, author, or event you track in BaBel+ is stored as a node.
ColumnTypeConstraintsNotes
idtextPrimary keyUUID string generated in application code
titletextNot nullDisplay name of the node
typetextNot nullE.g. libro, pelicula, concepto, autor, escuela, evento
descriptiontextNullableFree-text summary
statustextNot null, default pendienteReading/viewing progress
prioritytextNot null, default mediaImportance (alta, media, baja)
tagstextNullableJSON-encoded string array of tag keywords
authortextNullableCreator(s) of the work
yearintegerNullablePublication or release year
linktextNullableExternal URL
local_filetextNullablePath to a local file
ratingintegerNullablePersonal numeric rating
created_attextNot nullISO 8601 timestamp
updated_attextNot nullISO 8601 timestamp

relations

Directed, weighted edges between nodes. Deleting either endpoint cascades the deletion to the relation.
ColumnTypeConstraintsNotes
idtextPrimary keyUUID string
source_idtextNot null, FK → nodes.id cascadeOrigin node of the edge
target_idtextNot null, FK → nodes.id cascadeDestination node of the edge
typetextNot nullSemantic label, e.g. trata_sobre, es_autor_de, influyo_a
weightrealNot null, default 1.0Edge strength used by graph layout
created_attextNot nullISO 8601 timestamp

lists

Named, ordered collections of nodes — similar to playlists or reading lists.
ColumnTypeConstraintsNotes
idtextPrimary key4-character alphanumeric slug
nametextNot nullDisplay name of the list
descriptiontextNullableOptional summary
created_attextNot nullISO 8601 timestamp
updated_attextNot nullISO 8601 timestamp

list_nodes

Junction table that assigns nodes to lists with an explicit position and an optional per-list rating.
ColumnTypeConstraintsNotes
idtextPrimary keyUUID string
list_idtextNot null, FK → lists.id cascadeParent list
node_idtextNot null, FK → nodes.id cascadeMember node
positionintegerNot null, default 0Sort order within the list
ratingintegerNullablePer-list rating override
created_attextNot nullISO 8601 timestamp
A unique constraint on (list_id, node_id) prevents duplicate membership.

Drizzle schema source

The full TypeScript schema lives at packages/server/src/db/schema.ts:
import { pgTable, text, integer, real, unique } from 'drizzle-orm/pg-core'

export const nodes = pgTable('nodes', {
  id: text('id').primaryKey(),
  title: text('title').notNull(),
  type: text('type').notNull(),
  description: text('description'),
  status: text('status').notNull().default('pendiente'),
  priority: text('priority').notNull().default('media'),
  tags: text('tags'),
  author: text('author'),
  year: integer('year'),
  link: text('link'),
  localFile: text('local_file'),
  rating: integer('rating'),
  createdAt: text('created_at').notNull(),
  updatedAt: text('updated_at').notNull(),
})

export const relations = pgTable('relations', {
  id: text('id').primaryKey(),
  sourceId: text('source_id').notNull().references(() => nodes.id, { onDelete: 'cascade' }),
  targetId: text('target_id').notNull().references(() => nodes.id, { onDelete: 'cascade' }),
  type: text('type').notNull(),
  weight: real('weight').notNull().default(1.0),
  createdAt: text('created_at').notNull(),
})

export const lists = pgTable('lists', {
  id: text('id').primaryKey(),
  name: text('name').notNull(),
  description: text('description'),
  createdAt: text('created_at').notNull(),
  updatedAt: text('updated_at').notNull(),
})

export const listNodes = pgTable('list_nodes', {
  id: text('id').primaryKey(),
  listId: text('list_id').notNull().references(() => lists.id, { onDelete: 'cascade' }),
  nodeId: text('node_id').notNull().references(() => nodes.id, { onDelete: 'cascade' }),
  position: integer('position').notNull().default(0),
  rating: integer('rating'),
  createdAt: text('created_at').notNull(),
}, (table) => ({
  uniqueListNode: unique().on(table.listId, table.nodeId),
}))

Running migrations

BaBel+ uses a hand-written migrate() function in packages/server/src/db/migrate.ts that issues CREATE TABLE IF NOT EXISTS and ALTER TABLE … ADD COLUMN IF NOT EXISTS statements. This means migrations are idempotent — they are safe to run multiple times.

Automatic migration on startup

The server’s bootstrap() function calls migrate() before accepting any requests. You do not need to run migrations manually when deploying or restarting the server.

Manual migration

To push schema changes to the database outside of a server restart, run one of the following from the monorepo root:
npm run db:migrate
Or target the server workspace directly:
npm run db:migrate -w @babel-plus/server
Under the hood this runs drizzle-kit push, which compares the current schema against the live database and applies any outstanding changes directly. It does not write migration files to disk.
drizzle-kit push is a direct schema-push command suited for development and small deployments. It does not produce versioned migration files the way drizzle-kit generate + drizzle-kit migrate would. The out: './drizzle' directory configured in drizzle.config.ts is only used if you run drizzle-kit generate separately. If you need auditable, reversible migrations in a team environment, consider switching to the generate/migrate workflow.

Seeding

The seed script at packages/server/src/db/seed.ts is triggered automatically on first launch when the nodes table is empty. It creates a curated knowledge graph covering Western philosophy, anti-colonial theory, and political cinema.

What gets seeded

20 nodes across the following types:
TypeExamples
libroLa Ideología Alemana, Fenomenología del Espíritu, Estatismo y Anarquía, La Sociedad del Espectáculo, Los condenados de la tierra
peliculaLa Batalla de Argel
conceptoColonialismo, Revolución, Estado, Materialismo histórico
autorKarl Marx, Mijaíl Bakunin, Guy Debord, Frantz Fanon
filosofoG. W. F. Hegel
directorGillo Pontecorvo
escuelaMarxismo, Anarquismo, Idealismo alemán
eventoGuerra de Independencia Argelina
19 directed relations linking those nodes with typed, weighted edges — for example:
  • Karl Marxes_autor_deLa Ideología Alemana
  • G. W. F. Hegelinfluyo_aKarl Marx
  • La Batalla de Argelocurre_enGuerra de Independencia Argelina
  • Mijaíl Bakunincritica_aKarl Marx
1 default list — “Todos los nodos” — is created and all seeded nodes are added to it in chronological order.

Manual re-seed

To run the seed script at any time (e.g., after resetting the database):
npm run db:seed
Or target the workspace directly:
npm run db:seed -w @babel-plus/server
Running db:seed on a non-empty database will fail with a unique-constraint violation because the seed attempts to insert rows with freshly generated UUIDs. Reset the relevant tables first, or only run the seed against an empty database.

npm scripts reference

All scripts below are available from the monorepo root (package.json):
ScriptCommandDescription
db:migratenpm run db:migrateApply pending schema migrations via drizzle-kit push
db:seednpm run db:seedPopulate the database with the example knowledge graph
buildnpm run buildBuild all packages (server TypeScript + client Vite bundle)
startnpm startStart the production server (serves API + compiled SPA)
devnpm run devStart server and Vite dev server concurrently for local development

Connection

Set the DATABASE_URL environment variable to a standard PostgreSQL connection string:
DATABASE_URL=postgresql://user:password@hostname:5432/database_name
Drizzle ORM uses the pg npm package as its underlying driver. SSL is negotiated automatically by pg when the server requires it, which is the default on hosted PostgreSQL providers.
Both Render PostgreSQL and Neon offer free-tier PostgreSQL databases that work out of the box with a standard connection string. On Render, you can attach a database directly to your web service and the DATABASE_URL environment variable is injected automatically.

Build docs developers (and LLMs) love