Skip to main content

NestJS Integration

A complete, production-ready NestJS integration with AuthModule.forRoot(), JwtAuthGuard, @CurrentUser() decorator, and an admin panel.

Open live demo in StackBlitz
Source: demo/nestjs-fullstack/


Auth flow


Step 1 — AuthModule (forRoot pattern)

import 'reflect-metadata';
import { Module, Global, DynamicModule, Provider } from '@nestjs/common';
import { AuthConfigurator, AuthConfig, IUserStore } from '@nik2208/node-auth';

export const AUTH_CONFIGURATOR = 'AUTH_CONFIGURATOR';

@Global()
@Module({})
export class AuthModule {
static forRoot(options: { config: AuthConfig; userStore: IUserStore }): DynamicModule {
const configuratorProvider: Provider = {
provide: AUTH_CONFIGURATOR,
useFactory: () => new AuthConfigurator(options.config, options.userStore),
};
return {
module: AuthModule,
providers: [configuratorProvider],
exports: [AUTH_CONFIGURATOR],
};
}
}

Step 2 — JwtAuthGuard

import { Injectable, CanActivate, ExecutionContext, Inject } from '@nestjs/common';
import { AuthConfigurator } from '@nik2208/node-auth';
import { Request, Response } from 'express';

@Injectable()
export class JwtAuthGuard implements CanActivate {
constructor(@Inject(AUTH_CONFIGURATOR) private readonly auth: AuthConfigurator) {}

canActivate(context: ExecutionContext): Promise<boolean> {
const req = context.switchToHttp().getRequest<Request>();
const res = context.switchToHttp().getResponse<Response>();
return new Promise<boolean>((resolve, reject) => {
// NestJS req/res are Express-compatible; bridge via `as any`
this.auth.middleware()(req as any, res as any, (err?: unknown) => {
if (err) return reject(err);
resolve(true);
});
});
}
}

Step 3 — @CurrentUser() decorator

import { createParamDecorator, ExecutionContext } from '@nestjs/common';
import { Request } from 'express';

export const CurrentUser = createParamDecorator(
(_data: unknown, ctx: ExecutionContext) =>
ctx.switchToHttp().getRequest<Request & { user?: any }>().user,
);

Step 4 — AuthController

import { Controller, All, Req, Res, Next, Inject } from '@nestjs/common';
import { AuthConfigurator, AuthError, PasswordService } from '@nik2208/node-auth';
import { Request, Response, NextFunction } from 'express';

@Controller('auth')
export class AuthController {
private readonly router: ReturnType<AuthConfigurator['router']>;

constructor(@Inject(AUTH_CONFIGURATOR) auth: AuthConfigurator) {
this.router = auth.router({
onRegister: async (data) => {
const email = typeof data.email === 'string' ? data.email.trim() : '';
const password = typeof data.password === 'string' ? data.password.trim() : '';
if (!email || !password)
throw new AuthError('email and password are required', 'VALIDATION_ERROR', 400);
const existing = await userStore.findByEmail(email);
if (existing) throw new AuthError('Email already registered', 'EMAIL_EXISTS', 409);
const hash = await new PasswordService().hash(password);
return userStore.create({ email, password: hash, role: 'user' });
},
});
}

@All('*')
handle(@Req() req: Request, @Res() res: Response, @Next() next: NextFunction): void {
// Strip the /auth prefix so the inner router sees paths from /
req.url = req.url.replace(/^\/auth/, '') || '/';
// NestJS req/res are Express-compatible; bridge via `as any`
this.router(req as any, res as any, next);
}
}

Step 5 — Protected route with @CurrentUser()

import { Controller, Get, UseGuards } from '@nestjs/common';

@Controller('api')
export class ProfileController {
@Get('profile')
@UseGuards(JwtAuthGuard)
getProfile(@CurrentUser() user: any) {
return user; // {sub, email, role, …}
}
}

Step 6 — AppModule & bootstrap

import { Module } from '@nestjs/common';
import { NestFactory } from '@nestjs/core';
import { NestExpressApplication } from '@nestjs/platform-express';
import { createAdminRouter } from '@nik2208/node-auth';
import * as path from 'path';

const userStore = new MyUserStore(); // your IUserStore implementation

const authConfig = {
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET ?? 'change-me',
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET ?? 'change-me',
accessTokenExpiresIn: '15m',
refreshTokenExpiresIn: '7d',
cookieOptions: {
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax' as const,
},
};

@Module({
imports: [AuthModule.forRoot({ config: authConfig, userStore })],
providers: [JwtAuthGuard],
controllers: [AuthController, ProfileController],
})
class AppModule {}

async function bootstrap() {
const app = await NestFactory.create<NestExpressApplication>(AppModule);

app.use(require('cookie-parser')());
app.enableCors({ origin: true, credentials: true });

// Admin panel (HTML UI + REST API) at /admin
app.use('/admin', createAdminRouter(userStore, {
adminSecret: process.env.ADMIN_SECRET ?? '1234',
}) as any);

// Serve your frontend static files
app.useStaticAssets(path.join(__dirname, '..', 'public'));

await app.listen(Number(process.env.PORT) || 3000);
}

bootstrap();

Summary

PieceRole
AuthModule.forRoot()Creates AuthConfigurator singleton, exports it globally via AUTH_CONFIGURATOR token
JwtAuthGuardCalls auth.middleware() as NestJS CanActivate — sets req.user on success
@CurrentUser()Parameter decorator that extracts req.user from the guard
AuthController @All('*')Delegates every /auth/* request to the node-auth router
createAdminRouterExpress router mounted at /admin — provides HTML UI and REST API