DEV Community

Alessandro Pecorilla
Alessandro Pecorilla

Posted on

Connecting a Serverless PostgreSQL Database (Neon) to NestJS Using the Config Service

Introduction

The primary goal of this project is to illustrate the process of connecting a Neon database to a NestJS application through the Config Service, which helps manage configuration settings in a clean and organized manner. Neon PostgreSQL is designed to deliver a seamless database experience without the hassle of managing infrastructure.

In addition, I will demonstrate (in another guide) how to implement create and manage database entities with TypeORM, a popular Object-Relational Mapping (ORM) tool, to define and manage database entities.

Prerequisites

  1. A NestJS and TypeScript project already initialized -> If you don't have a NestJS project set up yet, you can follow the official NestJS guide to initialize a new project with TypeScript. (Link)

  2. An account on Neon PostgreSQL -> You can sign up for a Neon PostgreSQL account (Link)

  3. For setting up a new project in Neon PostgreSQL, you can follow their official guide (official link). As of now, Neon PostgreSQL offers advantageous free solutions when setting up a new project

Packages

In addition to the packages provided by Nest on the initial setup, the following dependencies will help us to connect to Neon database.

"dependencies": {
    "@nestjs/common": "^10.0.0",
    "@nestjs/config": "^3.2.2",
    "@nestjs/core": "^10.0.0",
    "@nestjs/platform-express": "^10.3.10",
    "@nestjs/typeorm": "^10.0.2",
    "class-transformer": "^0.5.1",
    "class-validator": "^0.14.1",
    "dotenv": "^16.4.5",
    "express": "^4.19.2",
    "pg": "^8.12.0",
    "typeorm": "^0.3.20",
  }
Enter fullscreen mode Exit fullscreen mode

 Configuring Environment Variables

Create a .env file in the root directory of your NestJS project. This file will contain all the necessary environment variables for your application. Below there is an example of a ".env" file for our project

# ====== NEON POSTGRES ====== #
DATABASE_HOST='NEON PGHOST'
DATABASE_USER='NEON PGUSER'
DATABASE_PASSWORD='NEON PGPASSWORD'
DATABASE_PORT= 5432 #(usually it's 5432)
DATABASE_ENDPOINT_ID='NEON ENDPOINT_ID'
DATABASE_NAME='NEON PGDATABASE'
# ====== PORTS ====== #
NEST_PORT= YOUR_NEST_PORT #(usually it's 3000)
Enter fullscreen mode Exit fullscreen mode

Given that the prerequisite N°3 has been met and you have successfully created a project on Neon PostgreSQL, you will find the necessary environment variables directly from the Neon dashboard. Here is an image illustrating the relevant variables you will need to configure

Image description

 (1) Configuring the Nest.JS Project -> config/ folder

We'll start by organizing our configuration files in the config folder. Here, we'll create two essential files: app.config.ts and database.config.ts

app.config.ts

This setup register the application configuration settings under the name 'config' with @nestjs/config. Here we will define the port and node environment.

import { registerAs } from '@nestjs/config';

export default registerAs('config', () => ({
    port: parseInt(process.env.NEST_PORT, 10) || 5575,
    nodenv: process.env.NODE_ENV,
}));
Enter fullscreen mode Exit fullscreen mode

database.config.ts

This setup configures the connection options for Neon PostgreSQL database connections using the environment variables defined earlier. It utilizes @nestjs/config to manage environment variables

import { registerAs } from '@nestjs/config';

export default registerAs('database', () => ({
    type: 'postgres', // Neon PostgreSQL database type
    host: process.env.DATABASE_HOST || 'localhost', 
    database: process.env.DATABASE_NAME, 
    port: process.env.DATABASE_PORT, 
    username: process.env.DATABASE_USER, 
    password: process.env.DATABASE_PASSWORD, 
    entities: [`${__dirname}/../**/*.entity{.ts,.js}`], // TypeORM Entities to be stored in the database
    subscribers: [`${__dirname}/../**/*.subscriber{.ts,.js}`], // OPTIONAL
    synchronize: process.env.NODE_ENV === 'development', // Set `true` to synchronize the database schema with the entities
    logging: process.env.NODE_ENV === 'development',
    ssl: true, 
    connection: {
        options: `project=${process.env.DATABASE_ENDPOINT_ID}`,
    },
    migrations: [`${__dirname}/../database/migrations/*{.ts,.js}`], // Migrations 
    migrationsTableName: 'typeorm-migrations', // Set the name of the migrations table
}))
Enter fullscreen mode Exit fullscreen mode

