DEV Community

Cover image for Building a Login and Registration System Using NestJS with TypeORM and PostgreSQL
BuildWithGagan
BuildWithGagan

Posted on

Building a Login and Registration System Using NestJS with TypeORM and PostgreSQL

Introduction

Authentication is a core feature of modern web applications. This step-by-step guide will help you build a robust login and registration system using NestJS, TypeORM, and PostgreSQL. Whether you're a beginner or someone looking to solidify their understanding, this guide walks you through everything from project setup to implementing secure authentication with global database configurations.


What Will You Learn?

  • Setting up a NestJS project
  • Configuring TypeORM with a PostgreSQL database
  • Building reusable entities and services
  • Securing passwords with bcrypt
  • Using JWT for token-based authentication
  • Handling validation and error scenarios

Setting Up Your Environment

1. Install NestJS CLI

First, ensure you have the NestJS CLI installed:

npm install -g @nestjs/cli
Enter fullscreen mode Exit fullscreen mode

2. Create a New NestJS Project

Generate a new project:

nest new nest-auth-system
Enter fullscreen mode Exit fullscreen mode

Choose npm or yarn for package management during the setup.

3. Install Required Dependencies

npm install @nestjs/typeorm typeorm pg bcrypt jsonwebtoken @nestjs/jwt @nestjs/config class-validator class-transformer
Enter fullscreen mode Exit fullscreen mode

Setting Up PostgreSQL with TypeORM

1. Create the Database

Using PostgreSQL, create a database for your project:

CREATE DATABASE nest_auth_db;
Enter fullscreen mode Exit fullscreen mode

2. Configure TypeORM Globally

Update your app.module.ts to configure TypeORM globally:

import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    ConfigModule.forRoot({ isGlobal: true }),
    TypeOrmModule.forRootAsync({
      imports: [ConfigModule],
      inject: [ConfigService],
      useFactory: (configService: ConfigService) => ({
        type: 'postgres',
        host: configService.get<string>('DB_HOST', 'localhost'),
        port: +configService.get<number>('DB_PORT', 5432),
        username: configService.get<string>('DB_USERNAME', 'postgres'),
        password: configService.get<string>('DB_PASSWORD', 'password'),
        database: configService.get<string>('DB_DATABASE', 'nest_auth_db'),
        autoLoadEntities: true,
        synchronize: true, // Disable in production
      }),
    }),
  ],
})
export class AppModule {}
Enter fullscreen mode Exit fullscreen mode

Add your database credentials to a .env file:

DB_HOST=localhost
DB_PORT=5432
DB_USERNAME=postgres
DB_PASSWORD=password
DB_DATABASE=nest_auth_db
JWT_SECRET=your_jwt_secret
Enter fullscreen mode Exit fullscreen mode

Building the User Entity

1. Generate a User Module

nest g module users
nest g service users
nest g controller users
Enter fullscreen mode Exit fullscreen mode

2. Create the User Entity

In users.entity.ts:

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

@Entity('users')
export class User {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ unique: true })
  username: string;

  @Column({ unique: true })
  email: string;

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

Creating Authentication Logic

1. Generate the Auth Module

nest g module auth
nest g service auth
nest g controller auth
Enter fullscreen mode Exit fullscreen mode

2. Implement Registration Logic

In auth.service.ts:

import { Injectable, BadRequestException } from '@nestjs/common';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcrypt';

@Injectable()
export class AuthService {
  constructor(private readonly usersService: UsersService) {}

  async register(username: string, email: string, password: string) {
    const existingUser = await this.usersService.findByEmail(email);
    if (existingUser) {
      throw new BadRequestException('Email already in use');
    }

    const hashedPassword = await bcrypt.hash(password, 10);
    return this.usersService.create({ username, email, password: hashedPassword });
  }
}
Enter fullscreen mode Exit fullscreen mode

3. Add UserService Methods

In users.service.ts:

import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { Repository } from 'typeorm';
import { User } from './user.entity';

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

  async create(data: Partial<User>): Promise<User> {
    return this.userRepository.save(data);
  }

  async findByEmail(email: string): Promise<User | undefined> {
    return this.userRepository.findOne({ where: { email } });
  }
}
Enter fullscreen mode Exit fullscreen mode

4. Create the Register Endpoint

In auth.controller.ts:

import { Controller, Post, Body } from '@nestjs/common';
import { AuthService } from './auth.service';

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

  @Post('register')
  async register(
    @Body('username') username: string,
    @Body('email') email: string,
    @Body('password') password: string,
  ) {
    return this.authService.register(username, email, password);
  }
}
Enter fullscreen mode Exit fullscreen mode

Adding Login Functionality

1. Implement Login Logic

In auth.service.ts:

import { UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';

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

  async login(email: string, password: string) {
    const user = await this.usersService.findByEmail(email);
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      throw new UnauthorizedException('Invalid credentials');
    }

    const token = this.jwtService.sign({ id: user.id });
    return { token };
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Create the Login Endpoint

In auth.controller.ts:

@Post('login')
async login(
  @Body('email') email: string,
  @Body('password') password: string,
) {
  return this.authService.login(email, password);
}
Enter fullscreen mode Exit fullscreen mode

Securing Routes with JWT

1. Add JWT Strategy

Generate a guard:

nest g guard jwt
Enter fullscreen mode Exit fullscreen mode

In jwt.strategy.ts:

import { Injectable } from '@nestjs/common';
import { PassportStrategy } from '@nestjs/passport';
import { ExtractJwt, Strategy } from 'passport-jwt';

@Injectable()
export class JwtStrategy extends PassportStrategy(Strategy) {
  constructor() {
    super({
      jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
      secretOrKey: process.env.JWT_SECRET,
    });
  }

  async validate(payload: any) {
    return { userId: payload.id };
  }
}
Enter fullscreen mode Exit fullscreen mode

2. Protect Routes

In your controller:

import { UseGuards } from '@nestjs/common';
import { JwtAuthGuard } from './jwt.guard';

@Controller('profile')
@UseGuards(JwtAuthGuard)
export class ProfileController {
  @Get()
  getProfile(@Req() req) {
    return req.user;
  }
}
Enter fullscreen mode Exit fullscreen mode

FAQs

Why Use TypeORM with NestJS?

TypeORM integrates seamlessly with NestJS and provides powerful features for database management.

How Secure Is JWT Authentication?

JWT is secure if implemented correctly with a strong secret and proper token expiration.


By following this comprehensive guide, you’ve built a robust login and registration system with NestJS, TypeORM, and PostgreSQL. This scalable solution can be enhanced further with features like password resets, user roles, and more!

Top comments (0)