@ticatec/common-express-server
v0.3.1
Published
A comprehensive TypeScript library providing common classes, controllers, and middleware for building scalable Express.js applications with multi-tenant support.
Readme
@ticatec/common-express-server
A comprehensive TypeScript library providing common classes, controllers, and middleware for building scalable Express.js applications with multi-tenant support.
中文 | English
Features
- 🚀 Express.js Foundation: Built on Express.js 5.x with full TypeScript support
- 🏢 Multi-tenant Architecture: Built-in support for multi-tenant applications
- 🔐 Authentication & Authorization: User authentication and role-based access control
- 🎯 Controller Patterns: Pre-built base controllers for common CRUD operations
- 📝 Validation: Integrated data validation using bean-validator
- 🔄 Error Handling: Centralized error handling and logging
- 🌐 Internationalization: Built-in language support via headers
- 📊 Logging: Structured logging with log4js integration
- 🎨 TypeScript First: Full TypeScript support with comprehensive type definitions
Installation
npm install @ticatec/common-express-serverPeer Dependencies
npm install express@^5.1.0Quick Start
1. Create a Basic Server
import { BaseServer, CommonRouterHelper } from '@ticatec/common-express-server';
class MyRouterHelper extends CommonRouterHelper {
// Add custom middleware or override methods as needed
}
class MyServer extends BaseServer<MyRouterHelper> {
protected getHelper(): MyRouterHelper {
return new MyRouterHelper();
}
protected async loadConfigFile(): Promise<void> {
// Load your configuration here
console.log('Loading configuration...');
}
protected getWebConf() {
return {
port: 3000,
ip: '0.0.0.0',
contextRoot: '/api'
};
}
protected async setupRoutes(app: Express): Promise<void> {
// Set up your routes here
await this.bindRoutes(app, '/users', () => import('./routes/UserRoutes'));
}
}
// Start the server
const server = new MyServer();
BaseServer.startup(server);2. Create Routes
import { CommonRoutes, CommonRouterHelper } from '@ticatec/common-express-server';
class UserRoutes extends CommonRoutes<CommonRouterHelper> {
constructor(helper: CommonRouterHelper) {
super(helper); // checkUser = true by default
this.setupRoutes();
}
private setupRoutes() {
this.router.get('/profile', this.helper.invokeRestfulAction(this.getProfile));
this.router.post('/update', this.helper.invokeRestfulAction(this.updateProfile));
}
private getProfile = async (req: Request) => {
// Your logic here
return { message: 'User profile' };
};
private updateProfile = async (req: Request) => {
// Your logic here
return { message: 'Profile updated' };
};
}
export default UserRoutes;Custom User Authentication
You can provide a custom user verification function to the CommonRoutes constructor:
import { CommonRoutes, CommonRouterHelper, UserChecker } from '@ticatec/common-express-server';
// Custom user checker function
const customUserChecker: UserChecker = (req: Request): boolean => {
// Your custom authentication logic
const userRole = req.headers['user-role'];
return userRole === 'admin' || userRole === 'moderator';
};
class AdminRoutes extends CommonRoutes<CommonRouterHelper> {
constructor(helper: CommonRouterHelper) {
// Use custom user checker instead of default
super(helper, customUserChecker);
this.setupRoutes();
}
private setupRoutes() {
this.router.get('/dashboard', this.helper.invokeRestfulAction(this.getDashboard));
}
private getDashboard = async (req: Request) => {
return { message: 'Admin dashboard' };
};
}
// Skip authentication entirely
class PublicRoutes extends CommonRoutes<CommonRouterHelper> {
constructor(helper: CommonRouterHelper) {
super(helper, false); // No authentication check
this.setupRoutes();
}
private setupRoutes() {
this.router.get('/info', this.helper.invokeRestfulAction(this.getInfo));
}
private getInfo = async (req: Request) => {
return { message: 'Public information' };
};
}The checkUser parameter accepts three types:
true(default): Uses the defaulthelper.checkLoggedUser()middlewarefalse: Skips user authentication entirelyUserCheckerfunction: A custom function(req: Request) => booleanthat returns true if authenticated
3. Create Controllers
import { TenantBaseController } from '@ticatec/common-express-server';
import { ValidationRules, StringValidator } from '@ticatec/bean-validator';
interface UserService {
createNew(user: any, data: any): Promise<any>;
update(user: any, data: any): Promise<any>;
search(user: any, query: any): Promise<any>;
}
const userValidationRules: ValidationRules = [
new StringValidator('name', { required: true, minLen: 2 }),
new StringValidator('email', {
required: true,
format: {
regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Invalid email format'
}
})
];
class UserController extends TenantBaseController<UserService> {
constructor(userService: UserService) {
super(userService, userValidationRules);
}
// CRUD methods are inherited and automatically validated
// createNew(), update(), del() are available
// Add custom methods
search() {
return async (req: Request): Promise<any> => {
const query = req.query;
this.checkInterface('search');
return await this.invokeServiceInterface('search', [
this.getLoggedUser(req),
query
]);
};
}
}Core Classes
BaseServer
Abstract base server class that provides:
- Express application setup
- Configuration loading
- Route binding
- Error handling
- Health check endpoint
- Static file serving
CommonRouterHelper
Middleware utilities for:
- JSON response formatting
- Cache control
- User authentication
- Error handling
- Request logging
CommonRoutes
Base class for route definitions with:
- Express router integration
- User authentication checks
- Logging capabilities
Controllers Hierarchy
- BaseController: Basic controller with logging and user context
- CommonController: CRUD operations with validation
- AdminBaseController: Admin-specific operations (tenant-independent)
- TenantBaseController: Tenant-specific operations
- AdminSearchController: Admin search operations
- TenantSearchController: Tenant search operations
Configuration
Application Configuration
import { AppConf } from '@ticatec/common-express-server';
// Initialize configuration
AppConf.init({
database: {
host: 'localhost',
port: 5432
},
server: {
port: 3000
}
});
// Use configuration
const config = AppConf.getInstance();
const dbHost = config.get('database.host');
const serverPort = config.get('server.port');Gateway Architecture
This application is designed to work behind an API Gateway. The gateway handles JWT tokens or session-based authentication and forwards the authenticated user information to the Express application via HTTP headers.
Architecture Flow
Client Request (JWT/Session) → API Gateway → Express Application
↓
User Info HeadersGateway Responsibilities
The API Gateway should:
- Authenticate requests using JWT tokens, session cookies, or other authentication mechanisms
- Extract user information from the authentication token/session
- Forward user data as HTTP headers to the Express application
- Handle authorization and rate limiting as needed
User Authentication Headers
The library expects user information in the request headers:
// Headers forwarded by the gateway
{
'user': encodeURIComponent(JSON.stringify({
accountCode: 'user123',
name: 'John Doe',
tenant: { code: 'tenant1', name: 'Tenant One' }
})),
'x-language': 'en'
}Gateway Implementation Example
Here's an example of how the gateway might process authentication:
// Gateway middleware (pseudo-code)
async function processAuthentication(request) {
// 1. Validate JWT token or session
const token = request.headers.authorization?.replace('Bearer ', '');
const userInfo = await validateJWT(token);
// 2. Extract user information
const user = {
accountCode: userInfo.sub,
name: userInfo.name,
tenant: {
code: userInfo.tenant_code,
name: userInfo.tenant_name
}
};
// 3. Forward to Express app with user header
request.headers['user'] = encodeURIComponent(JSON.stringify(user));
request.headers['x-language'] = userInfo.preferred_language || 'en';
// 4. Proxy request to Express application
return proxyToExpressApp(request);
}Security Considerations
- No direct authentication: This Express application does NOT handle JWT validation or session management
- Trust boundary: The application trusts that the gateway has properly authenticated users
- Header validation: User headers are parsed and validated but not authenticated
- Network security: Ensure secure communication between gateway and Express app (internal network/VPN)
User Impersonation
The library supports user impersonation, which allows privileged system users to act as another user (including cross-tenant operations) for debugging and troubleshooting purposes.
How It Works
When a privileged user needs to impersonate another user, the gateway should include both the original user and the target user in the headers:
// Headers with user impersonation
{
'user': encodeURIComponent(JSON.stringify({
// Original privileged user
accountCode: 'admin123',
name: 'System Admin',
tenant: { code: 'system', name: 'System Tenant' },
// User being impersonated
actAs: {
accountCode: 'user456',
name: 'Target User',
tenant: { code: 'client-a', name: 'Client A' }
}
})),
'x-language': 'en'
}Implementation in Controllers
The BaseController.getLoggedUser() method automatically handles impersonation:
class MyController extends TenantBaseController<MyService> {
async getUserData(req: Request) {
// This will return the impersonated user if actAs is present,
// otherwise returns the original user
const currentUser = this.getLoggedUser(req);
console.log('Operating as:', currentUser.name);
console.log('Tenant context:', currentUser.tenant.code);
// All operations will be performed in the context of the impersonated user
return await this.service.getUserData(currentUser);
}
}Use Cases
- Debug user issues: Support staff can reproduce problems by acting as the affected user
- Cross-tenant troubleshooting: System administrators can debug issues across different tenants
- Testing user permissions: Verify that user-specific access controls work correctly
- Data migration: Perform operations on behalf of users during system migrations
Express Application Responsibilities
The Express application simply:
- Trusts the gateway: Accepts user impersonation information from authenticated gateway requests
- Processes context: Uses the
actAsuser for all business operations when present - No validation: Does not validate impersonation privileges or restrictions
Gateway Responsibilities for Impersonation
All impersonation controls should be handled by the gateway:
- Privilege validation: Verify users have permission to impersonate others
- Audit logging: Record all impersonation activities for security auditing
- Time limits: Implement time-based restrictions on impersonation sessions
- Session management: Handle impersonation session lifecycle
- Cross-tenant controls: Apply additional checks for cross-tenant impersonation
- Notification: Optionally notify target users when their accounts are being impersonated
Multi-tenant Support
The library provides built-in multi-tenant support:
// Tenant-specific controller
class ProductController extends TenantBaseController<ProductService> {
// Automatically receives logged user context
// All operations are tenant-scoped
}
// Admin controller (cross-tenant)
class SystemController extends AdminBaseController<SystemService> {
// Operations across all tenants
}Validation
Built-in validation using @ticatec/bean-validator:
import { ValidationRules, StringValidator, NumberValidator } from '@ticatec/bean-validator';
const rules: ValidationRules = [
new StringValidator('name', { required: true, minLen: 2, maxLen: 50 }),
new StringValidator('email', {
required: true,
format: {
regex: /^[^\s@]+@[^\s@]+\.[^\s@]+$/,
message: 'Invalid email format'
}
}),
new NumberValidator('age', { required: false, minValue: 18, maxValue: 120 })
];
class UserController extends CommonController<UserService> {
constructor(service: UserService) {
super(service, rules); // Validation applied automatically
}
}Error Handling
Centralized error handling with @ticatec/express-exception:
import {
ActionNotFoundError,
UnauthenticatedError,
IllegalParameterError
} from '@ticatec/express-exception';
// Errors are automatically handled and formatted
throw new ActionNotFoundError('Resource not found');
throw new UnauthenticatedError('User not authenticated');
throw new IllegalParameterError('Invalid input data');API Reference
Types
// Function signatures
export type RestfulFunction = (req: Request) => any;
export type ControlFunction = (req: Request, res: Response) => any;
export type moduleLoader = () => Promise<any>;
// Validation types (from @ticatec/bean-validator)
export type ValidationRules = Array<BaseValidator>;
// User interfaces
export interface CommonUser {
accountCode: string;
name: string;
tenant: {
code: string;
name: string;
};
[key: string]: any;
}
export interface LoggedUser extends CommonUser {
actAs?: CommonUser; // For user impersonation
}Development
Build
npm run build # Build the project
npm run dev # Development mode with watchRequirements
- Node.js >= 18.0.0
- Express.js ^5.1.0
- TypeScript ^5.0.0
Dependencies
@ticatec/bean-validator: Data validation@ticatec/express-exception: Error handling@ticatec/node-common-library: Common utilitieslog4js: Logging framework
Contributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Support
For support and questions:
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📚 Documentation: GitHub Repository
Made with ❤️ by TicaTec
