Database with Prisma ORM, Docker and Postgres - NestJs with Passport #02
Tags: docker, nestjs, tutorial, prisma
Published: 2022-01-21 22:34:06

In the last post start with a blank configuration, understand how to NestJs works with routes, controllers, and services. Saw how easy is to set up Fastify to optimize our app.

Now, will set up the database and ORM for interacting and storing our data. We use PostgreSQL for the database using docker to create a default container for the app, will use Prisma for ORM because is the best Orm of the moment for interacting with the database.

---

## Docker Container

Now that we have our app up, let's containerize it.

Start by creating the following files in the project's root directory:

- `Dockerfile` - This file will be responsible for importing the Docker images, dividing them into development and production environments, copying all of our files, and installing dependencies.

- `docker-compose.yml` - This file will be responsible for defining our containers, required images for the app other services, storage volumes, environment variables, etc.

Open the `Dockerfile` and add

```yml
# Dockerfile

FROM node:alpine As development

WORKDIR /usr/src/app

COPY package*.json ./

RUN yarn add glob rimraf

RUN yarn --only=development

COPY . .

RUN yarn build

FROM node:alpine as production

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./

RUN yarn add glob rimraf

RUN yarn --only=production

COPY . .

COPY --from=development /usr/src/app/dist ./dist

CMD ["node", "dist/main"]
```

Open the `docker-compose.yml` file and add the following code

```yml
# docker-compose.yml

version: "3.7"

services:
  main:
    container_name: main
    build:
      context: .
      target: development
    volumes:
      - .:/usr/src/app
      - /usr/src/app/node_modules
    ports:
      - 3000:3000
    command: yarn start:dev
    env_file:
      - .env
    networks:
      - api
    depends_on:
      - postgres
  postgres:
    image: postgres:13
    container_name: postgres
    networks:
      - api
    env_file:
      - .env
    ports:
      - 5432:5432
    volumes:
      - pgdata:/var/lib/postgresql/data
networks:
  api:
volumes:
  pgdata:
```

Create a `.env` file and add the PostgreSQL credentials

```dotenv
# .env

# PostgreSQL
POSTGRES_USER=nestAuth
POSTGRES_PASSWORD=nestAuth
POSTGRES_DB=nestAuth
```

By default, Fastify listens only on the `localhost 127.0.0.1` interface. For we access our app from other hosts we need to add `0.0.0.0` in the `main.ts`

```ts
// src/main.ts

await app.listen(3000, "0.0.0.0");
```

Awesome, we have our dockerized, and let's go test then. Run in the terminal for development

```
docker-compose up
```

![Docker running in terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9bsmxo0gopv85cub02vd.png)

And our app is Running :clap:

---

## Prisma ORM

