An enterprise-grade extension layer on top of NestJS for building secure, scalable server applications with GraphQL, REST/Swagger, and MongoDB.
- Quick Start
- Features Overview
- Architecture
- API-First Design
- Authentication
- Roles & Permissions
- Multi-Tenancy
- Scalability
- API Versioning
- Webhook Support
- External System Integration
- Core Modules
- Configuration
- Development
- Documentation
- License
The fastest way to get started is via the lenne.Tech CLI with the starter project:
npm install -g @lenne.tech/cli
lt server create <ServerName>
cd <ServerName>
pnpm startOr install directly as a package:
pnpm add @lenne.tech/nest-serverlt server module <ModuleName>This generates a complete module with model, inputs, resolver, controller, and service.
| Category | Features |
|---|---|
| API-First | GraphQL (Apollo Server) + REST with Swagger/OpenAPI, dual API surface from single codebase |
| Authentication | BetterAuth (IAM) with JWT, sessions, 2FA/TOTP, Passkey/WebAuthn, social login; Legacy JWT auth |
| Authorization | Extensible role-based access control, field-level restrictions, system roles, hierarchy roles |
| Database | MongoDB via Mongoose, CrudService with security pipeline, advanced filtering & pagination |
| Multi-Tenancy | Header-based tenant isolation, membership management, hierarchy roles, auto-filtering |
| File Handling | GridFS file storage, resumable uploads via TUS protocol (up to 50 GB) |
| Security | Defense-in-depth: input validation, password hashing, role guard, audit fields, response filtering |
| Integration | SCIM support, OAuth providers, multi-provider email (Mailjet/Brevo/SMTP), webhook support via lifecycle hooks |
| Scalability | Stateless design, distributed migration locking, request-scoped isolation, query complexity analysis |
| API Versioning | NestJS-native URI/header/media-type versioning, GraphQL schema evolution with deprecations |
| DevOps | Health checks, database migrations, cron jobs, permissions reporting, system setup |
The framework is built on a Module Inheritance Pattern — all core modules are designed to be extended through class inheritance in consuming projects:
// Your project extends core classes — full control via override + super()
export class UserService extends CoreUserService {
override async create(input, serviceOptions) {
// Custom pre-processing (validation, enrichment, ...)
const result = await super.create(input, serviceOptions);
// Custom post-processing (notifications, logging, ...)
return result;
}
}Why inheritance over hooks/events:
- Full control: Override methods and precisely define what happens before/after
super()calls - Selective override: Skip parts of the parent implementation entirely for custom logic
- Type safety: TypeScript inheritance ensures proper typing and IDE support
- No silent failures: No runtime event systems or hooks that can fail silently
src/core/— Reusable framework components (exported via npm, extended by projects)src/server/— Internal test/demo implementation (not exported)
Every core module supports both auto-registration (zero-config) and manual registration (full customization via extended module). New modules can be added alongside existing ones without modifying the framework.
- NestJS — Server framework
- Apollo Server — GraphQL API (optional, disable via
graphQl: false) - Mongoose — MongoDB ODM
- Swagger/OpenAPI — REST API documentation
The server follows an API-first approach where API contracts are the primary interface:
Both GraphQL and REST endpoints are served from the same codebase and business logic. The @UnifiedField() decorator generates schemas for both APIs simultaneously:
@UnifiedField({ description: 'User email address', isOptional: false })
email: string;
// → Generates: GraphQL @Field() + Swagger @ApiProperty() + class-validator checks- GraphQL Introspection — Full schema introspection for client code generation (configurable via
graphQl.introspection) - Swagger/OpenAPI — Auto-generated REST API documentation from decorators
- SpectaQL — Visual GraphQL documentation (
pnpm run docs) - Permissions Dashboard — Interactive HTML report of all endpoints, roles, and security checks (
/permissions)
- Custom scalars:
Date,DateTime,JSON,Any - WebSocket subscriptions with authentication
- Query complexity analysis (DoS prevention)
- File upload support via
graphql-upload - Automatic enum registration
NestJS provides built-in API versioning support with multiple strategies. Projects can enable versioning for REST endpoints:
| Strategy | Example | Use Case |
|---|---|---|
| URI | /v1/users, /v2/users |
Most common, explicit in URL |
| Header | X-API-Version: 2 |
Clean URLs, version in header |
| Media Type | Accept: application/vnd.app.v2+json |
Content negotiation |
// main.ts — enable URI versioning
app.enableVersioning({ type: VersioningType.URI });
// controller — assign to version
@Controller({ path: 'users', version: '2' })
export class UsersV2Controller { ... }
// or per-route
@Get()
@Version('2')
async findAll() { ... }GraphQL APIs are evolved through schema additions and field deprecations (@deprecated) rather than versioning, following the GraphQL best practice of continuous evolution.
The framework provides webhook capabilities through two mechanisms:
BetterAuth supports hooks that intercept authentication lifecycle events. Projects can extend the BetterAuth service via the Module Inheritance Pattern to react to these events:
export class AuthService extends CoreBetterAuthService {
override async signIn(input, serviceOptions) {
const result = await super.signIn(input, serviceOptions);
// Trigger webhook after successful sign-in
await this.webhookService.dispatch('auth.sign-in', { userId: result.user.id });
return result;
}
}Authentication events that can be intercepted include: sign-in, sign-up, sign-out, session creation, token refresh, email verification, 2FA verification, and more.
Any service method can be extended to dispatch webhooks via the Module Inheritance Pattern:
export class ProjectService extends CoreProjectService {
override async create(input, serviceOptions) {
const project = await super.create(input, serviceOptions);
// Dispatch webhook on project creation
await this.webhookService.dispatch('project.created', project);
return project;
}
}This approach allows projects to add webhook dispatching to any CRUD operation or custom business logic without modifying the framework.
Two authentication systems are available — BetterAuth (recommended) and Legacy JWT auth:
Modern, session-based authentication with plugin architecture:
| Feature | Config |
|---|---|
| JWT tokens | betterAuth.jwt: true |
| Two-Factor (2FA/TOTP) | betterAuth.twoFactor: true |
| Passkey/WebAuthn | betterAuth.passkey: true |
| Social login (Google, GitHub, Apple, ...) | betterAuth.socialProviders: { ... } |
| Email verification | betterAuth.emailVerification: { ... } |
| Rate limiting | betterAuth.rateLimit: { max: 10 } |
| Cross-subdomain cookies | betterAuth.crossSubDomainCookies: true |
| Disable sign-up | betterAuth.emailAndPassword.disableSignUp: true |
Three integration patterns: zero-config, config-based, or manual (autoRegister: false).
BetterAuth provides lifecycle hooks for reacting to authentication events (sign-in, sign-up, session creation, etc.), enabling integration with external systems. See Webhook Support for details.
Passport-based JWT authentication with sign-in, sign-up, refresh tokens, and rate limiting. Runs in parallel with BetterAuth during migration. Legacy endpoints can be disabled via auth.legacyEndpoints.enabled: false.
A built-in migration status query (betterAuthMigrationStatus) tracks progress and indicates when legacy auth can be safely disabled.
The role and permission system is designed for extensibility — from simple role checks to complex multi-tenant hierarchies.
System roles (S_ prefix) are evaluated dynamically at runtime, never stored in the database:
| Role | Purpose |
|---|---|
S_USER |
Any authenticated user |
S_VERIFIED |
Email-verified users |
S_CREATOR |
Creator of the resource |
S_SELF |
User accessing own data |
S_EVERYONE |
Public access |
S_NO_ONE |
Permanently locked |
// Method-level: who can call this endpoint?
@Roles(RoleEnum.ADMIN)
async deleteUser() { ... }
// Field-level: who can see/modify this field?
@Restricted(RoleEnum.S_SELF, RoleEnum.ADMIN)
email: string;
// Membership-based: only team members can see this field
@Restricted({ memberOf: 'teamMembers' })
internalNotes: string;Projects can define custom role hierarchies beyond the built-in roles:
// Custom hierarchy with level-based comparison
const HR = createHierarchyRoles({ viewer: 1, editor: 2, admin: 3, owner: 4 });
@Roles(HR.EDITOR) // requires level >= 2 (editor, admin, or owner)
async editDocument() { ... }Every model can override securityCheck() for fine-grained, context-aware access control:
export class Project extends CorePersistenceModel {
override securityCheck(user: any, force?: boolean): this {
// Remove sensitive fields based on user context
if (!this.hasRole(user, RoleEnum.ADMIN)) {
this.budget = undefined;
}
return this;
}
}The built-in Permissions module scans all endpoints and generates security audit reports (HTML dashboard, JSON, Markdown) showing coverage of @Roles, @Restricted, and securityCheck() across the entire API.
Full multi-tenancy support with header-based tenant isolation, membership management, and automatic data filtering:
// config.env.ts
multiTenancy: {
headerName: 'x-tenant-id',
roleHierarchy: { member: 1, manager: 2, owner: 3 },
adminBypass: true,
}
// Usage in resolvers
@Roles(DefaultHR.MANAGER)
async updateProject(@CurrentTenant() tenantId: string) { ... }- Membership validation —
CoreTenantGuardvalidates membership on every request - Hierarchy roles — Level comparison (higher includes lower), custom via
createHierarchyRoles() - Automatic data isolation — Mongoose tenant plugin filters all queries by tenant context
- Defense-in-depth — Guard validation + Mongoose plugin Safety Net combination
- Multi-membership — Users can belong to multiple tenants with different roles
- Status management — ACTIVE, INVITED, SUSPENDED membership states
@SkipTenantCheck()— Opt out per endpoint@CurrentTenant()— Parameter decorator for validated tenant ID
The architecture is designed for horizontal scalability:
- Stateless request handling — No server-side session state required (JWT or BetterAuth sessions in MongoDB)
- Request-scoped isolation —
AsyncLocalStorage-basedRequestContextensures safe concurrent request processing without shared mutable state - Distributed migration locking — MongoDB-based distributed locks via
synchronizedMigration()for safe multi-instance deployments - Query complexity analysis — Configurable complexity limits prevent expensive GraphQL queries from consuming excessive resources
- Connection pooling — Managed transparently via Mongoose/MongoDB driver
- Configurable MongoDB — Support for replica sets and connection options via
mongoose.uriconfiguration
BetterAuth supports OAuth integration with external identity providers (Google, GitHub, Apple, Discord, and more) via the socialProviders configuration.
Built-in SCIM (System for Cross-domain Identity Management) filter parsing and MongoDB query conversion for enterprise identity management:
scimToMongo('userName eq "Joe" and emails[type eq "work"]')
// → MongoDB: { $and: [{ userName: 'Joe' }, { emails: { $elemMatch: { type: 'work' } } }] }Supported operators: eq, co, sw, ew, gt, ge, lt, le, pr, aco, and, or.
Pluggable email provider abstraction with support for:
- Mailjet — API-based transactional email
- Brevo — API-based transactional email (formerly Sendinblue)
- SMTP — Standard SMTP via Nodemailer
All providers use the same EmailService interface with EJS template engine and locale-aware template resolution.
The Module Inheritance Pattern ensures any core module can be extended to integrate with external systems — override service methods to add API calls, event publishing, webhook dispatching, or data synchronization without modifying the framework. See also Webhook Support.
| Module | Purpose |
|---|---|
| Auth | Legacy JWT authentication with Passport strategies |
| BetterAuth | Modern auth (2FA, Passkey, Social, sessions, lifecycle hooks) |
| User | User management, profile, roles, verification |
| Tenant | Multi-tenancy with membership and hierarchy roles |
| File | File upload/download with MongoDB GridFS |
| Tus | Resumable file uploads via tus.io protocol (up to 50 GB) |
| ErrorCode | Centralized error codes with unique identifiers |
| HealthCheck | REST (/health) and GraphQL health monitoring |
| Migrate | MongoDB migration system with distributed locking for cluster deployments |
| Permissions | Security audit dashboard (HTML, JSON, Markdown reports) |
| SystemSetup | Initial admin creation for fresh deployments |
Abstract base service providing a complete CRUD pipeline with built-in security:
export class ProjectService extends CrudService<Project> {
// Inherits: find, findOne, create, update, delete
// With: input validation, field selection, security checks, population
}Advanced querying with comparison operators (eq, ne, gt, in, contains, regex, ...), logical operators (AND, OR), pagination, sorting, and GraphQL-driven population.
Defense-in-depth security architecture with three layers:
Layer 1: Guards & Middleware
@Roles()— Method-level authorization (includes JWT auth automatically)@Restricted()— Field-level access control with process type support (INPUT/OUTPUT)- System roles —
S_USER,S_VERIFIED,S_CREATOR,S_SELF,S_EVERYONE,S_NO_ONE
Layer 2: CrudService Pipeline
- Input validation — MapAndValidatePipe with whitelist checking
@UnifiedField()— Single decorator for GraphQL, Swagger, and validationsecurityCheck()— Resource-level security on model instances
Layer 3: Mongoose Plugins (Safety Net)
- Password Plugin — Automatic BCrypt hashing
- Role Guard Plugin — Prevents unauthorized role escalation at DB level
- Audit Fields Plugin — Automatic
createdBy/updatedBytracking - Tenant Isolation Plugin — Automatic tenant filtering
Interceptor Chain:
- ResponseModelInterceptor — Plain objects to CoreModel conversion
- TranslateResponseInterceptor — Multi-language support via
Accept-Language - CheckSecurityInterceptor — Executes
securityCheck()and removes secret fields - CheckResponseInterceptor — Enforces
@Restricted()field-level filtering
Multi-provider email service (Mailjet, Brevo, SMTP) with EJS template engine and locale-aware template resolution.
Configuration is managed via src/config.env.ts with environment-based profiles (development, local, production):
export const config = {
local: {
port: 3000,
graphQl: { driver: 'apollo' },
mongoose: { uri: 'mongodb://localhost/my-app' },
betterAuth: { secret: 'my-secret', jwt: true, twoFactor: true },
multiTenancy: { roleHierarchy: { member: 1, manager: 2, owner: 3 } },
},
};- Presence implies enabled:
rateLimit: {}enables with defaults,undefinedstays disabled - Boolean shorthand:
jwt: trueenables with defaults,{ expiresIn: '1h' }customizes
Three methods to override configuration:
- Direct —
process.env.PORTinconfig.env.ts - JSON —
NEST_SERVER_CONFIGenvironment variable (deep merge) - Prefixed —
NSC__EMAIL__DEFAULT_SENDER__NAMEforemail.defaultSender.name
Requirements: Node.js >= 20, MongoDB, pnpm
# Install dependencies
pnpm install
# Start in development mode
pnpm run start:dev
# Run tests (Vitest E2E)
pnpm test
# Run tests with coverage
pnpm run vitest:cov
# Lint (oxlint) & format (oxfmt)
pnpm run lint
pnpm run format
# Build
pnpm run build
# Generate documentation
pnpm run docsLink into a consuming project for local development:
# In nest-server
pnpm run watch
# In your project
pnpm run link:nest-server # pnpm link /path/to/nest-server
pnpm run unlink:nest-server # pnpm unlink @lenne.tech/nest-server && pnpm installMAJOR.MINOR.PATCH — MAJOR mirrors NestJS version, MINOR = breaking changes, PATCH = non-breaking improvements.
- Request Lifecycle — Complete request pipeline, security architecture, interceptor chain
- Migration Guides — Version upgrade instructions
- Starter Project — Reference implementation
- CLI — Code generation tools
- NestJS Documentation — Framework docs
Many thanks to the developers of NestJS and all the developers whose packages are used here.
MIT - see LICENSE