DEV Community

Cover image for Building a NestJS Authentication API with MongoDB and JWT: A Step-by-Step Guide
FredAbod
FredAbod

Posted on

1

Building a NestJS Authentication API with MongoDB and JWT: A Step-by-Step Guide

Building a NestJS Authentication API with MongoDB and JWT: A Step-by-Step Guide

Authentication is a critical part of any application. In this guide, we’ll build a complete authentication system using NestJS, MongoDB, and JWT. We'll cover everything from setting up the project to implementing authentication routes and securing them with guards.

Dive IN

Prerequisites

  • Node.js and npm installed
  • Basic understanding of NestJS
  • MongoDB installed locally or access to a MongoDB cloud instance
  • A willingness to learn (and maybe some coffee ☕)

To install the NestJS CLI globally, run:

npm install -g @nestjs/cli

Verify the installation:

nest --version

Step 1: Initialize the Project

Create a new NestJS project:

nest new auth-app
cd auth-app
Enter fullscreen mode Exit fullscreen mode

Install the required dependencies:

npm install @nestjs/mongoose mongoose @nestjs/jwt @nestjs/passport passport-jwt bcryptjs
npm install --save-dev @types/bcryptjs @types/passport-jwt
Enter fullscreen mode Exit fullscreen mode

Setting Up MongoDB

Step 2: Configure MongoDB Connection
NestJS provides the @nestjs/mongoose package to integrate MongoDB. Let’s set up the connection.

Create a DatabaseModule:

nest generate module database
Enter fullscreen mode Exit fullscreen mode

Configure the connection in database.module.ts:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { ConfigModule, ConfigService } from '@nestjs/config';

@Module({
  imports: [
    MongooseModule.forRootAsync({
      imports: [ConfigModule],
      useFactory: async (configService: ConfigService) => ({
        uri: configService.get<string>('DATABASE_URL'),
      }),
      inject: [ConfigService],
    }),
  ],
})
export class DatabaseModule {}
Enter fullscreen mode Exit fullscreen mode

Add the DATABASE_URL to your .env file:

DATABASE_URL=mongodb://localhost/nest-auth
Enter fullscreen mode Exit fullscreen mode

Creating the User Module

Step 3: Define the User Schema

Generate the User module:

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

Create the User schema in user.schema.ts:

import { Prop, Schema, SchemaFactory } from '@nestjs/mongoose';
import { Document } from 'mongoose';

export type UserDocument = User & Document;

@Schema({ timestamps: true })
export class User {
  @Prop({ required: true, unique: true })
  username: string;

  @Prop({ required: true })
  password: string;

  @Prop({ required: true, unique: true })
  email: string;

  @Prop({ default: null })
  refreshToken: string;
}

export const UserSchema = SchemaFactory.createForClass(User);
Enter fullscreen mode Exit fullscreen mode

Update the UsersModule to include the schema:

import { Module } from '@nestjs/common';
import { MongooseModule } from '@nestjs/mongoose';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { User, UserSchema } from './schemas/user.schema';

@Module({
  imports: [MongooseModule.forFeature([{ name: User.name, schema: UserSchema }])],
  providers: [UsersService],
  controllers: [UsersController],
  exports: [UsersService],
})
export class UsersModule {}
Enter fullscreen mode Exit fullscreen mode

Implementing Authentication

Step 4: Create the Auth Module

Generate the Auth module:

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

Configure JWT and Passport in auth.module.ts:

import { Module } from '@nestjs/common';
import { JwtModule } from '@nestjs/jwt';
import { PassportModule } from '@nestjs/passport';
import { UsersModule } from '../users/users.module';
import { AuthService } from './auth.service';
import { AuthController } from './auth.controller';
import { JwtStrategy } from './jwt.strategy';

@Module({
  imports: [
    UsersModule,
    PassportModule,
    JwtModule.register({
      secret: process.env.JWT_SECRET || 'fallback-secret',
      signOptions: { expiresIn: '15m' },
    }),
  ],
  providers: [AuthService, JwtStrategy],
  controllers: [AuthController],
})
export class AuthModule {}
Enter fullscreen mode Exit fullscreen mode

Add the JWT_SECRET to your .env file:

JWT_SECRET=your_jwt_secret_key_here
Enter fullscreen mode Exit fullscreen mode

Step 5: Implement the Auth Service
The AuthService handles the business logic for authentication.

import { Injectable, UnauthorizedException } from '@nestjs/common';
import { JwtService } from '@nestjs/jwt';
import { UsersService } from '../users/users.service';
import * as bcrypt from 'bcryptjs';

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

  async validateUser(username: string, password: string): Promise<any> {
    const user = await this.usersService.findOne(username);
    if (!user) {
      throw new UnauthorizedException('Invalid credentials');
    }

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

    const { password: _, ...result } = user.toObject();
    return result;
  }

  async login(user: any) {
    const payload = { username: user.username, sub: user._id };
    return {
      access_token: this.jwtService.sign(payload),
    };
  }

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

Step 6: Implement the Auth Controller
The AuthController defines the authentication routes.

import { Controller, Post, Body, UseGuards, Get, Request } from '@nestjs/common';
import { AuthService } from './auth.service';
import { JwtAuthGuard } from './jwt-auth.guard';
import { LoginDto, RegisterDto } from '../common/dto/auth.dto';

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

  @Post('login')
  async login(@Body() loginDto: LoginDto) {
    const user = await this.authService.validateUser(loginDto.username, loginDto.password);
    return this.authService.login(user);
  }

  @Post('register')
  async register(@Body() registerDto: RegisterDto) {
    return this.authService.register(registerDto.username, registerDto.email, registerDto.password);
  }

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

Step 7: Secure Routes with Guards
Create a JWT Guard in jwt-auth.guard.ts:

import { Injectable } from '@nestjs/common';
import { AuthGuard } from '@nestjs/passport';

@Injectable()
export class JwtAuthGuard extends AuthGuard('jwt') {}
Enter fullscreen mode Exit fullscreen mode

Implement the JWT Strategy 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(),
      ignoreExpiration: false,
      secretOrKey: process.env.JWT_SECRET || 'fallback-secret',
    });
  }

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

Conclusion

In this guide, we built a complete authentication system using NestJS, MongoDB, and JWT. We covered everything from setting up the project to implementing secure routes with guards. This setup can serve as a foundation for more advanced features like role-based access control, email verification, and password reset functionality.
Don't Forget to Like and Follow❤️❤️
Here's the GitHub Repo for reference Nest-Auth-App
If You're coming from express.js no worries(●'◡'●) I Gat you, Check out my article on Understanding a NestJS Authentication App for an Express.js Developer

Happy coding! 🎉

Happy coding!

Tiugo image

Modular, Fast, and Built for Developers

CKEditor 5 gives you full control over your editing experience. A modular architecture means you get high performance, fewer re-renders and a setup that scales with your needs.

Start now

Top comments (0)