Fullstack CourseLearn by building
Back to week 2

Topic

Nest ↔ PostgreSQL: TypeORM root/env

Definition

TypeOrmModule.forRoot registers a single application-wide Postgres connection (and connection pool) for the lifetime of the Nest application, configured from environment values rather than hardcoded settings.

In simpler words

One line in AppModule tells Nest which Postgres to talk to, using the same DATABASE_URL from the SQL connection topic — nothing new, just wired into Nest.

This is where the SQL-level concepts (URL, entities-as-tables, no synchronize) become one concrete Nest config object.

After this you can

  • Read the exact forRoot() call in this app’s AppModule
  • Explain where env.DATABASE_URL comes from before it reaches forRoot
  • Say why entities and migrations arrays are both listed

The forRoot() config in this app

Definition

TypeOrmModule.forRoot accepts a connection configuration object (driver type, URL, entity classes, migration behavior) and Nest uses it once to open the pooled connection for the whole app.

In simpler words

Nest ≠ Next — this connection lives in the API process only; the Next.js web app never talks to Postgres directly.

type: 'postgres' selects the driver; url: env.DATABASE_URL supplies everything covered in the previous topic.

entities: [User, Ticket] tells TypeORM which classes describe the schema it should expect.

synchronize: false is non-negotiable here — schema changes only ever come from committed migrations.

AppModule (actual shape)

TypeOrmModule.forRoot({
  type: 'postgres',
  url: env.DATABASE_URL,
  entities: [User, Ticket],
  // Realtime rule: migrations own the schema. Never true in shared/staging/prod.
  synchronize: false,
  migrations: ['dist/database/migrations/*.js'],
  migrationsRun: false,
}),

migrationsRun: false means migrations are run explicitly (pnpm db:migration:run), not automatically on boot.

DATABASE_URL travels through packages/env

Definition

A shared, validated environment schema parses process.env once and produces a typed object, so every consumer (Nest app, TypeORM CLI) reads the same guaranteed-valid values.

In simpler words

AppModule never reads process.env.DATABASE_URL directly — it reads env.DATABASE_URL after loadApiEnv() has validated it.

apiEnvSchema in packages/env requires DATABASE_URL as a non-empty string — a missing or empty value fails fast at boot instead of failing confusingly later.

The TypeORM CLI (data-source.ts) reads the same variable independently via dotenv, because CLI commands run outside the Nest bootstrap.

Two readers, one variable

// app.module.ts (Nest runtime)
const env = loadApiEnv(); // validated
url: env.DATABASE_URL

// database/data-source.ts (TypeORM CLI)
loadDotenv({ path: resolve(__dirname, '../../.env') });
const url = process.env.DATABASE_URL;
if (!url) throw new Error('DATABASE_URL is required for TypeORM CLI');

Same .env, two entry points — the CLI cannot boot Nest just to run a migration.

Why both entities and migrations are configured

Definition

The entities array tells the running application which TypeScript classes map to tables for querying, while the migrations array (with migrationsRun) controls whether TypeORM applies pending schema changes automatically at startup.

In simpler words

They answer two different questions: “what shape do I expect the data in?” versus “should I change the schema right now?”

This repo answers the second question with migrationsRun: false — migrations run as a deliberate CLI step, never as a side effect of starting the API.

The CLI’s data-source.ts points migrations at TypeScript source (for generate/run during development); AppModule points at compiled dist/*.js (for what actually executes at runtime).

CLI vs runtime migration paths

// data-source.ts (CLI, source files)
migrations: [resolve(__dirname, './migrations/*.{ts,js}')]

// app.module.ts (runtime, compiled output)
migrations: ['dist/database/migrations/*.js']

The CLI works against source during development; the deployed app only ever sees compiled JS.

Keep in mind

  • Trace every config value back to its source before debugging further — most “TypeORM” bugs are actually env bugs.
  • synchronize: false + migrationsRun: false means: nothing about the schema changes without someone running a command on purpose.
  • Nest ≠ Next: only the API process holds this connection.

Test

Check your understanding

At least 10 questions — mix of concept, syntax, practical, and logic. Score ≥ 80% (enforced by the API) to save progress.

Checking your session…

10 questions · concept 3 · syntax 2 · practical 3 · logic 2

Concept1. Which statement best defines Nest ↔ PostgreSQL TypeORM root/env?
Syntax2. Which code-level choice matches Nest ↔ PostgreSQL TypeORM root/env?
Practical3. A reviewer spots a bug related to Nest ↔ PostgreSQL TypeORM root/env. What is the right fix?
Logic4. Which reasoning correctly explains Nest ↔ PostgreSQL TypeORM root/env?
Concept5. Which boundary does correct use of Nest ↔ PostgreSQL TypeORM root/env preserve?
Practical6. What is the safest next step when applying Nest ↔ PostgreSQL TypeORM root/env?
Syntax7. Which implementation direction matches the rule for Nest ↔ PostgreSQL TypeORM root/env?
Logic8. Which consequence follows from applying Nest ↔ PostgreSQL TypeORM root/env correctly?
Concept9. Which claim about Nest ↔ PostgreSQL TypeORM root/env is true in this Nest + Postgres monorepo?
Practical10. Which team practice best demonstrates Nest ↔ PostgreSQL TypeORM root/env?