index.ts

This index.ts file simplifies exporting configurations by re-exporting AppConfig and DatabaseConfig from their respective files

export { default as AppConfig } from './app.config';
export { default as DatabaseConfig } from './database.config';
Enter fullscreen mode Exit fullscreen mode

(2) Configuring the Nest.JS Project -> database/ folder

Inside this folder we configure a TypeORM DataSource for Neon database using settings from environment variables.

typeorm.config.ts

// ====== IMPORTS =========
import { DataSource } from 'typeorm';
import { ConfigService } from '@nestjs/config';
import { config } from 'dotenv';
config();

// 1. Define a configuration for TypeORM
const configService = new ConfigService();

export default new DataSource({
    type: 'postgres', // Neon PostgreSQL database type
    host: configService.get<string>('DATABASE_HOST'), 
    database: configService.get<string>('DATABASE_NAME'), 
    port: configService.get<number>('DATABASE_PORT'), 
    username: configService.get<string>('DATABASE_USER'), 
    password: configService.get<string>('DATABASE_PASSWORD'), 
    entities: [`${__dirname}/../src/**/*.entity{.ts,.js}`], 
    subscribers: [`${__dirname}/../**/*.subscriber{.ts,.js}`] // OPTIONAL,
    synchronize: process.env.NODE_ENV === 'development', 
    logging: process.env.NODE_ENV === 'development', 
    ssl: true, 
    migrations: [`${__dirname}/../database/migrations/*{.ts,.js}`], 
    migrationsTableName: 'typeorm-migrations', // Set the same name as you did on database.config
})
Enter fullscreen mode Exit fullscreen mode

From now on, this file will manage the entities you create in your project. Additionally, if you need to make modifications to the database schema, the setup includes support for using migrations.

(3) Configuring the Nest.JS Project -> app.module.ts

The app.module.ts orchestrates the main structure and dependencies of the NestJS application. It imports the necessary modules to configures TypeORM for database access using environment variables.

// ======== MODULES =========
import { Module, forwardRef } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';
// ======== CONTROLLERS =========
import { AppController } from './app.controller';
// ======== SERVICES =========
import { AppService } from './app.service';
// ======== CONFIG =========
import { AppConfig, DatabaseConfig } from 'config/index';

@Module({
  imports: [
    // Load environment variables
    ConfigModule.forRoot({
      isGlobal: true, // Set `true` for global configuration
      cache: true,
      load: [AppConfig, DatabaseConfig],
    }),
    // TypeORM configuration
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule], // Import the ConfigModule 
      useFactory: (configService: ConfigService) => ({
        ...configService.get('database'),
      }),
      inject: [ConfigService], // Inject the ConfigService
    }),
    // Other Project Modules
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule { }
Enter fullscreen mode Exit fullscreen mode

(4) Configuring the Nest.JS Project -> main.ts

This main.ts file initializes the NestJS application, sets up middleware, establishes a database connection using configuration from ConfigService, and starts the server to handle incoming requests.

// ====== IMPORTS ======
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import * as dotenv from 'dotenv';
import { static as expose } from 'express';
import { ConfigService } from '@nestjs/config';

dotenv.config();

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  // Configuration Service
  const configService = app.get(ConfigService);

  const port = configService.get('NEST_PORT');

  await app
    .listen(port || 3375)
    .then(() => {
      console.log('Current Database Name: ', configService.get('DATABASE_NAME'));
      console.log(`Application is running on port`, configService.get('NEST_PORT'));
    })
    .catch((err) => {
      console.error('Error while starting application: ', err.message);
    });
}
bootstrap();
Enter fullscreen mode Exit fullscreen mode

Conclusion

The configuration steps are now complete. To start the application and connect to the configured database, simply run the following command:

yarn start:dev OR yarn start
Enter fullscreen mode Exit fullscreen mode

You should see the database connection details in the terminal, indicating that the connection has been successfully established like the picture below

Image description

Additional Resources

Top comments (0)

Some comments may only be visible to logged-in visitors. Sign in to view all comments. Some comments have been hidden by the post's author - find out more