An opinionated, secure, extensible Node.js/Express service framework built on Digital Defiance cryptography libraries, providing complete backend infrastructure for secure applications.
It is an 'out of the box' solution with a specific recipe (Mongo, Express, React, Node, (MERN) stack) with ejs templating, JWT authentication, role-based access control, custom multi-language support via @digitaldefiance/i18n-lib, and a dynamic model registry system. You might either find it limiting or freeing, depending on your use case. It includes mnemonic authentication, ECIES encryption/decryption, PBKDF2 key derivation, email token workflows, and more.
Part of Express Suite
✨ Comprehensive Decorator API for Express Controllers - A complete decorator-based API for defining controllers, routes, validation, and OpenAPI documentation with automatic spec generation.
New Decorators:
| Category | Decorators |
|---|---|
| Controller | @Controller, @ApiController |
| HTTP Methods | @Get, @Post, @Put, @Delete, @Patch (enhanced with OpenAPI support) |
| Authentication | @RequireAuth, @RequireCryptoAuth, @Public, @AuthFailureStatus |
| Parameters | @Param, @Body, @Query, @Header, @CurrentUser, @EciesUser, @Req, @Res, @Next |
| Validation | @ValidateBody, @ValidateParams, @ValidateQuery (Zod & express-validator) |
| Response | @Returns, @ResponseDoc, @RawJson, @Paginated |
| Middleware | @UseMiddleware, @CacheResponse, @RateLimit |
| Transaction | @Transactional |
| OpenAPI | @ApiOperation, @ApiTags, @ApiSummary, @ApiDescription, @Deprecated, @ApiOperationId, @ApiExample |
| OpenAPI Params | @ApiParam, @ApiQuery, @ApiHeader, @ApiRequestBody |
| Lifecycle | @OnSuccess, @OnError, @Before, @After |
| Schema | @ApiSchema, @ApiProperty |
Key Features:
- Full RouteConfig Parity: Every feature available in manual
RouteConfighas a decorator equivalent - Automatic OpenAPI Generation: Decorators automatically generate complete OpenAPI 3.0.3 specifications
- Zod Integration: Zod schemas are automatically converted to OpenAPI request body schemas
- Metadata Merging: Multiple decorators on the same method merge their OpenAPI metadata
- Class-Level Inheritance: Class-level decorators (auth, tags, middleware) apply to all methods unless overridden
- Parameter Injection: Clean, typed parameter injection with
@Param,@Body,@Query,@Header - Lifecycle Hooks:
@Before,@After,@OnSuccess,@OnErrorfor request lifecycle management
Documentation Middleware:
SwaggerUIMiddleware- Serve Swagger UI with customization optionsReDocMiddleware- Serve ReDoc documentationgenerateMarkdownDocs()- Generate markdown documentation from OpenAPI spec
Migration Support:
- Full backward compatibility with existing
RouteConfigapproach - Comprehensive migration guide in
docs/DECORATOR_MIGRATION.md - Mixed usage supported (decorated + manual routes in same controller)
✨ String Key Enum Registration & i18n v4 Integration - Upgraded to @digitaldefiance/i18n-lib v4.0.5 with branded enum translation support and translateStringKey() for automatic component ID resolution.
New Features:
- Direct String Key Translation: Use
translateStringKey()without specifying component IDs - Automatic Component Routing: Branded enums resolve to their registered components automatically
- Safe Translation:
safeTranslateStringKey()returns placeholder on failure instead of throwing
Dependencies:
@digitaldefiance/ecies-lib: 4.15.1 → 4.16.0@digitaldefiance/i18n-lib: 4.0.3 → 4.0.5@digitaldefiance/node-ecies-lib: 4.15.1 → 4.16.0@digitaldefiance/suite-core-lib: 3.9.1 → 3.10.0@noble/curves: 1.4.2 → 1.9.0@noble/hashes: 1.4.0 → 1.8.0
✨ Dependency Upgrades & API Alignment - Upgraded to @digitaldefiance/ecies-lib v4.13.0, @digitaldefiance/node-ecies-lib v4.13.0, and @digitaldefiance/suite-core-lib v3.7.0.
Breaking Changes:
- Encryption API renamed:
encryptSimpleOrSingle()→encryptBasic()/encryptWithLength() - Decryption API renamed:
decryptSimpleOrSingleWithHeader()→decryptBasicWithHeader()/decryptWithLengthAndHeader() - Configuration change:
registerNodeRuntimeConfiguration()now requires a key parameter - XorService behavior change: Now throws error when key is shorter than data (previously cycled the key)
- Removed constants:
OBJECT_ID_LENGTHremoved fromIConstants- useidProvider.byteLengthinstead
Interface Changes:
IConstantsnow extends bothINodeEciesConstantsandISuiteCoreConstants- Added
ECIES_CONFIGto configuration - Encryption mode constants renamed:
SIMPLE→BASIC,SINGLE→WITH_LENGTH
- 🔐 ECIES Encryption/Decryption: End-to-end encryption using elliptic curve cryptography
- 🔑 PBKDF2 Key Derivation: Secure password hashing with configurable profiles
- 👥 Role-Based Access Control (RBAC): Flexible permission system with user roles
- 🌍 Multi-Language i18n: Plugin-based internationalization with 8+ languages
- 📊 Dynamic Model Registry: Extensible document model system
- 🔧 Runtime Configuration: Override defaults at runtime for advanced use cases
- 🛡️ JWT Authentication: Secure token-based authentication
- 📧 Email Token System: Verification, password reset, and recovery workflows
- 💾 MongoDB Integration: Full database layer with Mongoose schemas
- 🧪 Comprehensive Testing: 100+ tests covering all major functionality
- 🏗️ Modern Architecture: Service container, fluent builders, plugin system
- ⚡ Simplified Generics: 87.5% reduction in type complexity
- 🔄 Automatic Transactions: Decorator-based transaction management
- 🎨 Fluent APIs: Validation, response, pipeline, and route builders
npm install @digitaldefiance/node-express-suite
# or
yarn add @digitaldefiance/node-express-suiteimport { Application, DatabaseInitializationService, emailServiceRegistry } from '@digitaldefiance/node-express-suite';
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
import { EmailService } from './services/email'; // Your concrete implementation
// Create application instance
const app = new Application({
port: 3000,
mongoUri: 'mongodb://localhost:27017/myapp',
jwtSecret: process.env.JWT_SECRET,
defaultLanguage: LanguageCodes.EN_US
});
// Configure email service (required before using middleware)
emailServiceRegistry.setService(new EmailService(app));
// Initialize database with default users and roles
const initResult = await DatabaseInitializationService.initUserDb(app);
// Start server
await app.start();
console.log(`Server running on port ${app.environment.port}`);import { JwtService, UserService } from '@digitaldefiance/node-express-suite';
// Create services
const jwtService = new JwtService(app);
const userService = new UserService(app);
// Sign in user
const user = await userService.findByUsername('alice');
const { token, roles } = await jwtService.signToken(user, app.environment.jwtSecret);
// Verify token
const tokenUser = await jwtService.verifyToken(token);
console.log(`User ${tokenUser.userId} authenticated with roles:`, tokenUser.roles);The package uses a dynamic model registration system for extensibility:
import { ModelRegistry } from '@digitaldefiance/node-express-suite';
// Register a custom model
ModelRegistry.instance.register({
modelName: 'Organization',
schema: organizationSchema,
model: OrganizationModel,
collection: 'organizations',
});
// Retrieve model anywhere in your app
const OrgModel = ModelRegistry.instance.get<IOrganizationDocument>('Organization').model;
// Use the model
const org = await OrgModel.findById(orgId);The framework includes these pre-registered models:
- User: User accounts with authentication
- Role: Permission roles for RBAC
- UserRole: User-to-role associations
- EmailToken: Email verification and recovery tokens
- Mnemonic: Encrypted mnemonic storage
- UsedDirectLoginToken: One-time login token tracking
All model functions support generic type parameters for custom model names and collections:
import { UserModel, EmailTokenModel } from '@digitaldefiance/node-express-suite';
// Use with default enums
const defaultUserModel = UserModel(connection);
// Use with custom model names and collections
const customUserModel = UserModel(
connection,
'CustomUser',
'custom_users'
);Clone and extend base schemas with additional fields:
import { EmailTokenSchema } from '@digitaldefiance/node-express-suite';
import { Schema } from 'mongoose';
// Clone and extend the schema
const ExtendedEmailTokenSchema = EmailTokenSchema.clone();
ExtendedEmailTokenSchema.add({
customField: { type: String, required: false },
metadata: { type: Schema.Types.Mixed, required: false },
});
// Use with custom model
const MyEmailTokenModel = connection.model(
'ExtendedEmailToken',
ExtendedEmailTokenSchema,
'extended_email_tokens'
);Create custom model functions that wrap extended schemas:
import { IEmailTokenDocument } from '@digitaldefiance/node-express-suite';
import { Connection, Model } from 'mongoose';
// Extend the document interface
interface IExtendedEmailTokenDocument extends IEmailTokenDocument {
customField?: string;
metadata?: any;
}
// Create extended schema (as shown above)
const ExtendedEmailTokenSchema = EmailTokenSchema.clone();
ExtendedEmailTokenSchema.add({
customField: { type: String },
metadata: { type: Schema.Types.Mixed },
});
// Create custom model function
export function ExtendedEmailTokenModel<
TModelName extends string = 'ExtendedEmailToken',
TCollection extends string = 'extended_email_tokens'
>(
connection: Connection,
modelName: TModelName = 'ExtendedEmailToken' as TModelName,
collection: TCollection = 'extended_email_tokens' as TCollection,
): Model<IExtendedEmailTokenDocument> {
return connection.model<IExtendedEmailTokenDocument>(
modelName,
ExtendedEmailTokenSchema,
collection,
);
}
// Use the extended model
const model = ExtendedEmailTokenModel(connection);
const token = await model.create({
userId,
type: EmailTokenType.AccountVerification,
token: 'abc123',
email: 'user@example.com',
customField: 'custom value',
metadata: { source: 'api' },
});Extend the base enumerations for your application:
import { BaseModelName, SchemaCollection } from '@digitaldefiance/node-express-suite';
// Extend base enums
enum MyModelName {
User = BaseModelName.User,
Role = BaseModelName.Role,
Organization = 'Organization',
Project = 'Project',
}
enum MyCollection {
User = SchemaCollection.User,
Role = SchemaCollection.Role,
Organization = 'organizations',
Project = 'projects',
}
// Use with model functions
const orgModel = UserModel<MyModelName, MyCollection>(
connection,
MyModelName.Organization,
MyCollection.Organization
);Combining schemas, documents, and model functions:
import { IUserDocument, UserSchema } from '@digitaldefiance/node-express-suite';
import { Connection, Model, Schema } from 'mongoose';
// 1. Extend document interface
interface IOrganizationUserDocument extends IUserDocument {
organizationId: string;
department?: string;
}
// 2. Extend schema
const OrganizationUserSchema = UserSchema.clone();
OrganizationUserSchema.add({
organizationId: { type: String, required: true },
department: { type: String },
});
// 3. Create model function
export function OrganizationUserModel(
connection: Connection,
): Model<IOrganizationUserDocument> {
return connection.model<IOrganizationUserDocument>(
'OrganizationUser',
OrganizationUserSchema,
'organization_users',
);
}
// 4. Use in application
const model = OrganizationUserModel(connection);
const user = await model.create({
username: 'alice',
email: 'alice@example.com',
organizationId: 'org-123',
department: 'Engineering',
});Encryption and key management:
import { ECIESService } from '@digitaldefiance/node-express-suite';
const eciesService = new ECIESService();
// Generate mnemonic
const mnemonic = eciesService.generateNewMnemonic();
// Encrypt data
const encrypted = await eciesService.encryptWithLength(
recipientPublicKey,
Buffer.from('secret message')
);
// Decrypt data
const decrypted = await eciesService.decryptWithLengthAndHeader(
privateKey,
encrypted
);Secure key storage and retrieval:
import { KeyWrappingService } from '@digitaldefiance/node-express-suite';
const keyWrapping = new KeyWrappingService(app);
// Wrap a key with password
const wrapped = await keyWrapping.wrapKey(
privateKey,
password,
salt
);
// Unwrap key
const unwrapped = await keyWrapping.unwrapKey(
wrapped,
password,
salt
);Role and permission management:
import { RoleService } from '@digitaldefiance/node-express-suite';
const roleService = new RoleService(app);
// Get user roles
const roles = await roleService.getUserRoles(userId);
// Check permissions
const hasPermission = await roleService.userHasRole(userId, 'admin');
// Create role
const adminRole = await roleService.createRole({
name: 'admin',
description: 'Administrator role',
permissions: ['read', 'write', 'delete']
});Backup code generation and validation:
import { BackupCodeService } from '@digitaldefiance/node-express-suite';
const backupCodeService = new BackupCodeService(app);
// Generate backup codes
const codes = await backupCodeService.generateBackupCodes(userId);
// Validate code
const isValid = await backupCodeService.validateBackupCode(userId, userCode);
// Mark code as used
await backupCodeService.useBackupCode(userId, userCode);Initialize database with default users and roles:
import { DatabaseInitializationService } from '@digitaldefiance/node-express-suite';
// Initialize with default admin, member, and system users
const result = await DatabaseInitializationService.initUserDb(app);
if (result.success) {
console.log('Admin user:', result.data.adminUsername);
console.log('Admin password:', result.data.adminPassword);
console.log('Admin mnemonic:', result.data.adminMnemonic);
console.log('Backup codes:', result.data.adminBackupCodes);
}Before using middleware that requires email functionality, configure the email service:
import { emailServiceRegistry, IEmailService } from '@digitaldefiance/node-express-suite';
// Implement the IEmailService interface
class MyEmailService implements IEmailService {
async sendEmail(to: string, subject: string, text: string, html: string): Promise<void> {
// Your email implementation (AWS SES, SendGrid, etc.)
}
}
// Register at application startup
emailServiceRegistry.setService(new MyEmailService());import { authMiddleware } from '@digitaldefiance/node-express-suite';
// Protect routes with JWT authentication
app.get('/api/protected', authMiddleware, (req, res) => {
// req.user contains authenticated user info
res.json({ user: req.user });
});import { requireRole } from '@digitaldefiance/node-express-suite';
// Require specific role
app.delete('/api/users/:id',
authMiddleware,
requireRole('admin'),
async (req, res) => {
// Only admins can access this route
await userService.deleteUser(req.params.id);
res.json({ success: true });
}
);Override defaults at runtime for advanced use cases:
import {
getExpressRuntimeConfiguration,
registerExpressRuntimeConfiguration,
} from '@digitaldefiance/node-express-suite';
// Get current configuration
const config = getExpressRuntimeConfiguration();
console.log('Bcrypt rounds:', config.BcryptRounds);
// Register custom configuration
const customKey = Symbol('custom-express-config');
registerExpressRuntimeConfiguration(customKey, {
BcryptRounds: 12,
JWT: {
ALGORITHM: 'HS512',
EXPIRATION_SEC: 7200
}
});
// Use custom configuration
const customConfig = getExpressRuntimeConfiguration(customKey);interface IExpressRuntimeConfiguration {
BcryptRounds: number;
JWT: {
ALGORITHM: string;
EXPIRATION_SEC: number;
};
BACKUP_CODES: {
Count: number;
Length: number;
};
// ... more options
}Built-in support for multiple languages using the plugin-based i18n architecture:
import { getGlobalI18nEngine, translateExpressSuite } from '@digitaldefiance/node-express-suite';
import { LanguageCodes } from '@digitaldefiance/i18n-lib';
// Get the global i18n engine
const i18n = getGlobalI18nEngine();
// Translate strings using branded string keys (v3.11.0+)
// Component ID is automatically resolved from the branded enum
const message = translateExpressSuite(
ExpressSuiteStringKey.Common_Ready,
{},
LanguageCodes.FR
);
// "Prêt"
// Change language globally
i18n.setLanguage(LanguageCodes.ES);Starting with v3.11.0, the library uses translateStringKey() internally for automatic component ID resolution from branded enums. This means you can also use the engine directly:
import { getGlobalI18nEngine } from '@digitaldefiance/node-express-suite';
import { ExpressSuiteStringKey } from '@digitaldefiance/node-express-suite';
const engine = getGlobalI18nEngine();
// Direct translation - component ID resolved automatically
const text = engine.translateStringKey(ExpressSuiteStringKey.Common_Ready);
// Safe version returns placeholder on failure
const safe = engine.safeTranslateStringKey(ExpressSuiteStringKey.Common_Ready);- English (US)
- Spanish
- French
- Mandarin Chinese
- Japanese
- German
- Ukrainian
Comprehensive error types with localization:
import {
TranslatableError,
InvalidJwtTokenError,
TokenExpiredError,
UserNotFoundError
} from '@digitaldefiance/node-express-suite';
try {
const user = await userService.findByEmail(email);
} catch (error) {
if (error instanceof UserNotFoundError) {
// Handle user not found
res.status(404).json({
error: error.message // Automatically localized
});
} else if (error instanceof TranslatableError) {
// Handle other translatable errors
res.status(400).json({ error: error.message });
}
}Comprehensive test suite with 2541 passing tests:
# Run all tests
npm test
# Run specific test suites
npm test -- database-initialization.spec.ts
npm test -- jwt.spec.ts
npm test -- role.spec.ts
# Run with coverage
npm test -- --coverageTest helpers and mocks are available via the /testing entry point:
// Import test utilities
import {
mockFunctions,
setupTestEnv,
// ... other test helpers
} from '@digitaldefiance/node-express-suite/testing';
// Use in your tests
beforeAll(async () => {
await setupTestEnv();
});Note: The /testing entry point requires @faker-js/faker as a peer dependency. Install it in your dev dependencies:
npm install -D @faker-js/faker
# or
yarn add -D @faker-js/faker- 2541 tests passing (100% success rate)
- 57.86% overall coverage
- 11 modules at 100% coverage
- All critical paths tested (validation, auth, services)
The package supports automated TLS certificate management via Let's Encrypt using greenlock-express. When enabled, the Application automatically obtains and renews certificates, serves your app over HTTPS on port 443, and redirects HTTP traffic from port 80 to HTTPS.
| Variable | Type | Default | Description |
|---|---|---|---|
LETS_ENCRYPT_ENABLED |
boolean |
false |
Set to true or 1 to enable Let's Encrypt certificate management |
LETS_ENCRYPT_EMAIL |
string |
(required when enabled) | Contact email for your Let's Encrypt account (used for expiry notices and account recovery) |
LETS_ENCRYPT_HOSTNAMES |
string |
(required when enabled) | Comma-separated list of hostnames to obtain certificates for (supports wildcards, e.g. *.example.com) |
LETS_ENCRYPT_STAGING |
boolean |
false |
Set to true or 1 to use the Let's Encrypt staging directory (recommended for testing) |
LETS_ENCRYPT_CONFIG_DIR |
string |
./greenlock.d |
Directory for Greenlock configuration and certificate storage |
LETS_ENCRYPT_ENABLED=true
LETS_ENCRYPT_EMAIL=admin@example.com
LETS_ENCRYPT_HOSTNAMES=example.com
LETS_ENCRYPT_STAGING=falseLETS_ENCRYPT_ENABLED=true
LETS_ENCRYPT_EMAIL=admin@example.com
LETS_ENCRYPT_HOSTNAMES=example.com,*.example.com,api.example.com
LETS_ENCRYPT_STAGING=falseWildcard hostnames (e.g. *.example.com) require DNS-01 challenge validation. Ensure your DNS provider supports programmatic record creation or configure the appropriate Greenlock plugin.
When Let's Encrypt is enabled, the Application binds to three ports:
- Port 443 — HTTPS server with the auto-managed TLS certificate
- Port 80 — HTTP redirect server (301 redirects all traffic to HTTPS, also serves ACME HTTP-01 challenges)
environment.port(default 3000) — Primary HTTP server for internal or health-check traffic
Ports 80 and 443 are privileged ports on most systems. You may need elevated permissions to bind to them:
- Linux: Use
setcapto grant the Node.js binary the capability without running as root:sudo setcap 'cap_net_bind_service=+ep' $(which node)
- Docker: Map the ports in your container configuration (containers typically run as root internally)
- Reverse proxy: Alternatively, run behind a reverse proxy (e.g. nginx) that handles ports 80/443 and forwards to your app
Let's Encrypt mode and the dev-certificate HTTPS mode (HTTPS_DEV_CERT_ROOT) are mutually exclusive at runtime. When LETS_ENCRYPT_ENABLED=true, the dev-certificate HTTPS block is automatically skipped to avoid port conflicts. You do not need to unset HTTPS_DEV_CERT_ROOT — the Application handles this internally.
- Production: Use
LETS_ENCRYPT_ENABLED=truefor real certificates - Development: Use
HTTPS_DEV_CERT_ROOTfor self-signed dev certificates - Never enable both simultaneously — Let's Encrypt takes precedence when enabled
-
Always use environment variables for sensitive configuration:
const app = new Application({ jwtSecret: process.env.JWT_SECRET, mongoUri: process.env.MONGO_URI, });
-
Validate all user input before processing:
import { EmailString } from '@digitaldefiance/ecies-lib'; try { const email = new EmailString(userInput); // Email is validated } catch (error) { // Invalid email format }
-
Use secure password hashing with appropriate bcrypt rounds:
const config = getExpressRuntimeConfiguration(); const hashedPassword = await bcrypt.hash(password, config.BcryptRounds);
-
Use async operations to avoid blocking:
const [user, roles] = await Promise.all([ userService.findById(userId), roleService.getUserRoles(userId) ]);
-
Implement caching for frequently accessed data:
const cachedRoles = await cache.get(`user:${userId}:roles`); if (!cachedRoles) { const roles = await roleService.getUserRoles(userId); await cache.set(`user:${userId}:roles`, roles, 3600); }
-
Use database indexes for common queries:
userSchema.index({ email: 1 }, { unique: true }); userSchema.index({ username: 1 }, { unique: true });
new Application(config)- Create application instancestart()- Start the Express serverstop()- Stop the server gracefullyenvironment- Access configuration
ECIESService- Encryption and key managementKeyWrappingService- Secure key storageJwtService- JWT token operationsRoleService- Role and permission managementUserService- User account operationsBackupCodeService- Backup code managementMnemonicService- Mnemonic storage and retrievalSystemUserService- System user operationsDatabaseInitializationService- Database initialization with default users and rolesDirectLoginTokenService- One-time login token managementRequestUserService- Extract user from request contextChecksumService- CRC checksum operationsSymmetricService- Symmetric encryption operationsXorService- XOR cipher operationsFecService- Forward error correctionDummyEmailService- Test email service implementation
ModelRegistry- Dynamic model registrationdebugLog()- Conditional logging utilitywithTransaction()- MongoDB transaction wrapper
The node-express-suite package uses comprehensive testing with 604 tests covering all services, middleware, controllers, and database operations.
Test Framework: Jest with TypeScript support
Property-Based Testing: fast-check for validation properties
Coverage: 57.86% overall, 100% on critical paths
Database: MongoDB Memory Server for isolated testing
tests/
├── unit/ # Unit tests for services and utilities
├── integration/ # Integration tests for multi-service flows
├── e2e/ # End-to-end API tests
├── middleware/ # Middleware tests
└── fixtures/ # Test data and mocks
# Run all tests
npm test
# Run with coverage
npm test -- --coverage
# Run specific test suite
npm test -- user-service.spec.ts
# Run in watch mode
npm test -- --watchimport { UserService, Application } from '@digitaldefiance/node-express-suite';
describe('UserService', () => {
let app: Application;
let userService: UserService;
beforeAll(async () => {
app = new Application({
mongoUri: 'mongodb://localhost:27017/test',
jwtSecret: 'test-secret'
});
await app.start();
userService = new UserService(app);
});
afterAll(async () => {
await app.stop();
});
it('should create user', async () => {
const user = await userService.create({
username: 'alice',
email: 'alice@example.com',
password: 'SecurePass123!'
});
expect(user.username).toBe('alice');
});
});import { authMiddleware } from '@digitaldefiance/node-express-suite';
import { Request, Response, NextFunction } from 'express';
describe('Auth Middleware', () => {
it('should reject requests without token', async () => {
const req = { headers: {} } as Request;
const res = {
status: jest.fn().mockReturnThis(),
json: jest.fn()
} as unknown as Response;
const next = jest.fn() as NextFunction;
await authMiddleware(req, res, next);
expect(res.status).toHaveBeenCalledWith(401);
expect(next).not.toHaveBeenCalled();
});
});import { UserController } from '@digitaldefiance/node-express-suite';
describe('UserController', () => {
it('should register new user', async () => {
const controller = new UserController(app);
const req = {
body: {
username: 'alice',
email: 'alice@example.com',
password: 'SecurePass123!'
}
} as Request;
const result = await controller.register(req, res, next);
expect(result.statusCode).toBe(201);
expect(result.response.data.user).toBeDefined();
});
});import { connectMemoryDB, disconnectMemoryDB, clearMemoryDB } from '@digitaldefiance/express-suite-test-utils';
import { UserModel } from '@digitaldefiance/node-express-suite';
describe('User Model', () => {
beforeAll(async () => {
await connectMemoryDB();
});
afterAll(async () => {
await disconnectMemoryDB();
});
afterEach(async () => {
await clearMemoryDB();
});
it('should validate user schema', async () => {
const User = UserModel(connection);
const user = new User({
username: 'alice',
email: 'alice@example.com'
});
await expect(user.validate()).resolves.not.toThrow();
});
});- Use MongoDB Memory Server for isolated database testing
- Test with transactions to ensure data consistency
- Mock external services like email providers
- Test error conditions and edge cases
- Test middleware in isolation and integration
- Test authentication and authorization flows
Testing integration with other Express Suite packages:
import { Application } from '@digitaldefiance/node-express-suite';
import { ECIESService } from '@digitaldefiance/node-ecies-lib';
import { IBackendUser } from '@digitaldefiance/suite-core-lib';
describe('Cross-Package Integration', () => {
it('should integrate ECIES with user management', async () => {
const app = new Application({ /* config */ });
const ecies = new ECIESService();
// Create user with encrypted data
const user = await app.services.get(ServiceKeys.USER).create({
username: 'alice',
email: 'alice@example.com',
// ... encrypted fields
});
expect(user).toBeDefined();
});
});The decorator API provides a declarative, type-safe approach to building Express APIs with automatic OpenAPI documentation generation. Decorators eliminate boilerplate while maintaining full feature parity with manual RouteConfig.
| Category | Decorators | Purpose |
|---|---|---|
| Controller | @Controller, @ApiController |
Define controller base path and OpenAPI metadata |
| HTTP Methods | @Get, @Post, @Put, @Delete, @Patch |
Define route handlers with OpenAPI support |
| Authentication | @RequireAuth, @RequireCryptoAuth, @Public, @AuthFailureStatus |
Control authentication requirements |
| Parameters | @Param, @Body, @Query, @Header, @CurrentUser, @EciesUser, @Req, @Res, @Next |
Inject request data into handler parameters |
| Validation | @ValidateBody, @ValidateParams, @ValidateQuery |
Validate request data with Zod or express-validator |
| Response | @Returns, @ResponseDoc, @RawJson, @Paginated |
Document response types for OpenAPI |
| Middleware | @UseMiddleware, @CacheResponse, @RateLimit |
Attach middleware to routes |
| Transaction | @Transactional |
Wrap handlers in MongoDB transactions |
| OpenAPI | @ApiOperation, @ApiTags, @ApiSummary, @ApiDescription, @Deprecated, @ApiOperationId, @ApiExample |
Add OpenAPI documentation |
| OpenAPI Params | @ApiParam, @ApiQuery, @ApiHeader, @ApiRequestBody |
Document parameters with full OpenAPI metadata |
| Lifecycle | @OnSuccess, @OnError, @Before, @After |
Hook into request lifecycle events |
| Handler Args | @HandlerArgs |
Pass additional arguments to handlers |
| Schema | @ApiSchema, @ApiProperty |
Register OpenAPI schemas from classes |
import {
ApiController,
Get,
Post,
Put,
Delete,
RequireAuth,
Public,
Param,
Body,
Query,
ValidateBody,
Returns,
ApiTags,
Transactional,
DecoratorBaseController,
} from '@digitaldefiance/node-express-suite';
import { z } from 'zod';
// Define validation schema
const CreateUserSchema = z.object({
name: z.string().min(1).max(100),
email: z.string().email(),
role: z.enum(['admin', 'user']).optional(),
});
@RequireAuth() // All routes require authentication by default
@ApiTags('Users')
@ApiController('/api/users', {
description: 'User management endpoints',
})
class UserController extends DecoratorBaseController {
@Public() // Override class-level auth - this route is public
@Returns(200, 'User[]', { description: 'List of users' })
@Get('/')
async listUsers(
@Query('page', { schema: { type: 'integer' } }) page: number = 1,
@Query('limit') limit: number = 20,
) {
return this.userService.findAll({ page, limit });
}
@Returns(200, 'User', { description: 'User details' })
@Returns(404, 'ErrorResponse', { description: 'User not found' })
@Get('/:id')
async getUser(@Param('id', { description: 'User ID' }) id: string) {
return this.userService.findById(id);
}
@ValidateBody(CreateUserSchema)
@Transactional()
@Returns(201, 'User', { description: 'Created user' })
@Post('/')
async createUser(@Body() data: z.infer<typeof CreateUserSchema>) {
return this.userService.create(data);
}
@Transactional()
@Returns(200, 'User', { description: 'Updated user' })
@Put('/:id')
async updateUser(
@Param('id') id: string,
@Body() data: Partial<z.infer<typeof CreateUserSchema>>,
) {
return this.userService.update(id, data);
}
@Transactional()
@Returns(204, undefined, { description: 'User deleted' })
@Delete('/:id')
async deleteUser(@Param('id') id: string) {
await this.userService.delete(id);
}
}// Basic controller (no OpenAPI metadata)
@Controller('/api/items')
class ItemController {}
// OpenAPI-enabled controller with metadata
@ApiController('/api/users', {
tags: ['Users', 'Admin'],
description: 'User management endpoints',
deprecated: false,
name: 'UserController', // Optional, defaults to class name
})
class UserController extends DecoratorBaseController {}All HTTP method decorators support inline OpenAPI options:
@Get('/users/:id', {
summary: 'Get user by ID',
description: 'Retrieves a user by their unique identifier',
tags: ['Users'],
operationId: 'getUserById',
deprecated: false,
auth: true, // Shorthand for @RequireAuth()
cryptoAuth: false, // Shorthand for @RequireCryptoAuth()
rawJson: false, // Shorthand for @RawJson()
transaction: false, // Shorthand for @Transactional()
middleware: [], // Express middleware array
validation: [], // express-validator chains
schema: zodSchema, // Zod schema for body validation
})
async getUser() {}// Require JWT authentication
@RequireAuth()
@ApiController('/api/secure')
class SecureController {
@Get('/data')
getData() {} // Requires auth (inherited from class)
@Public()
@Get('/public')
getPublic() {} // No auth required (overrides class-level)
}
// Require ECIES crypto authentication
@RequireCryptoAuth()
@Post('/encrypted')
async createEncrypted() {}
// Custom auth failure status code
@AuthFailureStatus(403)
@Get('/admin')
getAdmin() {} // Returns 403 instead of 401 on auth failure@Get('/:id')
async getUser(
// Path parameter with OpenAPI documentation
@Param('id', { description: 'User ID', schema: { type: 'string', format: 'uuid' } }) id: string,
// Query parameters
@Query('include', { description: 'Fields to include' }) include?: string,
// Header value
@Header('X-Request-ID') requestId?: string,
// Authenticated user from JWT
@CurrentUser() user: AuthenticatedUser,
// ECIES authenticated member
@EciesUser() member: EciesMember,
// Raw Express objects (use sparingly)
@Req() req: Request,
@Res() res: Response,
@Next() next: NextFunction,
) {}
@Post('/')
async createUser(
// Entire request body
@Body() data: CreateUserDto,
// Specific field from body
@Body('email') email: string,
) {}// Zod schema validation
const CreateUserSchema = z.object({
name: z.string().min(1),
email: z.string().email(),
});
@ValidateBody(CreateUserSchema)
@Post('/')
async createUser(@Body() data: z.infer<typeof CreateUserSchema>) {}
// express-validator chains
@ValidateBody([
body('name').isString().notEmpty(),
body('email').isEmail(),
])
@Post('/')
async createUser() {}
// Language-aware validation with constants
@ValidateBody(function(lang) {
return [
body('username')
.matches(this.constants.UsernameRegex)
.withMessage(getTranslation(lang, 'invalidUsername')),
];
})
@Post('/')
async createUser() {}
// Validate path parameters
@ValidateParams(z.object({ id: z.string().uuid() }))
@Get('/:id')
async getUser() {}
// Validate query parameters
@ValidateQuery(z.object({
page: z.coerce.number().int().positive().optional(),
limit: z.coerce.number().int().max(100).optional(),
}))
@Get('/')
async listUsers() {}// Document response types (stackable for multiple status codes)
@Returns(200, 'User', { description: 'User found' })
@Returns(404, 'ErrorResponse', { description: 'User not found' })
@Get('/:id')
async getUser() {}
// Inline schema for simple responses
@ResponseDoc(200, {
description: 'Health check response',
schema: {
type: 'object',
properties: {
status: { type: 'string' },
timestamp: { type: 'string', format: 'date-time' },
},
},
})
@Get('/health')
healthCheck() {}
// Raw JSON response (bypasses response wrapper)
@RawJson()
@Get('/raw')
getRawData() {}
// Paginated endpoint (adds page/limit query params to OpenAPI)
@Paginated({ defaultPageSize: 20, maxPageSize: 100 })
@Returns(200, 'User[]')
@Get('/')
async listUsers() {}
// Offset-based pagination
@Paginated({ useOffset: true, defaultPageSize: 20 })
@Get('/items')
async listItems() {}// Attach middleware (class or method level)
@UseMiddleware(loggerMiddleware)
@ApiController('/api/data')
class DataController {
@UseMiddleware([validateMiddleware, sanitizeMiddleware])
@Post('/')
createData() {}
}
// Response caching
@CacheResponse({ ttl: 60 }) // Cache for 60 seconds
@Get('/static')
getStaticData() {}
@CacheResponse({
ttl: 300,
varyByUser: true, // Different cache per user
varyByQuery: ['page'], // Different cache per query param
keyPrefix: 'users', // Custom cache key prefix
})
@Get('/user-data')
getUserData() {}
// Rate limiting (auto-adds 429 response to OpenAPI)
@RateLimit({ requests: 5, window: 60 }) // 5 requests per minute
@Post('/login')
login() {}
@RateLimit({
requests: 100,
window: 3600,
byUser: true, // Limit per user instead of IP
message: 'Hourly limit exceeded',
keyGenerator: (req) => req.ip, // Custom key generator
})
@Get('/api-data')
getApiData() {}// Basic transaction
@Transactional()
@Post('/')
async createOrder() {
// this.session available automatically in DecoratorBaseController
await this.orderService.create(data, this.session);
}
// Transaction with timeout
@Transactional({ timeout: 30000 }) // 30 second timeout
@Post('/bulk')
async bulkCreate() {}// Full operation metadata
@ApiOperation({
summary: 'Get user by ID',
description: 'Retrieves a user by their unique identifier',
tags: ['Users'],
operationId: 'getUserById',
deprecated: false,
})
@Get('/:id')
getUser() {}
// Individual decorators (composable)
@ApiSummary('Get user by ID')
@ApiDescription('Retrieves a user by their unique identifier')
@ApiTags('Users', 'Public')
@ApiOperationId('getUserById')
@Deprecated()
@Get('/:id')
getUser() {}
// Class-level tags apply to all methods
@ApiTags('Users')
@ApiController('/api/users')
class UserController {
@ApiTags('Admin') // Adds to class tags: ['Users', 'Admin']
@Get('/admin')
adminEndpoint() {}
}
// Add examples
@ApiExample({
name: 'validUser',
summary: 'A valid user response',
value: { id: '123', name: 'John Doe', email: 'john@example.com' },
type: 'response',
statusCode: 200,
})
@Get('/:id')
getUser() {}// Document path parameter with full metadata
@ApiParam('id', {
description: 'User ID',
schema: { type: 'string', format: 'uuid' },
example: '123e4567-e89b-12d3-a456-426614174000',
})
@Get('/:id')
getUser(@Param('id') id: string) {}
// Document query parameters
@ApiQuery('page', {
description: 'Page number',
schema: { type: 'integer', minimum: 1 },
required: false,
example: 1,
})
@ApiQuery('sort', {
description: 'Sort field',
enum: ['name', 'date', 'id'],
})
@Get('/')
listUsers() {}
// Document headers
@ApiHeader('X-Request-ID', {
description: 'Request tracking ID',
schema: { type: 'string', format: 'uuid' },
required: true,
})
@Get('/')
getData() {}
// Document request body
@ApiRequestBody({
schema: 'CreateUserDto', // Reference to registered schema
description: 'User data to create',
required: true,
example: { name: 'John', email: 'john@example.com' },
})
@Post('/')
createUser() {}
// Or with Zod schema
@ApiRequestBody({
schema: CreateUserSchema, // Zod schema
description: 'User data',
})
@Post('/')
createUser() {}// Execute after successful response
@OnSuccess(({ req, result }) => {
console.log(`User ${req.params.id} fetched:`, result);
})
@Get('/:id')
getUser() {}
// Execute on error
@OnError(({ req, error }) => {
logger.error(`Error on ${req.path}:`, error);
})
@Get('/:id')
getUser() {}
// Execute before handler
@Before(({ req }) => {
console.log(`Incoming request to ${req.path}`);
})
@Get('/')
listUsers() {}
// Execute after handler (success or error)
@After(({ req, result, error }) => {
metrics.recordRequest(req.path, error ? 'error' : 'success');
})
@Get('/')
listUsers() {}
// Class-level hooks apply to all methods
@OnError(({ error }) => logger.error(error))
@ApiController('/api/users')
class UserController {}// Pass additional arguments to handler
@HandlerArgs({ maxItems: 100 })
@Get('/')
listItems(req: Request, config: { maxItems: number }) {
// config.maxItems === 100
}
// Multiple arguments
@HandlerArgs('prefix', 42, { option: true })
@Post('/')
createItem(req: Request, prefix: string, count: number, options: object) {}// Register class as OpenAPI schema
@ApiSchema({ description: 'User entity' })
class User {
@ApiProperty({
type: 'string',
format: 'uuid',
description: 'Unique identifier',
example: '123e4567-e89b-12d3-a456-426614174000',
})
id: string;
@ApiProperty({
type: 'string',
format: 'email',
required: true,
})
email: string;
@ApiProperty({
type: 'integer',
minimum: 0,
maximum: 150,
})
age?: number;
@ApiProperty({
type: 'array',
items: 'Role', // Reference to another schema
})
roles: Role[];
}
// Inheritance is supported
@ApiSchema()
class AdminUser extends User {
@ApiProperty({ type: 'string' })
adminLevel: string;
}Decorators can be freely composed and stacked. Order matters for some decorators:
// Middleware executes top to bottom
@UseMiddleware(first)
@UseMiddleware(second)
@UseMiddleware(third)
@Get('/')
handler() {} // Executes: first → second → third → handler
// Multiple @Returns accumulate (don't replace)
@Returns(200, 'User')
@Returns(400, 'ValidationError')
@Returns(404, 'NotFoundError')
@Returns(500, 'ServerError')
@Get('/:id')
getUser() {}
// Tags merge (class + method)
@ApiTags('Users')
@ApiController('/api/users')
class UserController {
@ApiTags('Admin')
@Get('/admin')
admin() {} // Tags: ['Users', 'Admin']
}
// Method-level overrides class-level for same field
@RequireAuth()
@ApiController('/api/data')
class DataController {
@Get('/private')
private() {} // Requires auth (inherited)
@Public()
@Get('/public')
public() {} // No auth (overridden)
}| RouteConfig Field | Decorator Equivalent |
|---|---|
method |
@Get, @Post, @Put, @Delete, @Patch |
path |
Decorator path argument |
handlerKey |
Decorated method name (automatic) |
handlerArgs |
@HandlerArgs(...args) |
useAuthentication |
@RequireAuth() |
useCryptoAuthentication |
@RequireCryptoAuth() |
middleware |
@UseMiddleware(...) |
validation |
@ValidateBody(), @ValidateParams(), @ValidateQuery() |
rawJsonHandler |
@RawJson() |
authFailureStatusCode |
@AuthFailureStatus(code) |
useTransaction |
@Transactional() |
transactionTimeout |
@Transactional({ timeout }) |
openapi.summary |
@ApiSummary(text) |
openapi.description |
@ApiDescription(text) |
openapi.tags |
@ApiTags(...tags) |
openapi.operationId |
@ApiOperationId(id) |
openapi.deprecated |
@Deprecated() |
openapi.requestBody |
@ApiRequestBody(options) |
openapi.responses |
@Returns(code, schema) |
openapi.parameters |
@ApiParam(), @ApiQuery(), @ApiHeader() |
// Controller options
interface ApiControllerOptions {
tags?: string[];
description?: string;
deprecated?: boolean;
name?: string;
}
// Route decorator options
interface RouteDecoratorOptions<TLanguage> {
validation?: ValidationChain[] | ((lang: TLanguage) => ValidationChain[]);
schema?: z.ZodSchema;
middleware?: RequestHandler[];
auth?: boolean;
cryptoAuth?: boolean;
rawJson?: boolean;
transaction?: boolean;
transactionTimeout?: number;
summary?: string;
description?: string;
tags?: string[];
operationId?: string;
deprecated?: boolean;
openapi?: OpenAPIRouteMetadata;
}
// Parameter decorator options
interface ParamDecoratorOptions {
description?: string;
example?: unknown;
required?: boolean;
schema?: OpenAPIParameterSchema;
}
// Cache options
interface CacheDecoratorOptions {
ttl: number; // Time to live in seconds
keyPrefix?: string;
varyByUser?: boolean;
varyByQuery?: string[];
}
// Rate limit options
interface RateLimitDecoratorOptions {
requests: number; // Max requests
window: number; // Time window in seconds
message?: string;
byUser?: boolean;
keyGenerator?: (req: Request) => string;
}
// Pagination options
interface PaginatedDecoratorOptions {
defaultPageSize?: number;
maxPageSize?: number;
useOffset?: boolean; // Use offset/limit instead of page/limit
}
// Transaction options
interface TransactionalDecoratorOptions {
timeout?: number; // Timeout in milliseconds
}Decorators not working?
- Ensure
experimentalDecorators: trueandemitDecoratorMetadata: truein tsconfig.json - Import
reflect-metadataat the top of your entry file - Extend
DecoratorBaseControllerfor full decorator support
OpenAPI metadata not appearing?
- Use
@ApiControllerinstead of@Controllerfor OpenAPI support - Ensure decorators are applied before HTTP method decorators (bottom-up execution)
- Check that schemas are registered with
@ApiSchemaorOpenAPISchemaRegistry
Authentication not enforced?
- Verify
@RequireAuth()is applied at class or method level - Check that
@Public()isn't overriding at method level - Ensure auth middleware is configured in your application
Validation errors not returning 400?
- Validation decorators automatically add 400 response to OpenAPI
- Ensure validation middleware is properly configured
- Check that Zod schemas or express-validator chains are valid
Parameter injection not working?
- Parameter decorators must be on method parameters, not properties
- Ensure the handler is called through the decorated controller
- Check parameter index matches the decorator position
For detailed migration instructions, see docs/DECORATOR_MIGRATION.md.
📚 Comprehensive documentation is available in the docs/ directory.
- 📚 Documentation Index - Complete documentation index
- 🏗️ Architecture - System design and architecture
- 🎮 Controllers - Controller system and decorators
- ⚙️ Services - Business logic and service container
- 📊 Models - Data models and registry
- 🔌 Middleware - Request pipeline
- 💾 Transactions - Transaction management
- 🔧 Plugins - Plugin system
See the full documentation index for all available documentation.
MIT © Digital Defiance
@digitaldefiance/ecies-lib- Core ECIES encryption library@digitaldefiance/node-ecies-lib- Node.js ECIES implementation@digitaldefiance/i18n-lib- Internationalization framework@digitaldefiance/suite-core-lib- Core user management primitives
Contributions are welcome! Please read the contributing guidelines in the main repository.
For issues and questions:
- GitHub Issues: https://github.com/Digital-Defiance/node-express-suite/issues
- Email: support@digitaldefiance.org
The framework uses a plugin-based architecture that separates database concerns from the core application. This replaces the old deep inheritance hierarchy (Mongo base → Application → concrete subclass) with a composable plugin pattern.
BaseApplication<TID> ← Database-agnostic base (accepts IDatabase)
└── Application<TID> ← HTTP/Express layer (server, routing, middleware)
└── useDatabasePlugin() ← Plug in any database backend
IDatabasePlugin<TID> ← Plugin interface for database backends
└── MongoDatabasePlugin ← Mongoose/MongoDB implementation
MongoApplicationConcrete ← Ready-to-use concrete class for testing/dev
| Class | Purpose |
|---|---|
BaseApplication<TID> |
Database-agnostic base. Accepts an IDatabase instance and optional lifecycle hooks. Manages PluginManager, ServiceContainer, and environment. |
Application<TID> |
Extends BaseApplication with Express HTTP/HTTPS server, routing, CSP/Helmet config, and middleware. Database-agnostic — database backends are provided via IDatabasePlugin. |
MongoApplicationConcrete<TID> |
Concrete Application subclass for testing/development. Wires up MongoDatabasePlugin with default configuration, schema maps, and a dummy email service. Replaces the old concrete class. |
The IDatabasePlugin<TID> interface extends IApplicationPlugin<TID> with database-specific lifecycle hooks:
interface IDatabasePlugin<TID> extends IApplicationPlugin<TID> {
readonly database: IDatabase;
readonly authenticationProvider?: IAuthenticationProvider<TID>;
connect(uri?: string): Promise<void>;
disconnect(): Promise<void>;
isConnected(): boolean;
// Optional dev/test store management
setupDevStore?(): Promise<string>;
teardownDevStore?(): Promise<void>;
initializeDevStore?(): Promise<unknown>;
}MongoDatabasePlugin implements IDatabasePlugin for MongoDB/Mongoose:
import { MongoDatabasePlugin } from '@digitaldefiance/node-express-suite';
const mongoPlugin = new MongoDatabasePlugin({
schemaMapFactory: getSchemaMap,
databaseInitFunction: DatabaseInitializationService.initUserDb.bind(DatabaseInitializationService),
initResultHashFunction: DatabaseInitializationService.serverInitResultHash.bind(DatabaseInitializationService),
environment,
constants,
});It wraps a MongooseDocumentStore and provides:
- Connection/disconnection lifecycle
- Dev database provisioning via
MongoMemoryReplSet - Authentication provider wiring
- Mongoose model and schema map access
The Application constructor accepts these parameters:
constructor(
environment: TEnvironment,
apiRouterFactory: (app: IApplication<TID>) => BaseRouter<TID>,
cspConfig?: ICSPConfig | HelmetOptions | IFlexibleCSP,
constants?: TConstants,
appRouterFactory?: (apiRouter: BaseRouter<TID>) => TAppRouter,
customInitMiddleware?: typeof initMiddleware,
database?: IDatabase, // Optional — use useDatabasePlugin() instead
)The database parameter is optional. When using a database plugin, the plugin's database property is used automatically.
Use useDatabasePlugin() to register a database plugin with the application:
const app = new Application(environment, apiRouterFactory);
app.useDatabasePlugin(mongoPlugin);
await app.start();useDatabasePlugin() stores the plugin as the application's database plugin AND registers it with the PluginManager, so it participates in the full plugin lifecycle (init, stop).
To use a non-Mongo database, implement IDatabasePlugin directly. You do not need IDocumentStore or any Mongoose types:
import type { IDatabasePlugin, IDatabase, IApplication } from '@digitaldefiance/node-express-suite';
class BrightChainDatabasePlugin implements IDatabasePlugin<Buffer> {
readonly name = 'brightchain-database';
readonly version = '1.0.0';
private _connected = false;
private _database: IDatabase;
constructor(private config: BrightChainConfig) {
this._database = new BrightChainDatabase(config);
}
get database(): IDatabase {
return this._database;
}
// Optional: provide an auth provider if your DB manages authentication
get authenticationProvider() {
return undefined;
}
async connect(uri?: string): Promise<void> {
await this._database.connect(uri ?? this.config.connectionString);
this._connected = true;
}
async disconnect(): Promise<void> {
await this._database.disconnect();
this._connected = false;
}
isConnected(): boolean {
return this._connected;
}
async init(app: IApplication<Buffer>): Promise<void> {
// Wire up any app-level integrations after connection
// e.g., register services, set auth provider, etc.
}
async stop(): Promise<void> {
await this.disconnect();
}
}
// Usage:
const app = new Application(environment, apiRouterFactory);
app.useDatabasePlugin(new BrightChainDatabasePlugin(config));
await app.start();This section covers migrating from the old inheritance-based hierarchy to the new plugin-based architecture.
| Before | After |
|---|---|
Old Mongo base → Application → old concrete class |
BaseApplication → Application + IDatabasePlugin |
| Database logic baked into the class hierarchy | Database logic provided via plugins |
| Old concrete class for testing/dev | MongoApplicationConcrete for testing/dev |
| Extending the old Mongo base for custom apps | Implementing IDatabasePlugin for custom databases |
// Old: The concrete class extended Application which extended the Mongo base class
// Database logic was tightly coupled into the inheritance chain
import { /* old concrete class */ } from '@digitaldefiance/node-express-suite';
const app = new OldConcreteApp(environment);
await app.start();// New: Application is database-agnostic, MongoDatabasePlugin provides Mongo support
import { MongoApplicationConcrete } from '@digitaldefiance/node-express-suite';
// For testing/development (drop-in replacement for the old concrete class):
const app = new MongoApplicationConcrete(environment);
await app.start();Or for custom wiring:
import { Application, MongoDatabasePlugin } from '@digitaldefiance/node-express-suite';
const app = new Application(environment, apiRouterFactory);
const mongoPlugin = new MongoDatabasePlugin({
schemaMapFactory: getSchemaMap,
databaseInitFunction: DatabaseInitializationService.initUserDb.bind(DatabaseInitializationService),
initResultHashFunction: DatabaseInitializationService.serverInitResultHash.bind(DatabaseInitializationService),
environment,
constants,
});
app.useDatabasePlugin(mongoPlugin);
await app.start();| Old Name | New Name | Notes |
|---|---|---|
| Old concrete class | MongoApplicationConcrete |
Drop-in replacement for testing/dev |
| Old Mongo base class | (removed) | Functionality moved to BaseApplication + MongoDatabasePlugin |
| Old base file | base-application.ts |
File renamed |
| Old concrete file | mongo-application-concrete.ts |
File renamed |
- Replace the old concrete class with
MongoApplicationConcrete - Replace any old Mongo base subclasses with
Application+useDatabasePlugin() - Update imports to use
base-application(renamed from old base file) - Update imports to use
mongo-application-concrete(renamed from old concrete file) - If you had a custom concrete subclass, convert it to use
MongoDatabasePluginor implement your ownIDatabasePlugin
Major improvements with large complexity reduction:
// Centralized dependency injection
const jwtService = app.services.get(ServiceKeys.JWT);
const userService = app.services.get(ServiceKeys.USER);// Before: IApplication<T, I, TBaseDoc, TEnv, TConst, ...>
// After: IApplication
const app: IApplication = ...;validation: function(lang) {
return ValidationBuilder.create(lang, this.constants)
.for('email').isEmail().withMessage(key)
.for('username').matches(c => c.UsernameRegex).withMessage(key)
.build();
}@Post('/register', { transaction: true })
async register() {
// this.session available automatically
await this.userService.create(data, this.session);
}return Response.created()
.message(SuiteCoreStringKey.Registration_Success)
.data({ user, mnemonic })
.build();class MyPlugin implements IApplicationPlugin {
async init(app: IApplication) { /* setup */ }
async stop() { /* cleanup */ }
}
app.plugins.register(new MyPlugin());RouteBuilder.create()
.post('/register')
.auth()
.validate(validation)
.transaction()
.handle(this.register);Version 2.0 introduces a major architecture refactor with 50% complexity reduction while maintaining backward compatibility where possible. This guide helps you migrate from v1.x to v2.0.
Before (v1.x):
class Application<T, I, TInitResults, TModelDocs, TBaseDocument, TEnvironment, TConstants, TAppRouter>
class UserController<I, D, S, A, TUser, TTokenRole, TTokenUser, TApplication, TLanguage>After (v2.0):
class Application // No generic parameters
class UserController<TConfig extends ControllerConfig, TLanguage>Migration:
- Remove all generic type parameters from Application instantiation
- Update controller signatures to use ControllerConfig interface
- Type information now inferred from configuration objects
Before (v1.x):
const jwtService = new JwtService(app);
const userService = new UserService(app);
const roleService = new RoleService(app);After (v2.0):
const jwtService = app.services.get(ServiceKeys.JWT);
const userService = app.services.get(ServiceKeys.USER);
const roleService = app.services.get(ServiceKeys.ROLE);Migration:
- Replace direct service instantiation with container access
- Services are now singletons managed by the container
- Import ServiceKeys enum for type-safe service access
Before (v1.x):
async register(req: Request, res: Response, next: NextFunction) {
return await withTransaction(
this.application.db.connection,
this.application.environment.mongo.useTransactions,
undefined,
async (session) => {
const user = await this.userService.create(data, session);
const mnemonic = await this.mnemonicService.store(userId, session);
return { statusCode: 201, response: { user, mnemonic } };
}
);
}After (v2.0):
@Post('/register', { transaction: true })
async register(req: Request, res: Response, next: NextFunction) {
const user = await this.userService.create(data, this.session);
const mnemonic = await this.mnemonicService.store(userId, this.session);
return Response.created().data({ user, mnemonic }).build();
}Benefits:
- 70% reduction in transaction boilerplate
- Automatic session management
- Cleaner, more readable code
Before (v1.x):
return {
statusCode: 201,
response: {
message: getSuiteCoreTranslation(SuiteCoreStringKey.Registration_Success, undefined, lang),
data: { user, mnemonic }
}
};After (v2.0):
return Response.created()
.message(SuiteCoreStringKey.Registration_Success)
.data({ user, mnemonic })
.build();Benefits:
- 40% reduction in response boilerplate
- Fluent, chainable API
- Automatic translation handling
Before (v1.x):
protected getValidationRules(lang: TLanguage) {
return [
body('username')
.matches(this.constants.UsernameRegex)
.withMessage(getSuiteCoreTranslation(key, undefined, lang)),
body('email')
.isEmail()
.withMessage(getSuiteCoreTranslation(key, undefined, lang))
];
}After (v2.0):
validation: function(lang: TLanguage) {
return ValidationBuilder.create(lang, this.constants)
.for('username').matches(c => c.UsernameRegex).withMessage(key)
.for('email').isEmail().withMessage(key)
.build();
}Benefits:
- 50% reduction in validation code
- Constants automatically injected
- Type-safe field access
- Cleaner syntax
Before (v1.x):
router.post('/backup-codes',
authMiddleware,
authenticateCryptoMiddleware,
validateSchema(backupCodeSchema),
this.getBackupCodes.bind(this)
);After (v2.0):
@Post('/backup-codes', {
pipeline: Pipeline.create()
.use(Auth.token())
.use(Auth.crypto())
.use(Validate.schema(backupCodeSchema))
.build()
})
async getBackupCodes() { /* ... */ }Benefits:
- Explicit middleware ordering
- Reusable pipeline definitions
- Better readability
npm install @digitaldefiance/node-express-suite@^2.0.0
# or
yarn add @digitaldefiance/node-express-suite@^2.0.0Before:
const app = new Application<MyTypes, MyIds, MyResults, MyModels, MyDoc, MyEnv, MyConst, MyRouter>({
port: 3000,
mongoUri: process.env.MONGO_URI,
jwtSecret: process.env.JWT_SECRET
});After:
const app = new Application({
port: 3000,
mongoUri: process.env.MONGO_URI,
jwtSecret: process.env.JWT_SECRET
});Find and replace service instantiation:
# Find
new JwtService(app)
# Replace with
app.services.get(ServiceKeys.JWT)
# Find
new UserService(app)
# Replace with
app.services.get(ServiceKeys.USER)Start with high-traffic endpoints:
- Add transaction decorator to write operations
- Replace response construction with Response builder
- Update validation to use ValidationBuilder
- Migrate middleware to Pipeline builder
# Run full test suite
npm test
# Run specific controller tests
npm test -- user-controller.spec.ts
# Check for deprecation warnings
DEBUG=* npm start- Update package to v2.0.0
- Remove generic parameters from Application
- Update service instantiation to use container
- Migrate transaction handling (high-priority endpoints)
- Migrate response construction (high-priority endpoints)
- Update validation rules (new endpoints first)
- Migrate middleware composition (optional)
- Run full test suite
- Check for deprecation warnings
- Update documentation
- Deploy to staging
- Monitor for issues
- Deploy to production
The following v1.x patterns still work in v2.0:
✅ Direct service instantiation (with deprecation warning) ✅ Manual transaction wrapping with withTransaction ✅ Manual response construction ✅ Traditional validation rules ✅ Direct middleware composition
- Service container adds negligible overhead (~0.1ms per request)
- Transaction decorator has same performance as manual wrapping
- Response builder is optimized for common cases
- Validation builder compiles to same express-validator chains
Solution: Remove generic type parameters from Application and controller signatures.
Solution: Ensure services are registered during application initialization. Check ServiceKeys enum.
Solution: Add { transaction: true } to route decorator options.
Solution: Ensure ValidationBuilder.create receives correct language and constants.
- Documentation: See REFACTOR_INDEX.md for complete refactor docs
- Examples: See REFACTOR_EXAMPLES.md for code examples
- Issues: Report bugs at GitHub Issues
- Support: Email support@digitaldefiance.org
Version sync — bumped major/minor without substantive changes to match rest of suite.
- Use
idProvider.parseSafe()instead offromBytes(deserialize(...))in Environment for cleaner ID parsing - Simplified environment tests accordingly
- Added comprehensive Greenlock staging boolean tests and property-based tests for Let's Encrypt configuration preservation
- Translated hardcoded English strings in UserController (
'Backup codes retrieved','Token is valid') to useSuiteCoreStringKeyi18n keys
Storage Interfaces Moved to suite-core-lib
- Moved
IDatabase,ICollection,IClientSession,IDatabaseLifecycleHooks, and related storage interfaces from localinterfaces/storage/to@digitaldefiance/suite-core-lib - Removed
IFailableResultfrom local interfaces (now imported from suite-core-lib) - Removed local
document-types.ts,document-types.branded.spec.ts, andtype-identity.property.spec.ts(moved upstream) - Updated all imports across the codebase to reference suite-core-lib
- Fixed environment variable parsing:
ADMIN_USER_ROLE_IDandSYSTEM_USER_ROLE_IDwere incorrectly reading fromADMIN_ROLE_IDandSYSTEM_ROLE_IDrespectively - Fixed debug print for
SYSTEM_USER_ROLE_ID
- Dependency updates (ecies-lib, node-ecies-lib)
- Dependency updates (ecies-lib, node-ecies-lib)
Document Type Naming Cleanup
- Renamed document interfaces:
IUserDocument→UserDocument,IRoleDocument→RoleDocument,IUserRoleDocument→UserRoleDocument(with re-exports for backward compatibility) - Updated
IServerInitResultand all services/controllers to use new document type names - Cleaned up model, schema, and service files to use consistent naming
- Added
dbandgetModel<T>()accessors toApplicationclass, delegating toMongoDatabasePluginwhen registered - Added comprehensive tests for the new database accessors
Plugin-Based Database Architecture
- BREAKING: Extracted MongoDB/Mongoose logic from
ApplicationintoMongoDatabasePlugin - NEW:
BaseApplication— database-agnostic base class that delegates storage toIDatabase - NEW:
IDatabasePlugininterface for pluggable database backends - NEW:
MongoDatabasePlugin— Mongoose-specific plugin handling connection, models, schema maps, and dev database - NEW:
createNoOpDatabase()utility for applications that don't need a database - Renamed
ApplicationBase→BaseApplication(file:application-base.ts→base-application.ts) - Removed
MongoApplicationBase(functionality absorbed byMongoDatabasePlugin) - Removed stale
.js/.js.map/.d.ts.mapartifacts frominterfaces/ - Updated
ApplicationBuilderto registerMongoDatabasePluginautomatically - Added plugin manager integration tests and property-based tests
- Added stale-reference detection tests
- Added no-op database tests
- Added integration tests: constants propagation, direct-login E2E, direct-login HTTP
- Auto-wire
MongoAuthenticationProviderduringMongoApplicationBase.start()when no auth provider is set
- Dependency updates (branded-interface, ecies-lib, i18n-lib, node-ecies-lib, suite-core-lib)
MongoApplicationBase Extraction
- NEW:
MongoApplicationBase— extracted MongoDB/Mongoose-specific logic fromApplicationBaseinto a dedicated subclass ApplicationBaseis now database-agnostic, accepting anIDatabaseinstance- Added
DISABLE_MONGOenvironment variable support for non-Mongo deployments - Updated all tests to work with the new class hierarchy
- Services (
BackupCodeService,JwtService,RoleService) now acceptIApplicationinstead ofIMongoApplication, enabling use with non-Mongo backends
Authentication Provider Abstraction
- NEW:
IAuthenticationProviderinterface — storage-agnostic authentication abstraction for user lookup, role fetching, and credential verification - NEW:
MongoAuthenticationProvider— Mongoose-backed implementation - Refactored
authenticate-tokenandauthenticate-cryptomiddlewares to useIAuthenticationProviderinstead of direct Mongoose calls - Added
authProviderproperty toIApplication - Updated
BaseControllerto pass auth provider to middlewares
Database-Agnostic Service Layer
- NEW:
IMongoApplicationinterface — separates Mongoose-specific capabilities (db,getModel) from the baseIApplication - NEW:
MongoBaseService— Mongoose-specific service base class BaseService.withTransaction()now supports both Mongoose andIDatabase.withTransaction()paths, with fallback to no-transaction execution- Refactored
Environmentto separate Mongo config from base config - Updated all controllers and services to use appropriate application interface
- Added
branded-primitiveandbranded-interfacetoCollectionSchemaFieldType - Added
reffield toFieldSchemafor branded type resolution - Added branded type tests for document-types
Branded API Responses
- NEW:
branded-responses/module with branded API response types using@digitaldefiance/branded-interface - Branded wrappers for all API response interfaces (
BrandedApiLoginResponse,BrandedApiTokenResponse, etc.) - Response validators and serializers for runtime type checking
- Extracted
ILetsEncryptConfiginto its own interface file - Added
@digitaldefiance/branded-interfacedependency - Comprehensive property-based tests for all branded responses
- Dependency updates and Greenlock test fixes
IConstantsnow extendsII18nConstantsfrom@digitaldefiance/i18n-lib(provides index signature for i18n template resolution)- Dependency updates (ecies-lib, i18n-lib, node-ecies-lib, suite-core-lib)
- Added property-based tests for no-brightchain-imports and type-identity
- Dependency updates
Storage-Agnostic Interfaces
- NEW:
interfaces/storage/module with database-agnostic abstractions:IClientSession— transaction support interfaceICollection<T>— full CRUD, query, index, aggregation, and schema validation interfaceIDatabase— database lifecycle and collection access interfaceIDatabaseLifecycleHooks— hooks for dev store provisioning and initializationDocumentTypes— comprehensive type definitions for filters, queries, indexes, aggregation, etc.
- Updated
IFailableResultwith additional utility types
IDocumentStore & Application Refactor
- NEW:
IDocumentStoreinterface — storage-agnostic interface for database operations (connect, disconnect, getModel, schemaMap) - NEW:
MongooseDocumentStore— Mongoose implementation ofIDocumentStore - NEW:
MongooseDatabase,MongooseCollection,MongooseSessionAdapter— Mongoose adapters for the storage interfaces - NEW:
defaultMongoUriValidatorutility for MongoDB URI validation - Refactored
ApplicationBaseto useIDocumentStoreinstead of direct Mongoose calls - Simplified
Applicationclass by delegating database operations to the document store - Added comprehensive tests for Mongoose adapters, application lifecycle, and backward compatibility
- Dependency updates (ecies-lib, i18n-lib, suite-core-lib)
Let's Encrypt / Automated TLS Support
- NEW:
GreenlockManager— automated TLS certificate management via Let's Encrypt usinggreenlock-express - Added Let's Encrypt environment variables (
LETS_ENCRYPT_ENABLED,LETS_ENCRYPT_EMAIL,LETS_ENCRYPT_HOSTNAMES,LETS_ENCRYPT_STAGING,LETS_ENCRYPT_CONFIG_DIR) - Application automatically obtains/renews certificates, serves HTTPS on port 443, and redirects HTTP from port 80
- Mutual exclusivity with dev-certificate HTTPS mode
- Comprehensive tests for Greenlock manager and Let's Encrypt configuration
- Updated README with full Let's Encrypt documentation
UPnP Support
- NEW: UPnP plugin (
src/plugins/upnp.ts) for automatic port forwarding - NEW:
UPnPService— NAT-PMP/UPnP port mapping service - NEW:
UPnPManager— lifecycle management for UPnP mappings - NEW:
UPnPConfigService— configuration and validation - NEW: UPnP network interfaces (
IUPnPService, UPnP types) - Added UPnP architecture documentation, configuration guide, and manual testing guide
- Comprehensive unit and property-based tests for all UPnP components
- Updated
@digitaldefiance/express-suite-test-utilsto v1.1.1
- Dependency updates: ecies-lib 4.17.10 → 4.18.0, i18n-lib 4.4.0 → 4.5.0, node-ecies-lib 4.17.10 → 4.18.0
- Dependency updates (ecies-lib, node-ecies-lib, suite-core-lib)
- Dependency updates: ecies-lib 4.17.2 → 4.17.4, i18n-lib 4.3.2 → 4.4.0, node-ecies-lib 4.17.2 → 4.17.5, reed-solomon-erasure.wasm ^1.0.1 → ^1.0.2
OpenAPI Response Type Cleanup
OpenAPIResponseDef.schemanow acceptsstring | Record<string, unknown>(wasstringonly), supporting inline schema objects- Removed duplicate
OpenAPIResponseinterface from builder (consolidated intoOpenAPIResponseDef) - Renamed
OpenAPIResponsein controller toOpenAPIEndpointResponseto avoid naming conflicts - Cleaned up re-exports in
openapi/index.ts
- Dependency updates: ecies-lib 4.16.30 → 4.17.2, i18n-lib 4.3.0 → 4.3.2, node-ecies-lib 4.16.30 → 4.17.2, suite-core-lib 3.10.31 → 3.12.1
Comprehensive Decorator API for Express Controllers
This release introduces a complete decorator-based API for defining controllers, routes, validation, and OpenAPI documentation. The decorator system provides a declarative, type-safe approach to building Express APIs with automatic OpenAPI 3.0.3 specification generation.
New Decorator Categories:
Controller Decorators:
@Controller(basePath)- Define controller base path (existing, preserved for backward compatibility)@ApiController(basePath, options?)- Define controller with OpenAPI metadata (tags, description, deprecated)
HTTP Method Decorators (Enhanced):
@Get,@Post,@Put,@Delete,@Patch- Now support inline OpenAPI options (summary, description, tags, operationId, deprecated)
Authentication Decorators:
@RequireAuth()- Require JWT authentication (auto-adds 401 response to OpenAPI)@RequireCryptoAuth()- Require ECIES crypto authentication@Public()- Mark route as public (overrides class-level auth)@AuthFailureStatus(code)- Set custom auth failure status code
Parameter Injection Decorators:
@Param(name, options?)- Inject path parameter (auto-adds to OpenAPI)@Body(field?)- Inject request body or specific field@Query(name, options?)- Inject query parameter (auto-adds to OpenAPI)@Header(name, options?)- Inject header value (auto-adds to OpenAPI)@CurrentUser()- Inject authenticated user (req.user)@EciesUser()- Inject ECIES authenticated member (req.eciesUser)@Req(),@Res(),@Next()- Inject Express request/response/next
Validation Decorators:
@ValidateBody(schema)- Validate request body with Zod or express-validator (auto-adds 400 response)@ValidateParams(schema)- Validate path parameters@ValidateQuery(schema)- Validate query parameters
Response Decorators:
@Returns(statusCode, schema, options?)- Document response type (stackable for multiple status codes)@ResponseDoc(responses)- Document multiple responses at once@RawJson()- Bypass standard response wrapper@Paginated(options?)- Add pagination query parameters and response envelope
Middleware Decorators:
@UseMiddleware(...middleware)- Attach Express middleware (class or method level)@CacheResponse(options)- Add response caching middleware@RateLimit(options)- Add rate limiting middleware (auto-adds 429 response)
Transaction Decorator:
@Transactional(options?)- Wrap handler in MongoDB transaction with optional timeout
OpenAPI Operation Decorators:
@ApiOperation(metadata)- Set full OpenAPI operation metadata@ApiTags(...tags)- Add tags (class or method level, additive)@ApiSummary(text)- Set operation summary@ApiDescription(text)- Set operation description@Deprecated()- Mark operation as deprecated@ApiOperationId(id)- Set unique operation ID@ApiExample(example)- Add request/response examples
OpenAPI Parameter Decorators:
@ApiParam(name, options)- Document path parameter with full OpenAPI metadata@ApiQuery(name, options)- Document query parameter@ApiHeader(name, options)- Document header parameter@ApiRequestBody(options)- Document request body with schema, example, description
Lifecycle Decorators:
@OnSuccess(callback)- Execute after successful response@OnError(callback)- Execute when error occurs@Before(callback)- Execute before handler@After(callback)- Execute after handler (success or error)
Handler Args Decorator:
@HandlerArgs(...args)- Pass additional arguments to handler
Schema Decorators:
@ApiSchema(name?)- Register class as OpenAPI schema@ApiProperty(options)- Add property metadata (type, description, required, example)
New Documentation Middleware:
SwaggerUIMiddleware(options)- Serve Swagger UI with customization (title, favicon, CSS, JS)ReDocMiddleware(options)- Serve ReDoc documentation with customizationgenerateMarkdownDocs(spec)- Generate markdown documentation from OpenAPI spec
New Infrastructure:
src/decorators/metadata-keys.ts- Symbol constants for all decorator metadatasrc/decorators/metadata-collector.ts- Utility for metadata operationssrc/interfaces/openApi/decoratorOptions.ts- All decorator option interfaces- Enhanced
DecoratorBaseControllerwith full metadata collection and parameter injection
Zod Integration:
- Comprehensive Zod to OpenAPI schema conversion
- Support for nested objects, arrays, unions, enums
- Automatic extraction of descriptions and examples from Zod schemas
Key Features:
- Full feature parity with manual
RouteConfigapproach - Automatic OpenAPI 3.0.3 specification generation
- Metadata merging from multiple decorators
- Class-level decorator inheritance with method-level overrides
- Backward compatible - existing code continues to work unchanged
Documentation:
- Updated README with comprehensive Decorator API section
- New
docs/DECORATOR_MIGRATION.mdwith complete migration guide - Updated
docs/CONTROLLERS.mdfeaturing decorator-based approach - JSDoc documentation on all public decorators and types
Testing:
- Property-based tests for decorator correctness properties
- Integration tests for full controller with all decorators
- E2E tests for HTTP requests to decorated endpoints
Deprecations:
- None. This release is fully backward compatible with existing code.
String Key Enum Registration & translateStringKey Support
- Upgraded to
@digitaldefiance/i18n-libv4.0.5+ with branded enum translation support - Upgraded to
@digitaldefiance/suite-core-libv3.10.0 withregisterStringKeyEnum()andtranslateStringKey()support - Upgraded to
@digitaldefiance/ecies-libv4.16.0 and@digitaldefiance/node-ecies-libv4.16.0 - Updated
@noble/curvesto v1.9.0 and@noble/hashesto v1.8.0
Dependencies:
@digitaldefiance/ecies-lib: 4.15.1 → 4.16.0@digitaldefiance/i18n-lib: 4.0.3 → 4.0.5@digitaldefiance/node-ecies-lib: 4.15.1 → 4.16.0@digitaldefiance/suite-core-lib: 3.9.1 → 3.10.0
Dependency Updates & Cleanup
- Removed showcase yarn.lock files (moved to separate showcase directory)
- Updated dependencies for compatibility with suite-core-lib v3.9.x
I18n v4 Upgrade & Branded Enum Support
- Upgraded to
@digitaldefiance/i18n-libv4.0.3 with branded enum support - Upgraded to
@digitaldefiance/suite-core-libv3.8.1 with improved i18n integration - Upgraded to
@digitaldefiance/ecies-libv4.14.3 and@digitaldefiance/node-ecies-libv4.14.2 - Added
build:prodscript for production builds - Updated validation builder for i18n v4 compatibility
- Updated response builder for i18n v4 compatibility
- Updated base controller for i18n v4 compatibility
- Updated error classes for i18n v4 compatibility
Dependencies:
@digitaldefiance/ecies-lib: 4.13.0 → 4.14.3@digitaldefiance/i18n-lib: 3.8.16 → 4.0.3@digitaldefiance/node-ecies-lib: 4.13.0 → 4.14.2@digitaldefiance/suite-core-lib: 3.7.0 → 3.8.1
Patch Releases
- Various bug fixes and dependency updates between v3.8.0 and v3.9.0
Breaking Changes:
- Updated encryption API to match ecies-lib v4.13.0:
encryptSimpleOrSingle(false, ...)→encryptWithLength(...)decryptSimpleOrSingleWithHeader(false, ...)→decryptWithLengthAndHeader(...)encryptSimpleOrSingle(true, ...)→encryptBasic(...)decryptSimpleOrSingleWithHeader(true, ...)→decryptBasicWithHeader(...)
registerNodeRuntimeConfiguration()now requires a key parameterXorService.xor()now throws error when key is shorter than data- Removed
OBJECT_ID_LENGTHfromIConstantsinterface
Interface Updates:
IConstantsnow extendsINodeEciesConstantsandISuiteCoreConstants- Added
ECIES_CONFIGto runtime configuration - Encryption mode constants renamed:
SIMPLE→BASIC,SINGLE→WITH_LENGTH
Dependencies:
@digitaldefiance/ecies-lib: 4.12.8 → 4.13.0@digitaldefiance/node-ecies-lib: 4.12.8 → 4.13.0@digitaldefiance/suite-core-lib: 3.6.50 → 3.7.0
- Update mongoose-types
- Update suite-core
- Properly use @digitaldefiance/mongoose-types throughout
- Add models from suite-core-lib
- Use @digitaldefiance/mongoose-types to suppose mongoose generic types
- Fix mongoose schema to support generic ids
- Version updates to reduce circular dependency
- Library updates
- Library updates
Type Safety Improvements
- ELIMINATED: All unsafe
as anytype casts from production code - IMPROVED: Mongoose query patterns - replaced projection objects with
.select()method - ENHANCED: Generic type handling in
SystemUserServiceandBackupCodeService - FIXED: Type compatibility between Mongoose documents and service interfaces
- ADDED: Explanatory comments for necessary type assertions
- VERIFIED: All 1164 tests passing with full type safety
- VERIFIED: Build successful with strict TypeScript checking
Technical Details
- Replaced
{ password: 0 } as anywith.select('-password')in authentication middleware - Made
SystemUserService.setSystemUsergeneric to accept different ID types - Updated test mocks to support new query method chains
- Documented remaining
as unknowncasts (2 instances, both necessary for generic compatibility)
- Upgrade to
@digitaldefiance/suite-core-libv3.0.0 - Upgrade to
@digitaldefiance/ecies-libv4.1.0 - Upgrade to
@digitaldefiance/node-ecies-libv4.1.0
- Update suite-core-lib
- Update suite-core-lib
- Update suite-core-lib
- Update suite-core-lib
- Fourth attempt to fix roles on login
- Third attempt to fix roles on login
- Second attempt to fix roles on login
- Fix roles on login
- Update libs
- Update suite-core-lib
- Fix user controller direct/email login requestUserDTO response
- Fix user schema validation/tests
- Update user schema for currency
- Update request user function for currency
- Documentation, library updates
- Documentation, library updates
- Add settings endpoint
- Update libs
- Add darkMode endpoint
- Add user settings controller endpoint
- Add user settings service function
- Update suite-core-lib
- Fix default expectations for directChallenge
- Update suite-core-lib
- Update suite-core-lib
- Update suite-core-lib
- Clarify InvalidModelError/ModelNotRegisteredError
- Add this.name to errors
- Update suite-core-lib
- Enable directChallenge login by default
- Fix req.user serialization
- Update suite-core-lib
- Add darkMode user preference
- fix authenticateCrypto
- Changes to base controller
- Working on handleable error handling
- Working on handleable error handling
- Working on handleable error handling
- Working on handleable error handling
- Fix user controller
- Fix Handleable loop
- Print db init failures in application base
- Library upgrade for ecies, etc
- Testing improvements
- Print server init results on dev init
- Add host to HTTPS
- Clarify message
- Don't throw for devDatabase
- Improve EmailRegistry
- Upates to test utils
- Fixes to testing
- Add hostname to constants
- Improve database initialization prints
- Improve database initialization
- Update suite-core
- Update suite-core
- Add .env print to db init fcn
- Fix EnvNotSet error
- Add SiteEmailDomain
- Update suite-core for flags and ISuccessMessage
- Update suite-core for flags
- Upgrade suite-core for adding strings
- Upgrade ecies
- Add regexes to constants
- Add mnemonic encryption key regex/validation
- Alignment with Express Suite packages
- All packages updated to v2.1.40 (i18n, ecies-lib, node-ecies-lib, suite-core-lib, node-express-suite, express-suite-react-components)
- Test utilities remain at v1.0.7
/testingentry point exports test utilities (mockFunctions, setupTestEnv, etc.)- Requires
@faker-js/fakeras dev dependency for test utilities
- Upgrade i18n/bring config through
- Upgrade node-ecies, use new express-suite-test-utils
- Upgrade underlying libraries
- Improve test coverage
- Provide mocks/fixtures for use in testing
- Provide concrete/runnable MongoApplicationConcrete class
- Export DummyEmailService for testing
- Further streamline Application generics
- Updates from suite-core
- Continued bugfixes
- Minor bugfixes for database-initialization
- minor bugfix for translation in database-initialization
- minor bugfix for translation in database-initialization
- minor bugfix for translation in database-initialization
- Upgrade i18n
- Upgrade i18n
- Minor update to i18n area of node-express-suite in database initialization
- Update i18n
- Minor bump for node-ecies
- Minor change from suite-core for createSuiteCoreComponentConfig()
- Convergence bump/upgrades from i18n/ecies
- Minor version bump from suite-core-lib
- Minor version bump from i18n/ecies
- Minor version bump from i18n/ecies
Test Suite Stabilization
- FIXED: i18n initialization using correct
initSuiteCoreI18nEngine()function - FIXED: Language registry duplicate registration errors in tests
- FIXED: Validation builder chain initialization before
withMessage() - FIXED: Translation mock signatures to match actual implementation
- FIXED: Environment timezone type expectations
- ADDED: 21 new tests for index exports coverage (604 total, 100% passing)
- IMPROVED: Code coverage from 53.35% to 57.86% (+4.51%)
- IMPROVED: 11 modules now at 100% coverage
- ENHANCED: Clean test output with proper console mocking
Quality & Stability Release
- UPGRADED: All dependencies to latest stable versions
- @digitaldefiance/suite-core-lib@2.1.3
- @digitaldefiance/i18n-lib@2.1.1
- @digitaldefiance/ecies-lib@2.1.3
- @digitaldefiance/node-ecies-lib@2.1.3
- IMPROVED: Test suite with 604 passing tests (100% success rate)
- IMPROVED: Code coverage to 57.86% (+4.5% improvement)
- IMPROVED: 11 modules at 100% coverage
- FIXED: i18n initialization and language registry management
- FIXED: Validation builder chain initialization
- FIXED: Translation mock signatures in tests
- ENHANCED: Type safety throughout the codebase
- ENHANCED: Clean test output with proper console mocking
- BREAKING: Simplified IApplication interface (removed 5 generic parameters)
- NEW: Service Container for centralized dependency injection
- NEW: ValidationBuilder with fluent API and constants injection
- NEW: Middleware Pipeline builder for explicit composition
- NEW: Route Builder DSL as alternative to decorators
- NEW: Automatic transaction management via decorators
- NEW: Response Builder with fluent API
- NEW: Application Builder for simplified construction
- NEW: Plugin System for extensibility
- NEW: Router Config separation
- NEW: Database Initializer interface
- NEW: Config Objects pattern throughout
- IMPROVED: 50% overall complexity reduction
- IMPROVED: 87.5% reduction in generic parameters
- IMPROVED: 70% reduction in transaction boilerplate
- IMPROVED: 40% reduction in response boilerplate
- IMPROVED: Better IDE support and type inference
- Bind this on controller validation
- Upgrade i18n, ecies, suite-core
- Refactor middlewares further
- Refactor middlewares to be more extensible
- AppRouter factory to make AppRouter extensible as well
- Make vars protected
- Overridable view rendering
- Minor fix on dist dir detection
- Version bump
- Wired the express-suite package to the shared constant stack: default exports now expose LocalhostConstants, and every service/schema/controller pulls constants from the application instance instead of hard-coding them.
- Propagated the richer constant set (keyring, wrapped-key, PBKDF2 profiles, encryption metadata) into createExpressRuntimeConfiguration, the checksum/FEC services, and type definitions so downstream apps share ECIES/node defaults.
- Updated system-user, backup-code, JWT, mnemonic, key-wrapping, and user flows to accept injected constants, including rewrapping logic and password/KDF validation paths.
- Tightened controller/router/service generics and typings, clarified validation guard rails, and swapped several equality checks to operate on Uint8Array for safer crypto comparisons.
- Refreshed mocks/tests to consume LocalhostConstants, fixed registry helpers, and expanded tsconfig.spec to compile the runtime sources so the new injections are covered.
- Make application factory pattern for api router
- Upgrade i18n with aliases for t() fn
- Handle database initialization errors
- Fix StringName strings
- Fix constatnts during database initialization
- Homogenize versions
- Update libs
- Properly export db-init-cache
- Re-release with js
- Upgrade to es2022/nx monorepo
- Update libs
- Upgrade various things to pluginI18nengine
- Update suite-core
- Update IApplication/Application so that IEnvironment is more extensible
- Update libs
- Pull in i18n registration updates up through suite-core-lib
- Update suite-core
- Update ecies/i18n/suite-core
- Update suite-core
- Update suite-core
- Use typed/handleable from i18n
- Update libs
- Update libs
- Update libs
- Add test
- Export api router
- Update suite-core-lib to include new error classes
- improve role/user services
- Export missing role schema
- Export missing enumeration
- Export enumerations
- Export schemas
- Update suite-core
- Update ecies libs
- Initial release with complete Express.js framework
- Dynamic model registry system
- JWT authentication and RBAC
- Multi-language i18n support
- Comprehensive service layer
- Database initialization utilities