Initiating a NestJs app with PostgreSQL using Docker
Tags: typescript, docker, database, api
Published: 2023-01-24 23:57:21

## Introduction

I sometimes find myself trying to remember exactly how to initiate a project after a very long time away. At this point, I've probably spent the last couple months working on a project off an entirely different stack, so much that I haven't initiated a new project in a while. 

I decided to create this go-to article for initiating a new project using nestjs with typescript. I also thought it'll be a handy tool for other developers to leverage when getting their backend projects started. I'll simply give a step by step process to get a project started, create it's basic structure, connect to a database using docker and implement a authentication service.


## Creating a NestJs App

First, I'll be going through creating a nestjs application, connecting to a `postgresql` database that will be spun up using `docker`, creating a User model and configuring an `ORM` - in this case `prisma` - to connect with our database. I'll also try to implement a simple authentication system to add some functionality to the app.

To get started with `nestJs` "a framework for building efficient, scalable Node.js server-side applications", I'll go to my terminal and install `nest cli` globally using the command below. This provides a way to create a new nest project which bundles with it the core files required to get the project started.

```sh
% npm i -g @nestjs/cli 
```

I'll start the new project with this command -`nest new <project-name>`- which creates a directory where all our files will reside. It also creates a basic structure for the whole application.

```sh
% nest new nest-init
... # select a preferred package manager
% code nest-init # or open in your favorite IDE
```

Now, the working directory looks something like

```
|-- node_modules/
|-- src
|    |-- main.ts
|    |-- app.module.ts
|-- test/
|-- package.json
|-- README.md
|-- tsconfig.json

```

NOTE: The `src` directory initially contained more files but I removed them because I won't be needing them. 

## The Database
### Starting the database

The next thing to do is to set up the database for the app to connect to. I'll do this using `docker-compose` which is a tool for "running multi-container applications on docker". I start by creating a `docker-compose.yml` file which will contain the code for running a postgreSQL instance. The file should contain something similar to the code below

```yaml
version: '3.8'
services:
  postgres:
    image: postgres:13-alpine
    ports:
      - 5432:5432
    env_file:
      - .env
    volumes:
      - postgres:/var/lib/postgresql/data
    networks:
      - nest-init

volumes:
   postgres:
      name: nest-init-docker-db

networks:
   nest-init:
```

To make use of this, a `.env` file is required which contains the credentials to be used when spinning up the postgreSQL instance. It should contain the following constants

```
POSTGRES_DB=<db_name>
POSTGRES_USER=<username>
POSTGRES_PASSWORD=<password>
```

I then proceed to run the command below which spins up the database with docker; the `-d` flag allows this to run in detached mode which in-turn makes the terminal session still available for use.

```sh
% docker-compose up -d
```


### Connecting to the database

Now that the database is running, I'll proceed to install and initiate `prisma` which creates a prisma directory and schema file. It also creates a `.env` file or in this case, adds a required line; `DATABASE_URL` to the file.

The database url in this case will look something like `postgresql://dami:root@localhost:5432/nest-init?schema=public`.

To install prisma
```sh
% npm install prisma --save-dev
...
% npx prisma init
```

Now I have a `schema.prisma` file that contains the database connection variable. It will also contain all the  yet to be created models that will be used in the application. It also defines the `client` to be used which in this case is `prisma client`. To make use of it though, it needs to be installed by running the command below 

```sh
% npm install @prisma/client

```

I also need to install `nest config` to easily access our configuration variables in the `.env` file.

```sh
% npm i --save @nestjs/config
```

The `ConfigModule` needs to be added to the `app.module.ts` file to be able to make use of the config service with the aid of `dependency injection`. Now my file looks like this

```ts
import { Module } from '@nestjs/common';
import { PrismaModule } from './prisma/prisma.module';
import { ConfigModule } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({
      isGlobal: true
    }),
    PrismaModule
  ],
})
export class AppModule {}
```

I'll also create a simple User model to be used for the authentication system.

```prisma
model User {
  id         Int      @id @default(autoincrement())
  email      String   @unique
  name       String
  password   String

  @@map("users")
}
```

After creating the model, a migration needs to be made so the model will be mapped to the database. This is done by running a simple prisma command

```sh
% npx prisma migrate dev
```

This creates a SQL migrations file and runs the migrations on the database. 


## Creating required services

