DEV Community

Henrique Weiand
Henrique Weiand

Posted on • Updated on

Authentication part 3 using NestJS and Postgres database neon.tech

Hello fellow coders! Let’s continue our series of NestJS auth flow implementations, and today we are going to add a database integration to keep users saved basically to work with the auth module.

I am going to focus on the database integration and functionality instead of creating the entire CRUD for the user entity etc, OK?

We are going to start using TypeORM as an ORM to help us interact with Postgres, but we also have an example of using Prisma in the future and everything that we have to adapt to switch the ORMs if necessary. At the end we are implementing neon.tech as a production database, right? 😉

Setting up TypeORM

First, as we don’t have this in either the library or the module, let’s use a few commands inside our project’s folder to do it.

nest g module databse
Enter fullscreen mode Exit fullscreen mode

and add the dependencies that we are going to use

npm install @nestjs/typeorm typeorm pg bcrypt
Enter fullscreen mode Exit fullscreen mode

And then

npm install -D reflect-metadata @types/bcrypt
Enter fullscreen mode Exit fullscreen mode

💡 Use any package manager that you like.

Let’s edit the created module database.module.ts

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            type: 'postgres',
            host: 'localhost',
            port: 5432,
            username: 'postgres',
            password: 'root',
            database: 'project',
            entities: [`${__dirname}/../**/*.entity{.ts,.js}`],
            migrations: [`${__dirname}/../migrations/*{.ts,.js}`],
            synchronize: false,
            migrationsRun: true,
            logging: true,
        }),
    ],
})
export class DatabaseModule { }
Enter fullscreen mode Exit fullscreen mode

For those who know a bit about NestJS, I know that we can improve this file, for example, by getting the values of this configuration from an ENV, and with ConfigModule, hold on! We are going to talk about it later, for now, we are focusing on the database and setup only.

There are a few important configurations here to understand.

  • Type: The database type you are going to use.

  • Host, port, username, password, database: The traditional fields.

  • Entities: In this case, it will guide the framework to find the files that we are eventually created to be as our tables and the code representation of them.

  • Migrations: This will be the folder with all migrations that we are going to create to be used as a “recipe” for the developers to set up the same database inside their environments. If you want to understand more about it, check this link out → https://docs.nestjs.com/techniques/database#migrations.

  • Synchronize: I usually keep this option off, because if you keep it on, when your application starts it will try to sync your entity to your database, which can be a problem sometimes and make you lose some data, take care of using it!

  • migrationsRun: It will make the application run the migrations with its starts.

  • logging: This is to log everything that is happening inside the TypeORM. (Don’t run it in production).

Well done fellow coders! Now that we already have the module, we can use it inside of the other places that intend to interact with the database.

Before we continue, let’s open the package.json and add a few commands that will be useful ahead.

"typeorm": "typeorm-ts-node-commonjs",
"migration:generate": "yarn typeorm migration:generate src/migrations/migration -d src/database/data-source.ts"
Enter fullscreen mode Exit fullscreen mode

Install local database with Docker

As you may have noticed, we don’t have any database running, which means, that if you run your project now, it will be like this.

To solve it, we need to run the Postgres, and we are going to do it quickly with Docker. Please access the website and install it before continuing.

Now, we have to create a new file inside the root folder called docker-compose.yaml with this content

version: '3.3'

services:
  postgres:
    container_name: postgres
    image: postgres:16-alpine
    volumes:
      - ./data/postgres:/var/lib/postgresql/data
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: root
      POSTGRES_DB: project
      PGDATA: /data/postgres
      POSTGRES_HOST_AUTH_METHOD: trust
    ports:
      - '5432:5432'
    restart: unless-stopped
Enter fullscreen mode Exit fullscreen mode

Speaking of it quickly, it is a “recipe” of a database Postgress that we are going to run inside Docker, it helps to have a smooth environment that can be easily replicated anywhere. We also have in this file the database credentials and the PORT that is going to be exposed inside the container. Now that we have the “recipe”, we need to run, so inside your terminal run

docker-compose up -d
Enter fullscreen mode Exit fullscreen mode

And then, if you try to run the application again, it will be like this