[Prisma](https://prisma.io) is an open-source ORM, it is used as an alternative to writing plain SQL, or using another database access tool such as SQL query builders (like knex.js) or ORMs (like TypeORM and Sequelize).

Start installing Prisma CLI as a development

```
yarn add Prisma -D
```

As a best practice invoke the CLI locally by prefixing it with `npx`, to create your initial Prisma setup using the `init` command

```
npx prisma init
```

This command creates a new Prisma directory with the following contents

- `schema.prisma`: Specifies your database connection and contains the database schema
- `.env`: A dotenv file, typically used to store your database credentials in a group of environment variables

By default your database connection it's set to `postgresql`

```prisma
// prisma/schema.prisma

generator client {
  provider = "prisma-client-js"
}

datasource db {
  provider = "postgresql"
  url      = env("DATABASE_URL")
}
```

How our connection type is correct, we will set the `DATABASE_URL` in `.env`

```dotenv
DATABASE_URL="postgresql://nestAuth:nestAuth@postgres:5432/nestAuth"
```

Remember to add in `.env` in `.gitignore` and create a `.env.example` before creating the repository in Github

Generating the Prisma Client requires the `schema.prisma` file. `COPY prisma ./prisma/` copies the whole Prisma directory in case you also need the migrations.

```yml
# Dockerfile

FROM node:alpine As development

WORKDIR /usr/src/app

COPY package*.json ./

# Here Prisma folder to the container
COPY prisma ./prisma/

RUN yarn add glob rimraf

RUN yarn --only=development

COPY . .

RUN yarn build

FROM node:alpine as production

ARG NODE_ENV=production
ENV NODE_ENV=${NODE_ENV}

WORKDIR /usr/src/app

COPY package*.json ./

# Here Prisma folder to the container
COPY prisma ./prisma/

RUN yarn add glob rimraf

RUN yarn --only=production

COPY . .

COPY --from=development /usr/src/app/dist ./dist

CMD ["node", "dist/main"]
```

---

### First Model

Now to test the connection we will be creating a `User` model, inside `schema.prisma` insert

```prisma
// prisma/schema.prisma

model User {
  id        Int            @id @default(autoincrement())
  email     String         @unique
  name      String
  password  String
  createdAt DateTime       @default(now())
  updatedAt DateTime       @updatedAt
}
```

With your model in place, you can generate your SQL migration files and run them against the database. Here I use `migrate dev` for a run in development mode and set `init` name for the migration

Before this you need up your docker

```
docker-compose up
```

and edit your `.env` file

```dotenv
DATABASE_URL="postgresql://nestAuth:nestAuth@localhsot:5432/nestAuth"
```

Always you run `prisma migrate` you need change the database host to  `localhost` after you rollback to name of your database container.

```
npx prisma migrate dev --name init
```

![Run Prisma migration in terminal](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/p9k2rv1me0rrf4sr0ry7.png)

Rollback `.env` file

```dotenv
DATABASE_URL="postgresql://nestAuth:nestAuth@nestauth:5432/nestAuth"
```

Good News, is our configuration is working, now our database is in sync with our app :clap:

---

### Setup Prisma

We will want to abstract away the Prisma Client API for database queries within a service. so let's create a new `PrismaService` that takes care of instantiating and`PrismaClient` to connecting to your database.

Create a `prisma.service.ts` inside `src` folder

```ts
// src/prisma.service.ts

import { INestApplication, Injectable, OnModuleInit } from "@nestjs/common";
import { PrismaClient } from "@prisma/client";

@Injectable()
export class PrismaService extends PrismaClient implements OnModuleInit {
  async onModuleInit() {
    await this.$connect();
  }

  async enableShutdownHooks(app: INestApplication) {
    this.$on("beforeExit", async () => {
      await app.close();
    });
  }
}
```

---

## First Service

Now we can write a user service to make database calls. So NestJs CLI has a command `nest g` to generate services, controllers, strategies, and others structures. For now, we run

```
nest g service users
```

Before start create a service we need to generate types of Prisma Model, we can generate with this

```
npx prisma generate
```

Inside the `src` folder the command creates a `users` folder with `users.service.ts` and the file test `users.service.spec.ts`. To test our database connection let's create a two services

- `user`: to get a user using the Prisma interface `Prisma.UserWhereUniqueInput` forget user by unique columns
- `createUser`: to create a new user using data from interface `Prisma.userCreateInput` that get auto the fields when the model needs to create a new register

And inside the `createUser` we need to encrypt the user password, so let's create a provider for this, in the `src` folder create a `providers` folder and create a `password.ts` file

```ts
// src/providers/password.ts

import { Injectable } from "@nestjs/common";
import * as bcrypt from "bcrypt";

const SALT_OR_ROUNDS = 10;

@Injectable()
export class PasswordProvider {
  async hashPassword(password: string): Promise<string> {
    return bcrypt.hashSync(password, SALT_OR_ROUNDS);
  }

  async comparePassword(password: string, hash: string): Promise<boolean> {
    return bcrypt.compareSync(password, hash);
  }
}
```

The class has two methods, `hashPassword` and `comparePassword` to encrypt and compare the password using brcypt. Inside the `UsersService` class we need to add in the constructor the provider `PasswordProvider` for use in the methods.

```ts
// src/users/users.service.ts

import { HttpException, HttpStatus, Injectable } from "@nestjs/common";
import { PrismaService } from "../prisma.service";
import { User, Prisma } from "@prisma/client";
import { PasswordProvider } from "src/providers/password";

@Injectable()
export class UsersService {
  constructor(
    private prisma: PrismaService,
    private passwordProvider: PasswordProvider
  ) {}

  async user(
    userWhereUniqueInput: Prisma.UserWhereUniqueInput
  ): Promise<User | null> {
    const user = await this.prisma.user.findUnique({
      where: userWhereUniqueInput,
    });

    delete user.password;

    return user;
  }

  async createUser(data: Prisma.UserCreateInput): Promise<User> {
    const userExists = await this.prisma.user.findUnique({
      where: { email: data.email },
    });

    if (userExists) {
      throw new HttpException("User already exists", HttpStatus.CONFLICT);
    }

    const passwordHashed = await this.passwordProvider.hashPassword(
      data.password
    );

    const user = await this.prisma.user.create({
      data: {
        ...data,
        password: passwordHashed,
      },
    });

    delete user.password;

    return user;
  }
}
```

With service created let's create a controller for route use that

```
nest g controller users
```

So this command create `users.controller.ts` and our test file inside `src/users`, so let's create two functions in the controller

- `signUpUser`: For run `createUser` service and return a data from them
- `getUserProfile`: Get an id of the user sent by the route and run the `user` service to find them

```ts
// src/users/users.controller.ts

import { Body, Controller, Get, Param, Post } from "@nestjs/common";
import { User } from "@prisma/client";
import { UsersService } from "./users.service";

// Set prefix route for this group. Ex.: for get profile /users/8126321
@Controller("users")
export class UsersController {
  constructor(private readonly usersService: UsersService) {}
  // Create user -> POST /users
  @Post()
  async signupUser(
    @Body() userData: { name: string; email: string; password: string }
  ): Promise<User> {
    return this.usersService.createUser(userData);
  }

  // Get user Profile -> GET /users/:id
  @Get("/:id")
  async profile(@Param("id") id: number): Promise<User> {
    return this.usersService.user({ id: Number(id) });
  }
}
```

Inside the `users.module.ts` file we need to add the providers, exports, and controllers array.

```ts
// src/users/users.module.ts

import { Module } from "@nestjs/common";
import { PrismaService } from "src/prisma.service";
import { PasswordProvider } from "src/providers/password";
import { UsersController } from "./users.controller";
import { UsersService } from "./users.service";

@Module({
  providers: [PasswordProvider, UsersService, PrismaService],
  exports: [UsersService],
  controllers: [UsersController],
})
export class UsersModule {}
```

And pass the `UsersModule` to the `AppModule` for use.

```ts
//src/app.module.ts

import { Module } from "@nestjs/common";
import { AppController } from "./app.controller";
import { PrismaService } from "./prisma.service";
import { UsersModule } from "./users/users.module";
import { UsersService } from "./users/users.service";
import { PasswordProvider } from "./providers/password";

@Module({
  imports: [UsersModule],
  controllers: [AppController],
  providers: [PrismaService, UsersService, PasswordProvider],
})
export class AppModule {}
```

---

## Let's Test

Now lets up our docker container

```
docker-compose up
```


![Docker running](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b6qanohdrojk3rwwr2mb.png)

And that's it! The app is running :clap:

So in Postman let's try using the `createUser` and `getProfile` routes

```curl
curl --location --request POST 'http://0.0.0.0:3000/users' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "test@e3x.com",
    "name": "Gabriel Menezes",
    "password": "123123"
}'
```


![Create user route](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/l1egknq2su4kua7n7tjs.png)

```curl
curl --location --request GET 'http://0.0.0.0:3000/users/37'
```


![Get user route](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/r2vcxvswlk9m2kckkpvq.png)

---

## Until Next Time

That is all we are going to cover in this article, we dockerize the app, set up Prisma, and create two routes. In the next piece in the series, we'll create and define our Auth providers for authenticating in our app.

Thank you for reading!

---

Follow repository to consulting code

{% github mnzsss/nest-auth-explained no-readme %}

---

## References

- [Setting up a NestJS project with Docker for Back-End development](https://dev.to/erezhod/setting-up-a-nestjs-project-with-docker-for-back-end-development-30lg)
- [Dockerizing a NestJS app with Prisma and PostgreSQL](https://notiz.dev/blog/dockerizing-nestjs-with-prisma-and-postgresql)