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!

Heroku

Deploy with ease. Manage efficiently. Scale faster.

Leave the infrastructure headaches to us, while you focus on pushing boundaries, realizing your vision, and making a lasting impression on your users.

Get Started

Top comments (0)

👋 Kindness is contagious

Explore a trove of insights in this engaging article, celebrated within our welcoming DEV Community. Developers from every background are invited to join and enhance our shared wisdom.

A genuine "thank you" can truly uplift someone’s day. Feel free to express your gratitude in the comments below!

On DEV, our collective exchange of knowledge lightens the road ahead and strengthens our community bonds. Found something valuable here? A small thank you to the author can make a big difference.

Okay