Creating user entity

Inside our user’s folder, let’s create a file called users.entity.ts, and inside it, this content

import { Column, Entity, PrimaryGeneratedColumn } from 'typeorm';

@Entity({ name: 'users' })
export class Users {
    @PrimaryGeneratedColumn('uuid', { name: 'id' })
    id: string;

    @Column()
    username: string;

    @Column()
    password: string;
}
Enter fullscreen mode Exit fullscreen mode

This file represents basically a table, its columns, and all specifications, and in our context, we are only creating a table with three fields.

Using migrations

As our idea is to use the command to create the migrations from our entities, we will need to create another file with the database credentials and repeat all the data over there.

⚠️ We are going to improve this data repetition later

Inside of database folder, create a new file called data-source.ts

import 'reflect-metadata';
import { DataSource } from 'typeorm';

export const AppDataSource = new DataSource({
    type: 'postgres',
    host: 'localhost',
    port: 5432,
    username: 'postgres',
    password: 'root',
    database: 'project',
    entities: [`${__dirname}/../**/*.entity{.ts,.js}`],
    migrations: [`${__dirname}/../migrations/*{.ts,.js}`],
    synchronize: false,
});
Enter fullscreen mode Exit fullscreen mode

The difference with the other is, that we are exporting a DataSource, and with the other, we just input the data inside a method, unfortunately, that method doesn’t receive a DataSource, which means we are going to change a few things later, but for now, let’s keep two files with the configurations.

To create our first migration after all of that, run the command

yarn migration:generate
Enter fullscreen mode Exit fullscreen mode

It will create folder migrations with the users migration inside, look:

To run and finally create the table you just need to run the project

yarn start:dev
Enter fullscreen mode Exit fullscreen mode

The initialization process will create the table

Adjusting a few things

It is important to keep the user’s credentials unreadable to anyone so now, we will need to change the user.entity in order to be aligned with this rule

import { BeforeInsert, Column, Entity, PrimaryGeneratedColumn } from 'typeorm';
import * as bcrypt from 'bcrypt';

@Entity({ name: 'users' })
export class Users {
    @PrimaryGeneratedColumn('uuid', { name: 'id' })
    id: string;

    @Column()
    username: string;

    @Column()
    password: string;

    @BeforeInsert()
    async hashPassword() {
        this.password = await bcrypt.hash(this.password, 10);
    }
}
Enter fullscreen mode Exit fullscreen mode

This decorator @BeforeInsert will run always before inserting a new user and it will apply a hash to the user’s password.

@BeforeInsert()
async hashPassword() {
    this.password = await bcrypt.hash(this.password, 10);
}
Enter fullscreen mode Exit fullscreen mode

Next, we have to make a few changes to our users.service.ts, these changes are important because they will interact with the input data and the database by using the repository.

import { HttpException, HttpStatus, Injectable } from '@nestjs/common';
import { Users } from './users.entity';
import { Repository } from 'typeorm';
import { InjectRepository } from '@nestjs/typeorm';
import * as bcrypt from 'bcrypt';

@Injectable()
export class UsersService {
    constructor(
        @InjectRepository(Users)
        private readonly userRepository: Repository<Users>,
    ) {}

    private async comparePasswords(
        userPassword: string,
        currentPassword: string,
    ) {
        return await bcrypt.compare(currentPassword, userPassword);
    }

    async findOneByUsername(username: string): Promise<Users | undefined> {
        return this.userRepository.findOne({ where: { username } });
    }

    async validateCredentials({
        username,
        password,
    }: {
        username: string;
        password: string;
    }): Promise<Users> {
        const user = await this.findOneByUsername(username);

        if (!user) {
            throw new HttpException('User not found', HttpStatus.UNAUTHORIZED);
        }

        const areEqual = await this.comparePasswords(user.password, password);

        if (!areEqual) {
            throw new HttpException(
                'Invalid credentials',
                HttpStatus.UNAUTHORIZED,
            );
        }

        return user;
    }

