avatarKagklis Vasileios

Summary

The provided content is a comprehensive guide on building a backend using NestJS, Prisma, and a PostgreSQL database within a Docker container.

Abstract

The article offers a step-by-step tutorial on setting up a backend application using NestJS and Prisma with a PostgreSQL database managed by Docker. It begins with the prerequisites of installing Node.js and NestJS CLI, then proceeds to create a new NestJS project and configure a PostgreSQL database within a Docker container. The guide also covers setting up Prisma as an ORM, creating models, generating and applying migrations, and connecting to the database using a global Prisma service module in NestJS. Additionally, it demonstrates how to create an Auth module with a controller and service for user registration, including handling HTTP requests and interacting with the database. The article concludes by testing the registration endpoint using Postman and emphasizes the importance of securing the application by hashing passwords, which is to be covered in a subsequent part of the tutorial.

Opinions

  • The author advocates for full-stack development by encouraging frontend developers to learn backend skills using NestJS.
  • The author emphasizes best practices such as not hardcoding credentials and using environment variables for database connections.
  • There is a strong recommendation for using Prisma as an ORM due to its ease of use and ability to manage database interactions efficiently.
  • The author highlights the importance of database migrations to maintain the schema and data integrity.
  • The article suggests that developers should be cautious about security, particularly in storing passwords, hinting at a future discussion on password hashing and validation.
  • The author encourages readers to subscribe to their newsletter for updates on the continuation of the tutorial, indicating a commitment to providing further learning resources.

How to Build a Backend with NestJS and Prisma — Part 1

Learn how to create a backend with NestJS, Prisma, and a PostgreSQL database in a Docker container.

Photo by 42 North on Unsplash

Most of my articles are about Angular (or frontend stuff in general), but not this one!

In this article, I will show you how to create a backend with NestJS and Prisma. We will also set up a PostgreSQL database in a Docker container.

Nest is a framework for building efficient, scalable Node.js server-side applications.

Of course, you can always mock backend responses. But see this as an opportunity to evolve from a Frontend to a Full Stack developer.

This will be a 10-step easy-to-follow guide — I promise.

Let’s get started!

Step 1: Download & install Node.js

I guess that you already have it installed. If you don’t, download and install it from here.

Step 2: Install NestJS

Open a terminal and run:

npm i -g @nestjs/cli

This will install the NestJS CLI globally.

Step 3: Create a NestJS backend

To create an app named nestjs-backend-demo, run:

nest new nestjs-backend-demo

You will be asked which package manager you want to use. Pick the one you’re familiar with — I’ll use npm.

After the installation is complete, open the project in your IDE.

For VS Code users, you can open the project by running:

cd nestjs-backend-demo
code .

This is what the project contains:

As you can see, the project is initialized with Prettier, ESLint, and Jest configured.

If you’ve ever used Angular, you should already be familiar with modules and services.

Lastly, controllers are responsible for handling incoming HTTP requests and providing responses.

Step 4: Setup a PostgreSQL database in Docker

Firstly, you need to have Docker installed and running.

Next, create a file named docker-compose.yml and copy-paste the following:

version: '2.23'
services:
  dev-db:
    image: postgres:16
    ports:
      - 5434:5432
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: <type-your-password-here>
      POSTGRES_DB: nestdemodb
    networks:
      - demoapp
networks:
  demoapp:

You can adjust the version to match your installed version. Also, don’t forget to provide a password!

Disclaimer: Don’t hardcode your authentication credentials into your code. This is simply a demo, but for production applications, you should never commit secrets, tokens, or passwords to your repositories. Find more information about this topic here and here.

To create a docker container with a PostgreSQL database, run:

docker compose up dev-db -d

The -d at the end stands for “run in the background”.

You can confirm the creation of the container by running:

docker ps

You can also check the status of the database by using the CONTAINER ID like this:

docker logs d7d4ccde1d73

If it says that the “database system is ready to accept connections”, you’re good to go!

Step 5: Setup Prisma (ORM)

You got your database up and running.

Now, you need a way to connect to it. For that, we will use Prisma. So, let’s set it up!

Prisma is an open-source ORM that makes it easy to manage and interact with a database.

To install and initialize Prisma, run:

npm install -D prisma
npm install @prisma/client
npx prisma init

This will create two new files: schema.prisma and .env.

The schema.prisma file is where we will declare our models — this is what it looks like at first:

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

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

The .env file is where we will declare environment variables to make them automatically available to Prisma.

Initially, there is only one such variable, the DATABASE_URL for connecting to the database.

Let’s modify this URL based on the information in the docker-compose.yml file we saw earlier.

The format is:

postgresql://<POSTGRES_USER>:<POSTGRES_PASSWORD>@localhost:<PORT>/<POSTGRES_DB>?schema=public

For our example, the URL becomes:

DATABASE_URL="postgresql://postgres:<type-your-password>@localhost:5434/nestdemodb?schema=public"

Double-check and make sure the port is correct!

Step 6: Create a model

Let’s create our first model. This model will represent the entity for the Users in the app. In the schema.prisma, file append the following lines:

