Skip to main content
Sequelize is the database ORM: it loads all models, runs associations, and exposes a typed models object that every part of the backend uses to read and write data.

Why

Sequelize gives you a fully typed ORM over PostgreSQL with first-class migration support and soft-delete semantics built in. It’s the only database client supported by this stack, and every model, migration, and seed is colocated under apps/backend/src/tools/rds/sequelize/ for easy navigation.

Setup

  1. In development.ts, production.ts, and test.ts, set tools.rds:
  2. In compose.yml, the rds service provides PostgreSQL:
  3. In .env.development, set:
  4. Run migrations: make migrate.
  5. Run make test module=backend.

Adding a new model

  1. Create apps/backend/src/tools/rds/sequelize/models/<ModelName>/index.ts (or a flat file for simple models). Extend Model from Sequelize, define attributes, and call ModelName.init(...) with the table name in snake_case plural.
  2. Register associations in the model file’s associate static method, called automatically by the model loader.
  3. Generate a migration: copy the closest existing migration in apps/backend/src/tools/rds/sequelize/migrations/ and name it <timestamp>-<describe-change>.ts.
  4. Export the model from apps/backend/src/tools/rds/sequelize/models/index.ts.
  5. Run make migrate to apply.
  6. Run make test module=backend.

Migrations

Migrations live in apps/backend/src/tools/rds/sequelize/migrations/ and are run in timestamp order. Run them with:
Never edit a migration that has already been applied in production — create a new one instead.

Soft deletes (paranoid models)

Models marked paranoid: true set a deleted_at timestamp on deletion instead of removing the row. Sequelize automatically excludes soft-deleted rows from all queries unless you explicitly pass paranoid: false:

Gotchas

  • Always pass the active transaction to every ORM call inside a transaction block. Omitting it causes dirty reads and partial writes.
  • Never run sequelize.sync() in production — use migrations.
  • The verbose: true config flag logs every SQL query. Useful for debugging, but leave it off in production.
  • POSTGRES_PORT must be a number. The config parses it with Number(process.env.POSTGRES_PORT) — ensure your .env has no quotes around it.

What’s next?