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. 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 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 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 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('DATABASE_URL'), }), inject: [ConfigService], }), ], }) export class DatabaseModule {} Add the DATABASE_URL to your .env file: DATABASE_URL=mongodb://localhost/nest-auth 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 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); 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 {} Implementing Authentication Step 4: Create the Auth Module Generate the Auth module: nest generate module auth nest generate service auth nest generate controller auth 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 {} Add the JWT_SECRET to your .env file: JWT_SECRET=your_jwt_secret_key_here 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 { 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:

Mar 27, 2025 - 14:16
 0
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

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

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

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 {}

Add the DATABASE_URL to your .env file:

DATABASE_URL=mongodb://localhost/nest-auth

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

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);

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 {}

Implementing Authentication

Step 4: Create the Auth Module

Generate the Auth module:

nest generate module auth
nest generate service auth
nest generate controller auth

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 {}

Add the JWT_SECRET to your .env file:

JWT_SECRET=your_jwt_secret_key_here

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);
  }
}

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;
  }
}

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') {}

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 };
  }
}

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!