Connecting a Serverless PostgreSQL Database (Neon) to NestJS Using the Config Service
Tags: nestjs, postgressql, typeorm, neonpostgressql
Published: 2024-07-13 13:43:27

### 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](https://docs.nestjs.com/first-steps))

2. An account on Neon PostgreSQL -> You can sign up for a Neon PostgreSQL account ([Link](https://neon.tech/))

3. For setting up a new project in Neon PostgreSQL, you can follow their official guide ([official link](https://neon.tech/docs/get-started-with-neon/signing-up#step-2-onboarding-in-the-neon-console)). 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. 

```json
"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",
  }
```

### 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

```env
# ====== 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)
```

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](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2t879rn0q6l4u57gnk3w.jpg)

### (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.

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

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

#### 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 

```typescript
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
}))
```

#### index.ts

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

```typescript
export { default as AppConfig } from './app.config';
export { default as DatabaseConfig } from './database.config';
```

### (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

```typescript
// ====== 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
})
```

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.

```typescript
// ======== 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 { }
```

### (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.

```typescript
// ====== 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();
```

### Conclusion

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

```bash
yarn start:dev OR yarn start
```

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

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/yi39qf9fivl23ywlfh0f.jpg)

### Additional Resources

- [Neon Connection Errors](https://neon.tech/docs/connect/connection-errors)
- [Connect to Other Stack](https://neon.tech/docs/get-started-with-neon/connect-neon)
- [NestJS + TypeORM Example](https://docs.nestjs.com/techniques/database)