Express utilities and decorators for building web applications with Inversify dependency injection container
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Middleware integration system supporting both Express middleware and custom Inversify-based middleware with dependency injection support. The system allows for flexible middleware composition at both controller and method levels.
Abstract base class for creating custom middleware that integrates with Inversify dependency injection.
/**
* Abstract base class for middleware with dependency injection support
*/
abstract class BaseMiddleware {
/** HTTP context initialized when middleware is invoked */
httpContext: HttpContext;
/**
* Binds a service to the request-scoped container
* @param serviceIdentifier - Service identifier to bind
* @returns Binding syntax for fluent configuration
*/
protected bind<T>(
serviceIdentifier: interfaces.ServiceIdentifier<T>
): interfaces.BindingToSyntax<T>;
/**
* Abstract handler method that must be implemented by concrete middleware
* @param req - Express request object
* @param res - Express response object
* @param next - Express next function
*/
abstract handler(
req: Request,
res: Response,
next: NextFunction
): void | Promise<void>;
}Decorator for applying middleware to controllers or individual methods.
/**
* Applies middleware to a controller class or method
* @param middleware - Middleware functions or service identifiers to apply
* @returns Decorator function for classes or methods
*/
function withMiddleware(...middleware: Middleware[]): ClassDecorator | MethodDecorator;Type definitions for middleware integration.
/**
* Middleware type supporting both Express handlers and Inversify service identifiers
*/
type Middleware = interfaces.ServiceIdentifier | RequestHandler;
/**
* Middleware metadata interface for storing middleware configuration
*/
interface MiddlewareMetaData {
[identifier: string]: Middleware[];
}Usage Examples:
import { injectable } from "inversify";
import {
BaseMiddleware,
withMiddleware,
controller,
httpGet,
BaseHttpController
} from "inversify-express-utils";
// Custom Middleware with Dependency Injection
@injectable()
class LoggingMiddleware extends BaseMiddleware {
async handler(req: Request, res: Response, next: NextFunction): Promise<void> {
const startTime = Date.now();
// Access injected services through HTTP context
const logger = this.httpContext.container.get<Logger>("Logger");
logger.info(`${req.method} ${req.path} - Request started`);
// Continue to next middleware/handler
next();
// Log completion (this runs after the response)
res.on("finish", () => {
const duration = Date.now() - startTime;
logger.info(`${req.method} ${req.path} - Completed in ${duration}ms`);
});
}
}
@injectable()
class AuthenticationMiddleware extends BaseMiddleware {
async handler(req: Request, res: Response, next: NextFunction): Promise<void> {
const token = req.headers.authorization;
if (!token) {
res.status(401).json({ error: "Authentication required" });
return;
}
try {
// Use services from container
const authService = this.httpContext.container.get<AuthService>("AuthService");
const user = await authService.validateToken(token);
// Store user in request for later use
(req as any).user = user;
next();
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
}
}
@injectable()
class RateLimitMiddleware extends BaseMiddleware {
async handler(req: Request, res: Response, next: NextFunction): Promise<void> {
const cacheService = this.httpContext.container.get<CacheService>("CacheService");
const clientIp = req.ip;
const key = `rate_limit_${clientIp}`;
const requestCount = await cacheService.increment(key, 60); // 60 second window
if (requestCount > 100) { // 100 requests per minute
res.status(429).json({ error: "Rate limit exceeded" });
return;
}
next();
}
}
// Express Middleware Functions
function corsMiddleware(req: Request, res: Response, next: NextFunction) {
res.header("Access-Control-Allow-Origin", "*");
res.header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
res.header("Access-Control-Allow-Headers", "Content-Type, Authorization");
if (req.method === "OPTIONS") {
res.sendStatus(204);
return;
}
next();
}
function compressionMiddleware(req: Request, res: Response, next: NextFunction) {
// Simple compression logic
const originalSend = res.send;
res.send = function(body) {
if (typeof body === "string" && body.length > 1000) {
res.header("Content-Encoding", "gzip");
// Compress body (pseudo-code)
body = compress(body);
}
return originalSend.call(this, body);
};
next();
}Apply middleware to all methods in a controller.
// Using decorator syntax
@controller("/api/users")
@withMiddleware(LoggingMiddleware, AuthenticationMiddleware)
class UserController extends BaseHttpController {
// All methods inherit the middleware
@httpGet("/")
getUsers() {
return this.ok(["user1", "user2"]);
}
@httpPost("/")
createUser(@requestBody() userData: any) {
return this.created("/users/123", userData);
}
}
// Using constructor parameter
@controller("/api/products", LoggingMiddleware, corsMiddleware)
class ProductController extends BaseHttpController {
@httpGet("/")
getProducts() {
return this.ok(["product1", "product2"]);
}
}Apply middleware to specific methods within a controller.
@controller("/api/admin")
class AdminController extends BaseHttpController {
@httpGet("/users")
@withMiddleware(AuthenticationMiddleware, RateLimitMiddleware)
getUsers() {
return this.ok(["user1", "user2"]);
}
@httpPost("/users")
@withMiddleware(AuthenticationMiddleware, compressionMiddleware)
createUser(@requestBody() userData: any) {
return this.created("/users/123", userData);
}
// No additional middleware on this method
@httpGet("/health")
getHealth() {
return this.ok({ status: "healthy" });
}
}Combine Inversify middleware and Express middleware in the same application.
@controller("/api/files")
@withMiddleware(corsMiddleware, LoggingMiddleware) // Express + Inversify middleware
class FileController extends BaseHttpController {
@httpPost("/upload")
@withMiddleware(AuthenticationMiddleware, compressionMiddleware)
uploadFile(@requestBody() fileData: any) {
return this.created("/files/123", fileData);
}
@httpGet("/:id")
@withMiddleware(RateLimitMiddleware)
getFile(@requestParam("id") id: string) {
return this.ok({ id, name: "file.txt" });
}
}Register custom middleware with the Inversify container.
Usage Examples:
import { Container } from "inversify";
const container = new Container();
// Register middleware classes
container.bind<LoggingMiddleware>("LoggingMiddleware").to(LoggingMiddleware);
container.bind<AuthenticationMiddleware>("AuthenticationMiddleware").to(AuthenticationMiddleware);
container.bind<RateLimitMiddleware>("RateLimitMiddleware").to(RateLimitMiddleware);
// Register services that middleware depends on
container.bind<Logger>("Logger").to(ConsoleLogger);
container.bind<AuthService>("AuthService").to(JwtAuthService);
container.bind<CacheService>("CacheService").to(RedisCache);
// Create server with container
const server = new InversifyExpressServer(container);
// Configure global Express middleware
server.setConfig((app) => {
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Note: Controller/method-specific middleware is handled automatically
});Middleware executes in the following order:
setConfig())@controller() or @withMiddleware())setErrorConfig())Usage Examples:
@controller("/api/data", corsMiddleware, LoggingMiddleware) // Middleware 2 & 3
class DataController extends BaseHttpController {
@httpGet("/")
@withMiddleware(AuthenticationMiddleware, RateLimitMiddleware) // Middleware 4 & 5
getData() { // Handler executes 6th
return this.ok({ data: "sample" });
}
}
// Execution order for GET /api/data:
// 1. Global middleware (express.json, etc.)
// 2. corsMiddleware
// 3. LoggingMiddleware
// 4. AuthenticationMiddleware
// 5. RateLimitMiddleware
// 6. getData() method
// 7. Error middleware (if any errors occur)Utility functions for working with middleware metadata.
/**
* Gets middleware metadata for a specific constructor and key
* @param constructor - Target constructor
* @param key - Metadata key
* @returns Array of middleware for the specified key
*/
function getMiddlewareMetadata(
constructor: DecoratorTarget,
key: string
): Middleware[];