Skip to main content

Framework-Agnostic Usage

awesome-node-auth ships with framework-neutral HTTP type interfaces so that the core library can be wired into any Node.js HTTP framework — Express, Fastify, Koa, NestJS, Next.js Route Handlers, or a plain http.Server — using the same public API.

Express is still the default

The built-in routers (createAuthRouter, createAdminRouter, buildUiRouter) are implemented with Express and return Express Router objects. This page describes the type-level framework-agnostic surface that was introduced in v1.7 so that non-Express consumers get a clean, typed integration path.


Architecture overview​

awesome-node-auth
├── src/http-types.ts Framework-neutral interfaces
│ ├── AuthRequest Minimal request shape (headers, cookies, body, …)
│ ├── AuthResponse Minimal response shape (status, json, cookie, …)
│ ├── AuthNextFunction (err?: unknown) => void
│ ├── AuthRequestHandler (req, res, next) => void | Promise<void>
│ └── AuthRouter Route-registration interface
│
└── src/adapters/
└── express.ts Express adapter
├── re-exports Router, RequestHandler, Request, Response, NextFunction
└── expressAdapter() — cast AuthRequestHandler → Express RequestHandler

The neutral types live in src/http-types.ts and are exported directly from the main package entry point:

import type {
AuthRequest,
AuthResponse,
AuthNextFunction,
AuthRequestHandler,
AuthRouter,
} from 'awesome-node-auth';

Express (default — zero changes required)​

Existing Express code continues to compile without modification. AuthRequestHandler uses any for its parameter types — a deliberate choice that makes every Express RequestHandler directly assignable without a cast.

Why any and not AuthRequest?

TypeScript enforces contravariance on function parameter types (strictFunctionTypes). If the parameters were typed as AuthRequest, then (req: Express.Request) => void would not be assignable to (req: AuthRequest) => void — because Express.Request is a subtype of AuthRequest, and subtype parameters fail contravariant checks. Using any bypasses this check, making Express, Fastify, Koa, and all third-party middleware directly assignable without an explicit cast.

import express from 'express';
import rateLimit from 'express-rate-limit';
import { AuthConfigurator } from 'awesome-node-auth';

const auth = new AuthConfigurator(config, userStore);

// rateLimiter now typed as AuthRequestHandler — Express RequestHandler still works
app.use('/auth', auth.router({
rateLimiter: rateLimit({ windowMs: 60_000, max: 20 }),
}));

Writing framework-neutral middleware​

When you write custom middleware that should work across frameworks, target the neutral AuthRequestHandler type. On Express, pass it through expressAdapter() to get the correct Express typing for app.use():

import type { AuthRequestHandler } from 'awesome-node-auth';
import { expressAdapter } from 'awesome-node-auth';

// Typed against the neutral interface
const requestLogger: AuthRequestHandler = (req, res, next) => {
console.log(`${req.method} ${req.url}`);
next();
};

// Mount on Express (no cast needed — expressAdapter is a zero-overhead helper)
app.use(expressAdapter(requestLogger));

NestJS​

In a NestJS application you use AuthConfigurator directly inside a service or guard. NestJS decorates its own ExecutionContext-based request objects, but the AuthRequest interface is broad enough to accommodate them:

import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import type { AuthRequest } from 'awesome-node-auth';

@Injectable()
export class JwtGuard implements CanActivate {
canActivate(context: ExecutionContext): boolean {
const req = context.switchToHttp().getRequest<AuthRequest>();
return !!req.user;
}
}

See the full NestJS integration guide for a complete DI-based setup.


Next.js App Router​

Next.js App Router uses NextRequest / NextResponse which do not extend Express types. Map them onto AuthRequest / AuthResponse to use awesome-node-auth's token service directly:

// app/api/me/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { TokenService } from 'awesome-node-auth';
import type { AuthRequest } from 'awesome-node-auth';

const tokenService = new TokenService();

export async function GET(request: NextRequest) {
// Map NextRequest to AuthRequest
const authReq: AuthRequest = {
headers: Object.fromEntries(request.headers.entries()),
cookies: Object.fromEntries(
request.cookies.getAll().map(({ name, value }) => [name, value])
),
method: request.method,
url: request.url,
};

const token = authReq.cookies?.['accessToken'];
if (!token) return NextResponse.json({ error: 'Unauthorized' }, { status: 401 });

// Validate token using the framework-neutral service
const payload = tokenService.verifyAccessToken(token, config.accessTokenSecret);
return NextResponse.json(payload);
}

See the full Next.js integration guide for a complete App Router setup including middleware.


Implementing your own adapter​

If you are integrating with a framework not listed here, implement the AuthRequest and AuthResponse interfaces for your framework's native objects and pass the resulting handler to your framework's router:

import type { AuthRequest, AuthResponse, AuthNextFunction } from 'awesome-node-auth';
import { createAuthMiddleware } from 'awesome-node-auth';

// 1. Create the framework-neutral middleware
const authMiddleware = createAuthMiddleware(config, sessionStore);

// 2. Wrap it for your framework (example: Koa)
koaRouter.use(async (ctx, next) => {
const req: AuthRequest = {
headers: ctx.headers as Record<string, string | string[] | undefined>,
cookies: ctx.cookies ? Object.fromEntries(
Object.entries(ctx.cookies) as [string, string][]
) : undefined,
method: ctx.method,
path: ctx.path,
url: ctx.url,
ip: ctx.ip,
};

const res: AuthResponse = {
status(code) { ctx.status = code; return this; },
json(body) { ctx.body = body; return this; },
send(body) { ctx.body = body; return this; },
end() { /* handled by Koa */ },
cookie(name, value, options) { ctx.cookies.set(name, value, options as any); return this; },
clearCookie(name, options) { ctx.cookies.set(name, '', { maxAge: 0, ...options as any }); return this; },
redirect(urlOrStatus: string | number, url?: string) {
ctx.redirect(typeof urlOrStatus === 'string' ? urlOrStatus : url!);
},
setHeader(name, value) {
ctx.set(name, Array.isArray(value) ? value.join(', ') : value);
return this;
},
};

await new Promise<void>((resolve, reject) => {
authMiddleware(req, res, (err?: unknown) => (err ? reject(err) : resolve()));
});

await next();
});

Exported symbols reference​

SymbolKindDescription
AuthRequestinterfaceMinimal request shape (headers, cookies, body, …)
AuthResponseinterfaceMinimal response shape (status, json, cookie, …)
AuthNextFunctiontype(err?: unknown) => void
AuthRequestHandlertype(req, res, next) => void | Promise<void>
AuthRouterinterfaceRoute-registration interface
expressAdapterfunctionCast AuthRequestHandler → Express RequestHandler