@lenne.tech/nest-server 11.21.0 → 11.21.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +444 -100
- package/dist/core/common/helpers/input.helper.js +11 -8
- package/dist/core/common/helpers/input.helper.js.map +1 -1
- package/dist/core/common/interceptors/check-security.interceptor.js +5 -7
- package/dist/core/common/interceptors/check-security.interceptor.js.map +1 -1
- package/dist/core/common/interfaces/server-options.interface.d.ts +2 -0
- package/dist/core/common/services/email.service.d.ts +5 -1
- package/dist/core/common/services/email.service.js +16 -2
- package/dist/core/common/services/email.service.js.map +1 -1
- package/dist/core/common/services/request-context.service.js +6 -0
- package/dist/core/common/services/request-context.service.js.map +1 -1
- package/dist/core/modules/auth/tokens.decorator.d.ts +1 -1
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.d.ts +6 -0
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js +52 -17
- package/dist/core/modules/better-auth/core-better-auth-user.mapper.js.map +1 -1
- package/dist/core/modules/better-auth/core-better-auth.service.d.ts +3 -1
- package/dist/core/modules/better-auth/core-better-auth.service.js +14 -0
- package/dist/core/modules/better-auth/core-better-auth.service.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.guard.d.ts +16 -2
- package/dist/core/modules/tenant/core-tenant.guard.js +168 -22
- package/dist/core/modules/tenant/core-tenant.guard.js.map +1 -1
- package/dist/core/modules/tenant/core-tenant.service.d.ts +3 -1
- package/dist/core/modules/tenant/core-tenant.service.js +14 -4
- package/dist/core/modules/tenant/core-tenant.service.js.map +1 -1
- package/dist/core/modules/user/core-user.service.js +12 -1
- package/dist/core/modules/user/core-user.service.js.map +1 -1
- package/dist/tsconfig.build.tsbuildinfo +1 -1
- package/package.json +32 -25
- package/src/core/common/helpers/input.helper.ts +24 -9
- package/src/core/common/interceptors/check-security.interceptor.ts +10 -11
- package/src/core/common/interfaces/server-options.interface.ts +45 -0
- package/src/core/common/services/email.service.ts +26 -5
- package/src/core/common/services/request-context.service.ts +8 -0
- package/src/core/modules/better-auth/README.md +20 -1
- package/src/core/modules/better-auth/core-better-auth-user.mapper.ts +86 -21
- package/src/core/modules/better-auth/core-better-auth.service.ts +27 -2
- package/src/core/modules/tenant/INTEGRATION-CHECKLIST.md +20 -1
- package/src/core/modules/tenant/README.md +71 -2
- package/src/core/modules/tenant/core-tenant.guard.ts +304 -27
- package/src/core/modules/tenant/core-tenant.service.ts +13 -4
- package/src/core/modules/user/core-user.service.ts +17 -1
package/README.md
CHANGED
|
@@ -1,165 +1,509 @@
|
|
|
1
1
|
# lenne.Tech Nest Server
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
(or other databases).
|
|
3
|
+
An enterprise-grade extension layer on top of [NestJS](https://nestjs.com/) for building secure, scalable server applications with **GraphQL**, **REST/Swagger**, and **MongoDB**.
|
|
5
4
|
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
[](/LICENSE)
|
|
6
|
+
[](https://www.npmjs.com/package/@lenne.tech/nest-server)
|
|
7
|
+
[](https://nodejs.org/)
|
|
8
|
+
|
|
9
|
+
## Table of Contents
|
|
10
|
+
|
|
11
|
+
- [Quick Start](#quick-start)
|
|
12
|
+
- [Features Overview](#features-overview)
|
|
13
|
+
- [Architecture](#architecture)
|
|
14
|
+
- [API-First Design](#api-first-design)
|
|
15
|
+
- [Authentication](#authentication)
|
|
16
|
+
- [Roles & Permissions](#roles--permissions)
|
|
17
|
+
- [Multi-Tenancy](#multi-tenancy)
|
|
18
|
+
- [Scalability](#scalability)
|
|
19
|
+
- [API Versioning](#api-versioning)
|
|
20
|
+
- [Webhook Support](#webhook-support)
|
|
21
|
+
- [External System Integration](#external-system-integration)
|
|
22
|
+
- [Core Modules](#core-modules)
|
|
23
|
+
- [Configuration](#configuration)
|
|
24
|
+
- [Development](#development)
|
|
25
|
+
- [Documentation](#documentation)
|
|
26
|
+
- [License](#license)
|
|
27
|
+
|
|
28
|
+
## Quick Start
|
|
29
|
+
|
|
30
|
+
The fastest way to get started is via the [lenne.Tech CLI](https://github.com/lenneTech/cli) with the [starter project](https://github.com/lenneTech/nest-server-starter):
|
|
8
31
|
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
32
|
+
```bash
|
|
33
|
+
npm install -g @lenne.tech/cli
|
|
34
|
+
lt server create <ServerName>
|
|
35
|
+
cd <ServerName>
|
|
36
|
+
pnpm start
|
|
37
|
+
```
|
|
12
38
|
|
|
13
|
-
|
|
39
|
+
Or install directly as a package:
|
|
14
40
|
|
|
15
|
-
|
|
41
|
+
```bash
|
|
42
|
+
pnpm add @lenne.tech/nest-server
|
|
43
|
+
```
|
|
16
44
|
|
|
17
|
-
|
|
18
|
-
[lenne.Tech Nest Server starter kit](https://github.com/lenneTech/nest-server-starter) via [CLI](https://github.com/lenneTech/cli):
|
|
45
|
+
### Create a new module
|
|
19
46
|
|
|
47
|
+
```bash
|
|
48
|
+
lt server module <ModuleName>
|
|
20
49
|
```
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
50
|
+
|
|
51
|
+
This generates a complete module with model, inputs, resolver, controller, and service.
|
|
52
|
+
|
|
53
|
+
## Features Overview
|
|
54
|
+
|
|
55
|
+
| Category | Features |
|
|
56
|
+
|----------|----------|
|
|
57
|
+
| **API-First** | GraphQL (Apollo Server) + REST with Swagger/OpenAPI, dual API surface from single codebase |
|
|
58
|
+
| **Authentication** | BetterAuth (IAM) with JWT, sessions, 2FA/TOTP, Passkey/WebAuthn, social login; Legacy JWT auth |
|
|
59
|
+
| **Authorization** | Extensible role-based access control, field-level restrictions, system roles, hierarchy roles |
|
|
60
|
+
| **Database** | MongoDB via Mongoose, CrudService with security pipeline, advanced filtering & pagination |
|
|
61
|
+
| **Multi-Tenancy** | Header-based tenant isolation, membership management, hierarchy roles, auto-filtering |
|
|
62
|
+
| **File Handling** | GridFS file storage, resumable uploads via TUS protocol (up to 50 GB) |
|
|
63
|
+
| **Security** | Defense-in-depth: input validation, password hashing, role guard, audit fields, response filtering |
|
|
64
|
+
| **Integration** | SCIM support, OAuth providers, multi-provider email (Mailjet/Brevo/SMTP), webhook support via lifecycle hooks |
|
|
65
|
+
| **Scalability** | Stateless design, distributed migration locking, request-scoped isolation, query complexity analysis |
|
|
66
|
+
| **API Versioning** | NestJS-native URI/header/media-type versioning, GraphQL schema evolution with deprecations |
|
|
67
|
+
| **DevOps** | Health checks, database migrations, cron jobs, permissions reporting, system setup |
|
|
68
|
+
|
|
69
|
+
## Architecture
|
|
70
|
+
|
|
71
|
+
### Modular Extensibility
|
|
72
|
+
|
|
73
|
+
The framework is built on a **Module Inheritance Pattern** — all core modules are designed to be extended through class inheritance in consuming projects:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// Your project extends core classes — full control via override + super()
|
|
77
|
+
export class UserService extends CoreUserService {
|
|
78
|
+
override async create(input, serviceOptions) {
|
|
79
|
+
// Custom pre-processing (validation, enrichment, ...)
|
|
80
|
+
const result = await super.create(input, serviceOptions);
|
|
81
|
+
// Custom post-processing (notifications, logging, ...)
|
|
82
|
+
return result;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
24
85
|
```
|
|
25
86
|
|
|
26
|
-
|
|
87
|
+
**Why inheritance over hooks/events:**
|
|
88
|
+
- **Full control**: Override methods and precisely define what happens before/after `super()` calls
|
|
89
|
+
- **Selective override**: Skip parts of the parent implementation entirely for custom logic
|
|
90
|
+
- **Type safety**: TypeScript inheritance ensures proper typing and IDE support
|
|
91
|
+
- **No silent failures**: No runtime event systems or hooks that can fail silently
|
|
27
92
|
|
|
28
|
-
|
|
29
|
-
and extended as a boilerplate (git clone) or integrated as a module (npm package).
|
|
93
|
+
### Two-Layer Structure
|
|
30
94
|
|
|
31
|
-
|
|
32
|
-
|
|
95
|
+
- `src/core/` — Reusable framework components (exported via npm, extended by projects)
|
|
96
|
+
- `src/server/` — Internal test/demo implementation (not exported)
|
|
33
97
|
|
|
34
|
-
|
|
98
|
+
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.
|
|
35
99
|
|
|
36
|
-
|
|
100
|
+
### Framework Stack
|
|
37
101
|
|
|
102
|
+
- [NestJS](https://nestjs.com/) — Server framework
|
|
103
|
+
- [Apollo Server](https://www.apollographql.com/docs/apollo-server/) — GraphQL API (optional, disable via `graphQl: false`)
|
|
104
|
+
- [Mongoose](https://mongoosejs.com/) — MongoDB ODM
|
|
105
|
+
- [Swagger/OpenAPI](https://swagger.io/) — REST API documentation
|
|
106
|
+
|
|
107
|
+
## API-First Design
|
|
108
|
+
|
|
109
|
+
The server follows an API-first approach where API contracts are the primary interface:
|
|
110
|
+
|
|
111
|
+
### Dual API Surface
|
|
112
|
+
|
|
113
|
+
Both **GraphQL** and **REST** endpoints are served from the same codebase and business logic. The `@UnifiedField()` decorator generates schemas for both APIs simultaneously:
|
|
114
|
+
|
|
115
|
+
```typescript
|
|
116
|
+
@UnifiedField({ description: 'User email address', isOptional: false })
|
|
117
|
+
email: string;
|
|
118
|
+
// → Generates: GraphQL @Field() + Swagger @ApiProperty() + class-validator checks
|
|
38
119
|
```
|
|
39
|
-
|
|
120
|
+
|
|
121
|
+
### Documented API Structure
|
|
122
|
+
|
|
123
|
+
- **GraphQL Introspection** — Full schema introspection for client code generation (configurable via `graphQl.introspection`)
|
|
124
|
+
- **Swagger/OpenAPI** — Auto-generated REST API documentation from decorators
|
|
125
|
+
- **SpectaQL** — Visual GraphQL documentation (`pnpm run docs`)
|
|
126
|
+
- **Permissions Dashboard** — Interactive HTML report of all endpoints, roles, and security checks (`/permissions`)
|
|
127
|
+
|
|
128
|
+
### GraphQL Features
|
|
129
|
+
|
|
130
|
+
- Custom scalars: `Date`, `DateTime`, `JSON`, `Any`
|
|
131
|
+
- WebSocket subscriptions with authentication
|
|
132
|
+
- Query complexity analysis (DoS prevention)
|
|
133
|
+
- File upload support via `graphql-upload`
|
|
134
|
+
- Automatic enum registration
|
|
135
|
+
|
|
136
|
+
## API Versioning
|
|
137
|
+
|
|
138
|
+
NestJS provides built-in [API versioning](https://docs.nestjs.com/techniques/versioning) support with multiple strategies. Projects can enable versioning for REST endpoints:
|
|
139
|
+
|
|
140
|
+
| Strategy | Example | Use Case |
|
|
141
|
+
|----------|---------|----------|
|
|
142
|
+
| **URI** | `/v1/users`, `/v2/users` | Most common, explicit in URL |
|
|
143
|
+
| **Header** | `X-API-Version: 2` | Clean URLs, version in header |
|
|
144
|
+
| **Media Type** | `Accept: application/vnd.app.v2+json` | Content negotiation |
|
|
145
|
+
|
|
146
|
+
```typescript
|
|
147
|
+
// main.ts — enable URI versioning
|
|
148
|
+
app.enableVersioning({ type: VersioningType.URI });
|
|
149
|
+
|
|
150
|
+
// controller — assign to version
|
|
151
|
+
@Controller({ path: 'users', version: '2' })
|
|
152
|
+
export class UsersV2Controller { ... }
|
|
153
|
+
|
|
154
|
+
// or per-route
|
|
155
|
+
@Get()
|
|
156
|
+
@Version('2')
|
|
157
|
+
async findAll() { ... }
|
|
40
158
|
```
|
|
41
159
|
|
|
42
|
-
|
|
43
|
-
[lenne.Tech Nest Server](https://github.com/lenneTech/nest-server) contains. As long as this is not yet available,
|
|
44
|
-
have a look at the [source code](https://github.com/lenneTech/nest-server/tree/master/src/core).
|
|
45
|
-
There you will find a lot of things that will help you to extend your server, such as:
|
|
160
|
+
**GraphQL** APIs are evolved through schema additions and field deprecations (`@deprecated`) rather than versioning, following the [GraphQL best practice](https://graphql.org/learn/best-practices/#versioning) of continuous evolution.
|
|
46
161
|
|
|
47
|
-
|
|
48
|
-
- [Filter and pagination](https://github.com/lenneTech/nest-server/tree/master/src/core/common/args)
|
|
49
|
-
- [Decorators for restrictions and roles](https://github.com/lenneTech/nest-server/tree/master/src/core/common/decorators)
|
|
50
|
-
- [Authorisation handling](https://github.com/lenneTech/nest-server/tree/master/src/core/modules/auth)
|
|
51
|
-
- [Ready to use user module](https://github.com/lenneTech/nest-server/tree/master/src/core/modules/user)
|
|
52
|
-
- [Common helpers](https://github.com/lenneTech/nest-server/tree/master/src/core/common/helpers) and
|
|
53
|
-
[helpers for tests](https://github.com/lenneTech/nest-server/blob/master/src/test/test.helper.ts)
|
|
54
|
-
- ...
|
|
162
|
+
## Webhook Support
|
|
55
163
|
|
|
56
|
-
|
|
164
|
+
The framework provides webhook capabilities through two mechanisms:
|
|
57
165
|
|
|
58
|
-
|
|
59
|
-
# development
|
|
60
|
-
$ pnpm start
|
|
166
|
+
### BetterAuth Lifecycle Hooks
|
|
61
167
|
|
|
62
|
-
|
|
63
|
-
$ pnpm run start:dev
|
|
168
|
+
BetterAuth supports [hooks](https://www.better-auth.com/docs/concepts/hooks) that intercept authentication lifecycle events. Projects can extend the BetterAuth service via the Module Inheritance Pattern to react to these events:
|
|
64
169
|
|
|
65
|
-
|
|
66
|
-
|
|
170
|
+
```typescript
|
|
171
|
+
export class AuthService extends CoreBetterAuthService {
|
|
172
|
+
override async signIn(input, serviceOptions) {
|
|
173
|
+
const result = await super.signIn(input, serviceOptions);
|
|
174
|
+
// Trigger webhook after successful sign-in
|
|
175
|
+
await this.webhookService.dispatch('auth.sign-in', { userId: result.user.id });
|
|
176
|
+
return result;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
67
179
|
```
|
|
68
180
|
|
|
181
|
+
Authentication events that can be intercepted include: sign-in, sign-up, sign-out, session creation, token refresh, email verification, 2FA verification, and more.
|
|
69
182
|
|
|
70
|
-
|
|
183
|
+
### Module Inheritance for Custom Webhooks
|
|
71
184
|
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
185
|
+
Any service method can be extended to dispatch webhooks via the Module Inheritance Pattern:
|
|
186
|
+
|
|
187
|
+
```typescript
|
|
188
|
+
export class ProjectService extends CoreProjectService {
|
|
189
|
+
override async create(input, serviceOptions) {
|
|
190
|
+
const project = await super.create(input, serviceOptions);
|
|
191
|
+
// Dispatch webhook on project creation
|
|
192
|
+
await this.webhookService.dispatch('project.created', project);
|
|
193
|
+
return project;
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
```
|
|
197
|
+
|
|
198
|
+
This approach allows projects to add webhook dispatching to any CRUD operation or custom business logic without modifying the framework.
|
|
199
|
+
|
|
200
|
+
## Authentication
|
|
201
|
+
|
|
202
|
+
Two authentication systems are available — BetterAuth (recommended) and Legacy JWT auth:
|
|
203
|
+
|
|
204
|
+
### BetterAuth (IAM) — Recommended
|
|
205
|
+
|
|
206
|
+
Modern, session-based authentication with plugin architecture:
|
|
207
|
+
|
|
208
|
+
| Feature | Config |
|
|
209
|
+
|---------|--------|
|
|
210
|
+
| **JWT tokens** | `betterAuth.jwt: true` |
|
|
211
|
+
| **Two-Factor (2FA/TOTP)** | `betterAuth.twoFactor: true` |
|
|
212
|
+
| **Passkey/WebAuthn** | `betterAuth.passkey: true` |
|
|
213
|
+
| **Social login** (Google, GitHub, Apple, ...) | `betterAuth.socialProviders: { ... }` |
|
|
214
|
+
| **Email verification** | `betterAuth.emailVerification: { ... }` |
|
|
215
|
+
| **Rate limiting** | `betterAuth.rateLimit: { max: 10 }` |
|
|
216
|
+
| **Cross-subdomain cookies** | `betterAuth.crossSubDomainCookies: true` |
|
|
217
|
+
| **Disable sign-up** | `betterAuth.emailAndPassword.disableSignUp: true` |
|
|
218
|
+
|
|
219
|
+
Three integration patterns: zero-config, config-based, or manual (`autoRegister: false`).
|
|
220
|
+
|
|
221
|
+
BetterAuth provides **lifecycle hooks** for reacting to authentication events (sign-in, sign-up, session creation, etc.), enabling integration with external systems. See [Webhook Support](#webhook-support) for details.
|
|
222
|
+
|
|
223
|
+
### Legacy Auth (JWT)
|
|
224
|
+
|
|
225
|
+
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`.
|
|
226
|
+
|
|
227
|
+
A built-in **migration status query** (`betterAuthMigrationStatus`) tracks progress and indicates when legacy auth can be safely disabled.
|
|
228
|
+
|
|
229
|
+
## Roles & Permissions
|
|
230
|
+
|
|
231
|
+
The role and permission system is designed for extensibility — from simple role checks to complex multi-tenant hierarchies.
|
|
232
|
+
|
|
233
|
+
### System Roles (Runtime Checks)
|
|
234
|
+
|
|
235
|
+
System roles (`S_` prefix) are evaluated dynamically at runtime, never stored in the database:
|
|
236
|
+
|
|
237
|
+
| Role | Purpose |
|
|
238
|
+
|------|---------|
|
|
239
|
+
| `S_USER` | Any authenticated user |
|
|
240
|
+
| `S_VERIFIED` | Email-verified users |
|
|
241
|
+
| `S_CREATOR` | Creator of the resource |
|
|
242
|
+
| `S_SELF` | User accessing own data |
|
|
243
|
+
| `S_EVERYONE` | Public access |
|
|
244
|
+
| `S_NO_ONE` | Permanently locked |
|
|
245
|
+
|
|
246
|
+
### Method & Field-Level Authorization
|
|
247
|
+
|
|
248
|
+
```typescript
|
|
249
|
+
// Method-level: who can call this endpoint?
|
|
250
|
+
@Roles(RoleEnum.ADMIN)
|
|
251
|
+
async deleteUser() { ... }
|
|
252
|
+
|
|
253
|
+
// Field-level: who can see/modify this field?
|
|
254
|
+
@Restricted(RoleEnum.S_SELF, RoleEnum.ADMIN)
|
|
255
|
+
email: string;
|
|
256
|
+
|
|
257
|
+
// Membership-based: only team members can see this field
|
|
258
|
+
@Restricted({ memberOf: 'teamMembers' })
|
|
259
|
+
internalNotes: string;
|
|
260
|
+
```
|
|
75
261
|
|
|
76
|
-
|
|
77
|
-
$ pnpm run test:e2e
|
|
262
|
+
### Custom Role Hierarchies
|
|
78
263
|
|
|
79
|
-
|
|
80
|
-
|
|
264
|
+
Projects can define custom role hierarchies beyond the built-in roles:
|
|
265
|
+
|
|
266
|
+
```typescript
|
|
267
|
+
// Custom hierarchy with level-based comparison
|
|
268
|
+
const HR = createHierarchyRoles({ viewer: 1, editor: 2, admin: 3, owner: 4 });
|
|
269
|
+
|
|
270
|
+
@Roles(HR.EDITOR) // requires level >= 2 (editor, admin, or owner)
|
|
271
|
+
async editDocument() { ... }
|
|
81
272
|
```
|
|
82
273
|
|
|
83
|
-
|
|
274
|
+
### Resource-Level Security
|
|
275
|
+
|
|
276
|
+
Every model can override `securityCheck()` for fine-grained, context-aware access control:
|
|
277
|
+
|
|
278
|
+
```typescript
|
|
279
|
+
export class Project extends CorePersistenceModel {
|
|
280
|
+
override securityCheck(user: any, force?: boolean): this {
|
|
281
|
+
// Remove sensitive fields based on user context
|
|
282
|
+
if (!this.hasRole(user, RoleEnum.ADMIN)) {
|
|
283
|
+
this.budget = undefined;
|
|
284
|
+
}
|
|
285
|
+
return this;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
84
288
|
```
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
289
|
+
|
|
290
|
+
### Permissions Reporting
|
|
291
|
+
|
|
292
|
+
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.
|
|
293
|
+
|
|
294
|
+
## Multi-Tenancy
|
|
295
|
+
|
|
296
|
+
Full multi-tenancy support with header-based tenant isolation, membership management, and automatic data filtering:
|
|
297
|
+
|
|
298
|
+
```typescript
|
|
299
|
+
// config.env.ts
|
|
300
|
+
multiTenancy: {
|
|
301
|
+
headerName: 'x-tenant-id',
|
|
302
|
+
roleHierarchy: { member: 1, manager: 2, owner: 3 },
|
|
303
|
+
adminBypass: true,
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
// Usage in resolvers
|
|
307
|
+
@Roles(DefaultHR.MANAGER)
|
|
308
|
+
async updateProject(@CurrentTenant() tenantId: string) { ... }
|
|
88
309
|
```
|
|
89
310
|
|
|
90
|
-
|
|
311
|
+
- **Membership validation** — `CoreTenantGuard` validates membership on every request
|
|
312
|
+
- **Hierarchy roles** — Level comparison (higher includes lower), custom via `createHierarchyRoles()`
|
|
313
|
+
- **Automatic data isolation** — Mongoose tenant plugin filters all queries by tenant context
|
|
314
|
+
- **Defense-in-depth** — Guard validation + Mongoose plugin Safety Net combination
|
|
315
|
+
- **Multi-membership** — Users can belong to multiple tenants with different roles
|
|
316
|
+
- **Status management** — ACTIVE, INVITED, SUSPENDED membership states
|
|
317
|
+
- **`@SkipTenantCheck()`** — Opt out per endpoint
|
|
318
|
+
- **`@CurrentTenant()`** — Parameter decorator for validated tenant ID
|
|
319
|
+
|
|
320
|
+
## Scalability
|
|
321
|
+
|
|
322
|
+
The architecture is designed for horizontal scalability:
|
|
323
|
+
|
|
324
|
+
- **Stateless request handling** — No server-side session state required (JWT or BetterAuth sessions in MongoDB)
|
|
325
|
+
- **Request-scoped isolation** — `AsyncLocalStorage`-based `RequestContext` ensures safe concurrent request processing without shared mutable state
|
|
326
|
+
- **Distributed migration locking** — MongoDB-based distributed locks via `synchronizedMigration()` for safe multi-instance deployments
|
|
327
|
+
- **Query complexity analysis** — Configurable complexity limits prevent expensive GraphQL queries from consuming excessive resources
|
|
328
|
+
- **Connection pooling** — Managed transparently via Mongoose/MongoDB driver
|
|
329
|
+
- **Configurable MongoDB** — Support for replica sets and connection options via `mongoose.uri` configuration
|
|
91
330
|
|
|
92
|
-
|
|
331
|
+
## External System Integration
|
|
332
|
+
|
|
333
|
+
### Authentication Providers
|
|
334
|
+
|
|
335
|
+
BetterAuth supports OAuth integration with external identity providers (Google, GitHub, Apple, Discord, and more) via the `socialProviders` configuration.
|
|
336
|
+
|
|
337
|
+
### SCIM Support
|
|
338
|
+
|
|
339
|
+
Built-in [SCIM](http://www.simplecloud.info/) (System for Cross-domain Identity Management) filter parsing and MongoDB query conversion for enterprise identity management:
|
|
340
|
+
|
|
341
|
+
```typescript
|
|
342
|
+
scimToMongo('userName eq "Joe" and emails[type eq "work"]')
|
|
343
|
+
// → MongoDB: { $and: [{ userName: 'Joe' }, { emails: { $elemMatch: { type: 'work' } } }] }
|
|
93
344
|
```
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
345
|
+
|
|
346
|
+
Supported operators: `eq`, `co`, `sw`, `ew`, `gt`, `ge`, `lt`, `le`, `pr`, `aco`, `and`, `or`.
|
|
347
|
+
|
|
348
|
+
### Email Providers
|
|
349
|
+
|
|
350
|
+
Pluggable email provider abstraction with support for:
|
|
351
|
+
- **Mailjet** — API-based transactional email
|
|
352
|
+
- **Brevo** — API-based transactional email (formerly Sendinblue)
|
|
353
|
+
- **SMTP** — Standard SMTP via Nodemailer
|
|
354
|
+
|
|
355
|
+
All providers use the same `EmailService` interface with EJS template engine and locale-aware template resolution.
|
|
356
|
+
|
|
357
|
+
### Extensibility
|
|
358
|
+
|
|
359
|
+
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](#webhook-support).
|
|
360
|
+
|
|
361
|
+
## Core Modules
|
|
362
|
+
|
|
363
|
+
| Module | Purpose |
|
|
364
|
+
|--------|---------|
|
|
365
|
+
| **Auth** | Legacy JWT authentication with Passport strategies |
|
|
366
|
+
| **BetterAuth** | Modern auth (2FA, Passkey, Social, sessions, lifecycle hooks) |
|
|
367
|
+
| **User** | User management, profile, roles, verification |
|
|
368
|
+
| **Tenant** | Multi-tenancy with membership and hierarchy roles |
|
|
369
|
+
| **File** | File upload/download with MongoDB GridFS |
|
|
370
|
+
| **Tus** | Resumable file uploads via [tus.io](https://tus.io/) protocol (up to 50 GB) |
|
|
371
|
+
| **ErrorCode** | Centralized error codes with unique identifiers |
|
|
372
|
+
| **HealthCheck** | REST (`/health`) and GraphQL health monitoring |
|
|
373
|
+
| **Migrate** | MongoDB migration system with distributed locking for cluster deployments |
|
|
374
|
+
| **Permissions** | Security audit dashboard (HTML, JSON, Markdown reports) |
|
|
375
|
+
| **SystemSetup** | Initial admin creation for fresh deployments |
|
|
376
|
+
|
|
377
|
+
### CrudService
|
|
378
|
+
|
|
379
|
+
Abstract base service providing a complete CRUD pipeline with built-in security:
|
|
380
|
+
|
|
381
|
+
```typescript
|
|
382
|
+
export class ProjectService extends CrudService<Project> {
|
|
383
|
+
// Inherits: find, findOne, create, update, delete
|
|
384
|
+
// With: input validation, field selection, security checks, population
|
|
385
|
+
}
|
|
98
386
|
```
|
|
99
|
-
see [Debug.run.xml](.run/Debug.run.xml)
|
|
100
387
|
|
|
101
|
-
|
|
102
|
-
Via `pnpm link` the NestJS Server can be linked into the project.
|
|
388
|
+
**Advanced querying** with comparison operators (`eq`, `ne`, `gt`, `in`, `contains`, `regex`, ...), logical operators (`AND`, `OR`), pagination, sorting, and GraphQL-driven population.
|
|
103
389
|
|
|
104
|
-
|
|
105
|
-
Project use following scripts (via `package.json`):
|
|
390
|
+
### Security Layers
|
|
106
391
|
|
|
107
|
-
-
|
|
108
|
-
- `pnpm run unlink:nest-server` (for `pnpm unlink @lenne.tech/nest-server && pnpm install`)
|
|
392
|
+
Defense-in-depth security architecture with three layers:
|
|
109
393
|
|
|
110
|
-
|
|
394
|
+
**Layer 1: Guards & Middleware**
|
|
395
|
+
- `@Roles()` — Method-level authorization (includes JWT auth automatically)
|
|
396
|
+
- `@Restricted()` — Field-level access control with process type support (INPUT/OUTPUT)
|
|
397
|
+
- System roles — `S_USER`, `S_VERIFIED`, `S_CREATOR`, `S_SELF`, `S_EVERYONE`, `S_NO_ONE`
|
|
111
398
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
(
|
|
399
|
+
**Layer 2: CrudService Pipeline**
|
|
400
|
+
- Input validation — MapAndValidatePipe with whitelist checking
|
|
401
|
+
- `@UnifiedField()` — Single decorator for GraphQL, Swagger, and validation
|
|
402
|
+
- `securityCheck()` — Resource-level security on model instances
|
|
115
403
|
|
|
116
|
-
|
|
404
|
+
**Layer 3: Mongoose Plugins (Safety Net)**
|
|
405
|
+
- Password Plugin — Automatic BCrypt hashing
|
|
406
|
+
- Role Guard Plugin — Prevents unauthorized role escalation at DB level
|
|
407
|
+
- Audit Fields Plugin — Automatic `createdBy`/`updatedBy` tracking
|
|
408
|
+
- Tenant Isolation Plugin — Automatic tenant filtering
|
|
117
409
|
|
|
118
|
-
|
|
119
|
-
|
|
410
|
+
**Interceptor Chain:**
|
|
411
|
+
- ResponseModelInterceptor — Plain objects to CoreModel conversion
|
|
412
|
+
- TranslateResponseInterceptor — Multi-language support via `Accept-Language`
|
|
413
|
+
- CheckSecurityInterceptor — Executes `securityCheck()` and removes secret fields
|
|
414
|
+
- CheckResponseInterceptor — Enforces `@Restricted()` field-level filtering
|
|
415
|
+
|
|
416
|
+
### Email & Templates
|
|
417
|
+
|
|
418
|
+
Multi-provider email service (Mailjet, Brevo, SMTP) with EJS template engine and locale-aware template resolution.
|
|
419
|
+
|
|
420
|
+
## Configuration
|
|
120
421
|
|
|
121
|
-
|
|
122
|
-
1. Via "normal" integration of the environment variables into the `src/config.env.ts`
|
|
123
|
-
2. Via JSON in the `NEST_SERVER_CONFIG` environment variable
|
|
124
|
-
3. Via single environment variables with the prefix `NSC__` (Nest Server Config)
|
|
422
|
+
Configuration is managed via `src/config.env.ts` with environment-based profiles (development, local, production):
|
|
125
423
|
|
|
126
|
-
#### Normal environment variables
|
|
127
|
-
Using `dotenv` (see https://www.dotenv.org/) environment variables can directly integrated into the
|
|
128
|
-
`src/config.env.ts` via `process.env`. E.g.:
|
|
129
424
|
```typescript
|
|
130
425
|
export const config = {
|
|
131
|
-
|
|
132
|
-
port:
|
|
426
|
+
local: {
|
|
427
|
+
port: 3000,
|
|
428
|
+
graphQl: { driver: 'apollo' },
|
|
429
|
+
mongoose: { uri: 'mongodb://localhost/my-app' },
|
|
430
|
+
betterAuth: { secret: 'my-secret', jwt: true, twoFactor: true },
|
|
431
|
+
multiTenancy: { roleHierarchy: { member: 1, manager: 2, owner: 3 } },
|
|
133
432
|
},
|
|
134
433
|
};
|
|
135
434
|
```
|
|
136
435
|
|
|
137
|
-
|
|
138
|
-
The `NEST_SERVER_CONFIG` is the environment variable for the server configuration.
|
|
139
|
-
The value of `NEST_SERVER_CONFIG` must be a (multiline) JSON string that will be parsed by the server
|
|
140
|
-
(see config.env.ts). The keys will override the other configuration values via deep merge
|
|
141
|
-
(see https://lodash.com/docs/4.17.15#merge, without array merging).
|
|
436
|
+
### Configuration Patterns
|
|
142
437
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
variables. The key is the name of the configuration value in uppercase and with double underscores (`__`) instead of
|
|
146
|
-
dots. Single underscores are used to separate compound terms like `DEFAULT_SENDER` for `defaultSender`.
|
|
147
|
-
For example, the configuration value `email.defaultSender.name` can be set via the environment variable
|
|
148
|
-
`NSC__EMAIL_DEFAULT_SENDER_NAME`.
|
|
438
|
+
- **Presence implies enabled**: `rateLimit: {}` enables with defaults, `undefined` stays disabled
|
|
439
|
+
- **Boolean shorthand**: `jwt: true` enables with defaults, `{ expiresIn: '1h' }` customizes
|
|
149
440
|
|
|
150
|
-
|
|
151
|
-
|
|
441
|
+
### Environment Variables
|
|
442
|
+
|
|
443
|
+
Three methods to override configuration:
|
|
444
|
+
|
|
445
|
+
1. **Direct** — `process.env.PORT` in `config.env.ts`
|
|
446
|
+
2. **JSON** — `NEST_SERVER_CONFIG` environment variable (deep merge)
|
|
447
|
+
3. **Prefixed** — `NSC__EMAIL__DEFAULT_SENDER__NAME` for `email.defaultSender.name`
|
|
448
|
+
|
|
449
|
+
## Development
|
|
450
|
+
|
|
451
|
+
**Requirements:** Node.js >= 20, MongoDB, pnpm
|
|
452
|
+
|
|
453
|
+
```bash
|
|
454
|
+
# Install dependencies
|
|
455
|
+
pnpm install
|
|
456
|
+
|
|
457
|
+
# Start in development mode
|
|
458
|
+
pnpm run start:dev
|
|
459
|
+
|
|
460
|
+
# Run tests (Vitest E2E)
|
|
461
|
+
pnpm test
|
|
462
|
+
|
|
463
|
+
# Run tests with coverage
|
|
464
|
+
pnpm run vitest:cov
|
|
465
|
+
|
|
466
|
+
# Lint (oxlint) & format (oxfmt)
|
|
467
|
+
pnpm run lint
|
|
468
|
+
pnpm run format
|
|
469
|
+
|
|
470
|
+
# Build
|
|
471
|
+
pnpm run build
|
|
472
|
+
|
|
473
|
+
# Generate documentation
|
|
474
|
+
pnpm run docs
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### Debugging as package
|
|
478
|
+
|
|
479
|
+
Link into a consuming project for local development:
|
|
152
480
|
|
|
153
481
|
```bash
|
|
154
|
-
#
|
|
155
|
-
|
|
482
|
+
# In nest-server
|
|
483
|
+
pnpm run watch
|
|
484
|
+
|
|
485
|
+
# In your project
|
|
486
|
+
pnpm run link:nest-server # pnpm link /path/to/nest-server
|
|
487
|
+
pnpm run unlink:nest-server # pnpm unlink @lenne.tech/nest-server && pnpm install
|
|
156
488
|
```
|
|
157
489
|
|
|
490
|
+
### Versioning
|
|
491
|
+
|
|
492
|
+
`MAJOR.MINOR.PATCH` — MAJOR mirrors NestJS version, MINOR = breaking changes, PATCH = non-breaking improvements.
|
|
493
|
+
|
|
494
|
+
## Documentation
|
|
495
|
+
|
|
496
|
+
- [Request Lifecycle](docs/REQUEST-LIFECYCLE.md) — Complete request pipeline, security architecture, interceptor chain
|
|
497
|
+
- [Migration Guides](migration-guides/) — Version upgrade instructions
|
|
498
|
+
- [Starter Project](https://github.com/lenneTech/nest-server-starter) — Reference implementation
|
|
499
|
+
- [CLI](https://github.com/lenneTech/cli) — Code generation tools
|
|
500
|
+
- [NestJS Documentation](https://docs.nestjs.com/) — Framework docs
|
|
501
|
+
|
|
158
502
|
## Thanks
|
|
159
503
|
|
|
160
|
-
Many thanks to the developers of [
|
|
504
|
+
Many thanks to the developers of [NestJS](https://github.com/nestjs/nest)
|
|
161
505
|
and all the developers whose packages are used here.
|
|
162
506
|
|
|
163
507
|
## License
|
|
164
508
|
|
|
165
|
-
MIT - see LICENSE
|
|
509
|
+
MIT - see [LICENSE](/LICENSE)
|
|
@@ -426,13 +426,16 @@ function prepareServiceOptionsForCreate(serviceOptions) {
|
|
|
426
426
|
return serviceOptions;
|
|
427
427
|
}
|
|
428
428
|
function processDeep(data, func, options) {
|
|
429
|
-
const
|
|
430
|
-
processedObjects: new WeakMap(),
|
|
431
|
-
specialClasses: [],
|
|
432
|
-
specialFunctions: [],
|
|
433
|
-
specialProperties: [],
|
|
434
|
-
...options,
|
|
429
|
+
const resolvedOptions = {
|
|
430
|
+
processedObjects: options?.processedObjects ?? new WeakMap(),
|
|
431
|
+
specialClasses: options?.specialClasses ?? [],
|
|
432
|
+
specialFunctions: options?.specialFunctions ?? [],
|
|
433
|
+
specialProperties: options?.specialProperties ?? [],
|
|
435
434
|
};
|
|
435
|
+
return processDeepInternal(data, func, resolvedOptions);
|
|
436
|
+
}
|
|
437
|
+
function processDeepInternal(data, func, options) {
|
|
438
|
+
const { processedObjects, specialClasses, specialFunctions, specialProperties } = options;
|
|
436
439
|
if (!data) {
|
|
437
440
|
return func(data);
|
|
438
441
|
}
|
|
@@ -443,7 +446,7 @@ function processDeep(data, func, options) {
|
|
|
443
446
|
processedObjects.set(data, true);
|
|
444
447
|
}
|
|
445
448
|
if (Array.isArray(data)) {
|
|
446
|
-
return func(data.map((item) =>
|
|
449
|
+
return func(data.map((item) => processDeepInternal(item, func, options)));
|
|
447
450
|
}
|
|
448
451
|
if (typeof data === 'object') {
|
|
449
452
|
if (specialFunctions.find((sF) => typeof data[sF] === 'function') ||
|
|
@@ -457,7 +460,7 @@ function processDeep(data, func, options) {
|
|
|
457
460
|
}
|
|
458
461
|
}
|
|
459
462
|
for (const [key, value] of Object.entries(data)) {
|
|
460
|
-
data[key] =
|
|
463
|
+
data[key] = processDeepInternal(value, func, options);
|
|
461
464
|
}
|
|
462
465
|
return func(data);
|
|
463
466
|
}
|