Skip to content

[Startup MVP recipes #2]  Nest.js + TypeORM config module and environment variables

Intro

We usually have different environments: local, dev, prod etc. (or test, staging..) Each of the environment has its own separate database, and connect to different services with different credentials.

Environment variables are used to hide confidential information and to provide a layer of customization on different environments like dev and production.

Let’s take a look at how to use env vars with Nest.js + TypeORM together.

Nest.js comes with a config system:

The official complete doc https://docs.nestjs.com/techniques/configuration is recommended but in this tutorial we will just provide essentials of our project for now.

TypeORM also comes with its own config system and supports env vars(https://orkhan.gitbook.io/typeorm/docs/using-ormconfig), but I would recommend to have a single source of truth and config the settings in one place.

(Credits to this answer on StackOverflow https://stackoverflow.com/questions/59913475/configure-typeorm-with-one-configuration-for-cli-and-nestjs-application)

Env vars

Create .env.local for local development, also please consider add this file to .gitignore

# .env.local

DB_HOST=localhost
DB_PORT=5432
DB_USER=sample_nestjs_user
DB_PASSWORD=sample_nestjs_password
DB_DATABASE=sample_nestjs_db

Optionally you can configure similarly for dev or prod environments

like for dev

# .env.dev

DB_HOST=dev.some-dbhost.com
DB_PORT=5432
DB_USER=dev_db_user
DB_PASSWORD=dev_db_password
DB_DATABASE=dev_db

For production please declare the value of env vars directly in e.g. console panel of AWS or similar

Nest.js Config Module

Install Nest.js Config Module

npm i --save @nestjs/config

Import Config Module

// app.module.ts

ConfigModule.forRoot({
  isGlobal: true,
  envFilePath: [`.env.${process.env.NODE_ENV}`],
}),

Then, we can specify the NODE_ENV=local to start using .env.local

// package.json
{
	"scripts": {
		"start:local": "NODE_ENV=local nest start --watch"
	}
}

DB config to be used in both Nest.js and TypeORM (& CLI)

Create the config, in namespace database (or whatever name you want)

// src/config/db.config.ts

import { registerAs } from '@nestjs/config';
import { SnakeNamingStrategy } from 'typeorm-naming-strategies';

export default registerAs('database', () => ({
  type: 'postgres',
  host: process.env.DB_HOST,
  port: process.env.DB_PORT,
  database: process.env.DB_DATABASE,
  username: process.env.DB_USER,
  password: process.env.DB_PASSWORD,
  logging: true,
  logger: 'file',
  entities: ['./dist/**/*entity.{ts,js}'],
  migrationsTableName: 'migrations',
  migrations: ['dist/**/migrations/*.{ts,js}'],
  synchronize: process.env.NODE_ENV === 'local',
  cli: {
    migrationsDir: `src/infrastructure/database/migrations`,
  },
  namingStrategy: new SnakeNamingStrategy(),
}));

That’s how we use env vars instead of hardcoding the credentials or configs.

In this tutorial we also added more configs on TypeORM. We will explain how synchorinze works and introduce the concept of DB migrations in later chapters. For naming strategy we will keep it consistent with Postgres.

We will import the DB config both into Nest.js and TypeORM.

In Nest.js ‘s Config Module load the config and in TypeORM module:

// app.module.ts

import dbConfig from 'src/config/db.config';

ConfigModule.forRoot({
  isGlobal: true,
	load: [dbConfig],
  envFilePath: [`.env.${process.env.NODE_ENV}`],
}),

TypeOrmModule.forRootAsync({
  imports: [ConfigModule],
  useFactory: async (configService: ConfigService) => ({
    ...(await configService.get('database')),
  }),
  inject: [ConfigService],
}),

In this way it gets database config injected.

TypeORM CLI

To use the same config with env vars in TypeORM CLI (e.g. to run DB migrations):

Create ormconfig.ts in root folder

// ormconfig.ts

import { ConfigModule } from '@nestjs/config';
import dbConfig from 'src/config/db.config';

ConfigModule.forRoot({
  isGlobal: true,
  load: [dbConfig],
  envFilePath: [`.env.${process.env.NODE_ENV}`],
});

export default dbConfig();

In this way it uses the Config Module from Nest.js again, executes in env context and provides the result config to TypeORM’s own config.

To add NODE_ENV to TypeORM CLI commands

// package.json

{
	"scripts": {
		"typeorm": "ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
        "typeorm:local": "NODE_ENV=local ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
        "typeorm:dev": "NODE_ENV=dev ts-node -r tsconfig-paths/register ./node_modules/typeorm/cli.js",
        "typeorm:migration:generate": "npm run typeorm -- migration:generate -n",
        "typeorm:migration:generate:dev": "npm run typeorm:dev -- migration:generate -n",
        "typeorm:migration:run": "npm run typeorm -- migration:run",
        "typeorm:migration:run:dev": "npm run typeorm:dev -- migration:run"
	}
}

Leave a Reply

Your email address will not be published.