model User {
  id        String   @id @default(uuid())
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt

  email String @unique
  hash  String

  firstName String
  lastName String

  @@map("users")
}

The Prisma model naming convention is to use the singular of the name.

On the other hand, for database table names the convention is to use the plural. To respect both conventions, use the @@map attribute as shown in the previous snippet.

Step 7: Generate and apply a migration

Each time you modify something in the schema.prisma file, you need to generate and apply a migration for the changes to take place in your database.

To do this, run:

npx prisma migrate dev

Disclaimer: When using the dev option, if it’s not the first time you run this command, you will be asked to reset all data in your database to proceed.

DON’T use this command in production — for production and testing environments there is another command: npx prisma migrate deploy.

You will also be asked to give a name for the new migration. You should provide a meaningful and descriptive name.

This will create a migrations directory the first time you run it.

Subsequent executions will add new directories and scripts under the migrations directory.

This is like backing up your steps, so you can reach your current schema even if you started from an empty one.

The dev command also runs the generate command behind the scenes:

npx prisma generate

which generates TypeScript types for the schema. We can then import and use those types into our services and controllers.

You can inspect your database by using Prisma Studio.

To open it, run:

npx prisma studio

If a browser tab doesn’t pop automatically, visit localhost:5555.

On the landing page, you can select which model you want to inspect.

For example, for the User model, the table should look like this:

It should have all the declared fields but no data as we haven’t registered any users yet.

Step 8: Connect to the database

The last step regarding the database part is to connect to it.

Create a new module:

nest g module prisma

and a new service:

nest g service prisma --no-spec

The second command will automatically add the new service to the providers array of the PrismaModule. Since other modules will use it, add it to the exports array as well.

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

Additionally, we need to add the @Global() decorator to make the module global-scoped.

The PrismaService will contain the logic for connecting to the database.

To that end, it should extend the PrismaClient and call the constructor of the superclass with the following configuration:

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

For this to work, you also need to install @nestjs/config:

npm install @nestjs/config

and import the ConfigModule in the AppModule:

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

@Module({
  imports: [
    // Other imports...
    ConfigModule.forRoot({ isGlobal: true }),
  ],
  controllers: [AppController],
  providers: [AppService],
})
export class AppModule {}

Step 9: Create a Controller

Since the model we use in this demo is for users, let’s create a controller that will handle the user registration (i.e. creating a user).

First, create the auth module, service, and controller:

nest g module auth
nest g service auth --no-spec
nest g controller auth --no-spec

Then, create a register.dto.ts file that contains the data transfer object (DTO) class for the registration:

export class RegisterDTO {
  email: string;
  password: string;
  firstName: string;
  lastName: string;
}

In the AuthService, inject the PrismaService and create the register method:

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

  async register(dto: RegisterDTO) {
    try {
      const user = await this.prisma.user.create({
        data: {
          email: dto.email,
          hash: dto.password,
          firstName: dto.firstName,
          lastName: dto.lastName,
        },
        select: {
          id: true,
          email: true,
          createdAt: true,
        },
      });
      console.log(user);
    } catch (error) {
      if (error instanceof PrismaClientKnownRequestError) {
        if (error.code === 'P2002') {
          throw new ForbiddenException('Email already in use!');
        }
      }
      throw error;
    }
  }
}

The method this.prisma.user.create() creates and returns the new user. The select block contains the fields that will be returned — for example, we don’t want to return the password (hash field).

For now, let’s just print the created user in the console.

The above snippet contains a security issue (more on that later). Can you spot it?

Finally, let’s use the register method in the AuthController:

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

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

This controller will handle requests that start with /auth. Its register method will handle requests to /auth/register.

If you want to append a prefix, like /api, edit your main.ts file:

async function bootstrap() {
  const app = await NestFactory.create(AppModule);
  app.setGlobalPrefix('api'); // <== Adds prefix to all endpoints
  await app.listen(3000);
}

Step 10: Create a User

I saved the best for last — finally some action!

Make sure you have Docker and the PostgreSQL container up and running. Then run:

npm run start

This will start your NestJS backend server and you should see something like this:

Open Postman (or any other alternative you may prefer) to test the endpoint:

You should see the user printed from the console.log into your terminal.

Also, if you check the database with Prisma Studio, it should no longer be empty.

So, I mentioned a security issue earlier, which is visible in the above screenshot.

The password is not hashed and is stored as plain text in the database! 😱

Additionally, what happens if we don’t provide a property, like say the firstName?

We’ve already covered a lot in this one, so there has to be a Part 2!

Subscribe to my newsletter and stay tuned for Part 2, in which we will:

  • add field validations
  • hash the password before storing it in the database
  • use an Angular application to perform the registration

You can find the source code of the demo in this GitHub repository.

Conclusion

In this article, we created a backend with NestJS.

We configured Prisma as the ORM and set up a PostgreSQL database in a Docker container.

We also created and tried our first endpoint for registering users.

That’s all for now.

Thank you for reading!

Nestjs
Nodejs
JavaScript
Web Development
Programming
Recommended from ReadMedium