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:

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<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!