    async create({
        username,
        password,
    }: {
        username: string;
        password: string;
    }): Promise<Users> {
        const userInDb = await this.findOneByUsername(username);
        if (userInDb) {
            throw new HttpException(
                'User already exists',
                HttpStatus.BAD_REQUEST,
            );
        }

        const user: Users = this.userRepository.create({
            username,
            password,
        });

        await this.userRepository.save(user);

        return user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Just after changing this service, we will also need to change the auth.service.ts because this service uses the users.service.ts by dependency injection and then its methods, so let’s do it as well.

import {
    Injectable,
    InternalServerErrorException,
    UnauthorizedException,
} from '@nestjs/common';
import { UsersService } from '../users/users.service';
import { JwtService } from '@nestjs/jwt';

@Injectable()
export class AuthService {
    constructor(
        private usersService: UsersService,
        private jwtService: JwtService,
    ) {}

    async signIn(username: string, pass: string) {
        const user = await this.usersService.validateCredentials({
            username,
            password: pass,
        });
        if (!user) {
            throw new UnauthorizedException();
        }
        const payload = { sub: user.id, username: user.username };
        return {
            access_token: await this.jwtService.signAsync(payload),
        };
    }

    async signUp(username: string, pass: string) {
        const user = await this.usersService.create({
            username,
            password: pass,
        });
        if (!user) {
            throw new InternalServerErrorException();
        }
        delete user.password;
        return user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Finally, let’s edit our Controller, which is our main port to the world, where the users hit the API to interact.

import {
    Body,
    Controller,
    Get,
    HttpCode,
    HttpStatus,
    Post,
    Request,
    UseGuards,
} from '@nestjs/common';
import { AuthGuard } from './auth.guard';
import { AuthService } from './auth.service';

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

    @HttpCode(HttpStatus.OK)
    @Post('signUp')
    signUp(@Body() signInDto: Record<string, any>) {
        return this.authService.signUp(signInDto.username, signInDto.password);
    }

    @HttpCode(HttpStatus.OK)
    @Post('signIn')
    signIn(@Body() signInDto: Record<string, any>) {
        return this.authService.signIn(signInDto.username, signInDto.password);
    }

    @UseGuards(AuthGuard)
    @Get('profile')
    getProfile(@Request() req) {
        return req.user;
    }
}
Enter fullscreen mode Exit fullscreen mode

Well done! We are ready to run the project and test it.

yarn start:dev
Enter fullscreen mode Exit fullscreen mode

Running the solution

First, let’s register a new user with the endpoint auth/signUp, like this

After we can make the sign In like this

Using Neon.tech

As I had promised, let’s use neon.tech as a Postgres provider. You will have to open their website, create your account, and then create a project until you see this screen.

Now, you have to find an option to create a database

Create a database with the name that you want, in my case nestjs-auth-flow-blog-post , and after that go to our file called database.module.ts where we have to put the credentials. Yours probably will be similar to mine

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';

@Module({
    imports: [
        TypeOrmModule.forRoot({
            type: 'postgres',
            host: 'ep-yellow-bush-89528540.us-east-1.aws.neon.tech',
            port: 5432,
            username: 'henriqueweiand',
            password: 'YOUR PASSWORD HERE',
            database: 'nestjs-auth-flow-blog-post',
            entities: [`${__dirname}/../**/*.entity{.ts,.js}`],
            migrations: [`${__dirname}/../migrations/*{.ts,.js}`],
            synchronize: false,
            migrationsRun: true,
            logging: true,
            sslmode: 'require',
            ssl: true,
        }),
    ],
})
export class DatabaseModule {}
Enter fullscreen mode Exit fullscreen mode

If you take a look, I also added these two lines to the original file.

sslmode: 'require',
ssl: true,
Enter fullscreen mode Exit fullscreen mode

Which is necessary!

And then you can run your project again, and magically, you are already using the neon.tech database!!
Implementing auth flow as fast as possible using NestJS
Authentication part 2 using NestJS

Conclusion

This was our last post about auth flow, it was a pretty simple way to implement an auth flow with username and password and of course, there are many other ways and advanced technical approaches to apply here, however, the target here was to show how we could do it as faster as we can. Feel free to improve and change anything that you like.

Thank you fellow devs!

Top comments (0)