Now, I can start creating the required services. `Nest CLI` provides handy tools for creation of [modules](https://docs.nestjs.com/modules), [services](https://docs.nestjs.com/providers) and [controllers](https://docs.nestjs.com/controllers). It also automatically bundles related files in a directory and adds the individual service module to the `app.module.ts` file. At the end or a project, the `src` folder should look something like;

```
src
 |-- auth
 |     |-- auth.controller.ts
 |     |-- auth.module.ts
 |     |-- auth.service.ts
 |-- prisma
 |     |-- prisma.module.ts
 |     |-- prisma.service.ts
 |-- app.module.ts
 |-- main.ts
```


### Prisma Service

I'll start by creating the prisma service while would be used to access the database from any part of the application.I'll generate to files, the `prisma.module.ts` and `prisma.service.ts` files using the command below.

```sh
% nest g module prisma
% nest g service prisma --no-spec
```

The `--no-spec` flag ensures that prisma doesn't generate test files alongside the required files. Next, I'll add the following code to the `prisma.service.ts` file

```ts
import { Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClient } from '@prisma/client';

@Injectable()
export class PrismaService extends PrismaClient{
    constructor(config: ConfigService) {
        super({
            datasources: {
                db: {
                    url: config.get('DATABASE_URL')
                }
            }
        })
    }

}

```

and the `prisma.module.ts` file looks like

```ts
import { Global, Module } from '@nestjs/common';
import { PrismaService } from './prisma.service';

@Global()
@Module({
    providers: [PrismaService],
    exports: [PrismaService]
})
export class PrismaModule {}

```

With this, I can also inject the `PrismaService` wherever the application needs to access the database.


### Auth Service

Going forward, I'll be implementing a simple `register` endpoint in the authentication system to to test the apps functionality. To do this, the auth `service`, `module` and `controller` files need to be generated using the commands below


```sh
% nest g module auth
...
% nest g service auth --no-spec
...
% nest g controller auth --no-spec
```


The generated module file already contains imports and bundles the service and controller files. I'll go ahead to add the route to the controller file and the `register` logic in the service file.

I'll create a simple DTO to serve as a schema for the expected payload and use `class-validator` to validate the incoming data. Note that this needs to be installed running

```sh
% npm i class-validator
% npm i class-transformer
```

The validation pipe also needs to be added to the `main.ts` file to allow validation in the whole application.

`main.ts`
```ts
import { ValidationPipe } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.useGlobalPipes(new ValidationPipe({
    whitelist: true
  }))
  await app.listen(3000);
}
bootstrap();

```

Now I'll write the auth controller

`auth.controller.ts`
```ts
import { Body, Controller, Post } from '@nestjs/common';
import { AuthService } from './auth.service';
import { IsEmail, IsNotEmpty, IsString } from "class-validator"

class RegisterDTO {
    @IsNotEmpty()
    @IsEmail()
    email: string

    @IsNotEmpty()
    @IsString()
    name: string

    @IsNotEmpty()
    @IsString()
    password: string

    @IsNotEmpty()
    @IsString()
    repeatPassword: string
}

@Controller('auth')
export class AuthController {
    constructor(private authService: AuthService) {}

    @Post('register')
    register(@Body() dto: RegisterDTO) {
        return this.authService.register(dto)
    }
}

```

And in the `auth.service.ts` file

```ts
import { BadRequestException, ForbiddenException, Injectable } from '@nestjs/common';
import { ConfigService } from '@nestjs/config';
import { PrismaClientKnownRequestError } from '@prisma/client/runtime';
import { PrismaService } from 'src/prisma/prisma.service';
import { RegisterDTO } from './auth.controller';
import * as CryptoJS from 'crypto-js';

@Injectable()
export class AuthService {
    constructor(private prisma: PrismaService, private config: ConfigService) {}

    async register(dto: RegisterDTO) {
        try {
            if (dto.password !== dto.repeatPassword) throw new BadRequestException("Passwords must be equal!")
            const key = CryptoJS.enc.Utf8.parse(this.config.get('SECRET_PASS'));
            const passwordHash = CryptoJS.AES.encrypt(dto.password, key, {iv: key}).toString()
            const user = await this.prisma.user.create({
                data: {
                    name: dto.name,
                    email: dto.email,
                    password: passwordHash
                }
            })
            const {password, ...result} = user
            return {
                statusCode: 201,
                message: "User created successfully.",
                data: result
            }
        } catch (error) {
            console.log(error)
            if (error instanceof PrismaClientKnownRequestError) {
                if (error.code === 'P2002') {
                    throw new ForbiddenException("Email or name has already been used!")
                }
            } throw error;
        }
    }
}

```

Now I can start the server by running `npm run start:dev` and make a request to the register endpoint.

The complete code can be found [here](https://github.com/AgbaD/backend-init/) on Github.
Cheers!