Building a Login and Registration System Using NestJS with TypeORM and PostgreSQL
BuildWithGagan
Posted on November 26, 2024
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
2. Create a New NestJS Project
Generate a new project:
nest new nest-auth-system
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
Setting Up PostgreSQL with TypeORM
1. Create the Database
Using PostgreSQL, create a database for your project:
CREATE DATABASE nest_auth_db;
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 {}
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
Building the User Entity
1. Generate a User Module
nest g module users
nest g service users
nest g controller users
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;
}
Creating Authentication Logic
1. Generate the Auth Module
nest g module auth
nest g service auth
nest g controller auth
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 });
}
}
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 } });
}
}
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);
}
}
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 };
}
}
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);
}
Securing Routes with JWT
1. Add JWT Strategy
Generate a guard:
nest g guard jwt
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 };
}
}
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;
}
}
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!
Posted on November 26, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.