npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@soapjs/soap-express

v0.5.1

Published

HTTP-focused Express.js integration for @soapjs/soap framework with dependency injection support

Downloads

1,159

Readme

@soapjs/soap-express

HTTP-focused Express.js integration for the @soapjs/soap framework 0.6.5+ with modern dependency injection, authentication, and advanced routing capabilities.

Table of Contents

Features

  • 🚀 HTTP-Only Focus: Clean separation from WebSocket functionality
  • 🔧 Modern DI System: Full integration with the @soapjs/soap DI container
  • 🎯 Decorator-Based: Clean, declarative API using decorators
  • 🛡️ Type Safety: Full TypeScript support
  • 🔐 Authentication: Built-in auth decorators and middleware factory
  • 📨 CQRS & Events: @CommandHandler / @QueryHandler / @EventHandler with bus wiring
  • 📦 Middleware Support: Built-in middleware for validation, CORS, rate limiting, etc.
  • 🔄 RouteIO: Request/response transformation system
  • Express Integration: Seamless integration with Express.js
  • 🎨 Flexible Architecture: Use DI with decorators or direct container access
  • 🔌 Plugin System: Extensible plugin architecture
  • 📚 Auto Documentation: Automatic API documentation generation

Installation

npm install @soapjs/soap-express @soapjs/soap express

Quick Start

import { SoapExpressApp, Controller, Get } from '@soapjs/soap-express';
import { DI, Injectable, Inject } from '@soapjs/soap';

// Service with dependency injection
@Injectable()
class UserService {
  async getUsers() {
    return [{ id: 1, name: 'John Doe' }];
  }
}

// Controller
@Controller('/api/users')
class UserController {
  constructor(@Inject('UserService') private userService: UserService) {}

  @Get('/')
  async getUsers(req: any, res: any) {
    const users = await this.userService.getUsers();
    res.json(users);
  }
}

// App setup with new DI system
const app = new SoapExpressApp();
DI.bind('UserService').toClass(UserService);
app.registerController(UserController);

await app.start(3000);

Core Concepts

1. SoapExpressApp

The main application class that wraps Express.js and provides integration with @soapjs/soap.

const app = new SoapExpressApp({
  container?: DIContainer,           // DI container (optional, uses global by default)
  errorHandler?: Function,           // Custom error handler
  errorHandlerOptions?: object,      // Error handler options
  middlewares?: any[],              // Global middlewares
  cors?: object,                    // CORS options
  rateLimit?: object,               // Rate limiting options
  logging?: object                  // Logging options
});

2. Controllers

Controllers are classes that handle HTTP requests. They use decorators to define routes and can inject services.

@Controller('/api/users')
class UserController {
  // Route handlers here
}

3. Services

Services contain business logic and can be injected into controllers.

@Injectable()
class UserService {
  // Business logic here
}

Controllers & Routes

Basic Route Decorators

import { Controller, Get, Post, Put, Delete, Patch } from '@soapjs/soap-express';

@Controller('/api/users')
class UserController {
  @Get('/')
  async getUsers(req: Request, res: Response) {
    // Handle GET /api/users
  }

  @Get('/:id')
  async getUser(req: Request, res: Response) {
    // Handle GET /api/users/:id
  }

  @Post('/')
  async createUser(req: Request, res: Response) {
    // Handle POST /api/users
  }

  @Put('/:id')
  async updateUser(req: Request, res: Response) {
    // Handle PUT /api/users/:id
  }

  @Delete('/:id')
  async deleteUser(req: Request, res: Response) {
    // Handle DELETE /api/users/:id
  }
}

Advanced Route Options

Routes can be configured with advanced options:

@Get('/', {
  cors: {
    origin: ['http://localhost:3000', 'https://myapp.com'],
    credentials: true
  },
  rateLimit: {
    maxRequests: 100,
    windowMs: 15 * 60 * 1000 // 15 minutes
  },
  cache: {
    ttl: 300 // 5 minutes
  },
  validation: {
    request: {
      schema: {
        name: { type: 'string', required: true },
        email: { type: 'string', required: true, format: 'email' }
      }
    }
  }
})
async getUsers(req: Request, res: Response) {
  // Route with advanced options
}

Dependency Injection

The framework integrates with the @soapjs/soap 0.6.5+ DI container, providing modern dependency resolution and injection with the new DI.bind().toClass() API.

Using Decorators (Recommended)

import { Injectable, Inject } from '@soapjs/soap';

@Injectable()
class UserService {
  async getUsers() {
    return [{ id: 1, name: 'John Doe' }];
  }
}

@Injectable()
class EmailService {
  async sendEmail(to: string, subject: string, body: string) {
    // Send email logic
  }
}

@Controller('/api/users')
class UserController {
  constructor(
    @Inject('UserService') private userService: UserService,
    @Inject('EmailService') private emailService: EmailService
  ) {}

  @Post('/')
  async createUser(req: Request, res: Response) {
    const user = await this.userService.createUser(req.body);
    await this.emailService.sendEmail(user.email, 'Welcome!', 'Welcome to our app!');
    res.json(user);
  }
}

// Registration using new DI system
DI.bind('UserService').toClass(UserService);
DI.bind('EmailService').toClass(EmailService);

Using Container Directly

import { DI } from '@soapjs/soap';

@Controller('/api/users')
class UserController {
  @Get('/')
  async getUsers(req: Request, res: Response) {
    const userService = DI.get('UserService');
    const users = await userService.getUsers();
    res.json(users);
  }
}

// Registration using new DI system
DI.bind('UserService').toClass(UserService);
DI.bind('config').toValue({ apiKey: 'secret' });
DI.bind('logger').toFactory(() => new Logger());

Service Registration Methods

// Class registration (new DI system)
DI.bind('UserService').toClass(UserService);

// Value registration
DI.bind('config').toValue({ apiKey: 'secret' });

// Factory registration
DI.bind('logger').toFactory(() => new Logger());

// Interface binding
DI.bind('IUserRepository').toInterface(UserRepository);

// Abstract binding
DI.bind('BaseService').toAbstract(UserService);

Authentication & Authorization

Auth Decorators

import { Auth, AdminOnly, RolesOnly, Public, SelfOnly } from '@soapjs/soap-express';

@Controller('/api/users')
class UserController {
  @Get('/')
  @Public() // No authentication required
  async getUsers(req: Request, res: Response) {
    // Public endpoint
  }

  @Get('/profile')
  @Auth('jwt') // Simple strategy name
  async getProfile(req: Request, res: Response) {
    // Requires JWT authentication
    res.json({ user: req.user });
  }

  @Post('/')
  @Auth({ 
    strategy: 'jwt', 
    roles: { allow: ['admin', 'user'] } 
  })
  async createUser(req: Request, res: Response) {
    // Requires JWT + specific roles
  }

  @Get('/admin')
  @AdminOnly('jwt') // Requires admin role
  async adminOnly(req: Request, res: Response) {
    // Admin only endpoint
  }

  @Get('/:id')
  @SelfOnly('jwt') // Only resource owner can access
  async getUserById(req: Request, res: Response) {
    // User can only access their own data
  }

  @Post('/:id/update')
  @RolesOnly(['admin', 'moderator'], 'jwt') // Multiple roles
  async updateUser(req: Request, res: Response) {
    // Requires specific roles
  }
}

Auth Strategy Registration

// Auth strategies come from @soapjs/soap-auth.

// Register ALL HTTP strategies from a SoapAuth-compatible provider in one call.
// Accepts any object exposing listStrategies(type) + getStrategy(name, type),
// so soap-express needs no direct dependency on soap-auth.
app.registerAuth(soapAuth);

// ...or register a single strategy directly:
// app.registerAuthStrategy(new JWTStrategy({
//   secret: process.env.JWT_SECRET || 'your-secret-key',
//   algorithms: ['HS256'],
//   issuer: 'your-app',
//   audience: 'your-users'
// }));

// app.registerAuthStrategy(new LocalStrategy({
//   usernameField: 'email',
//   validateUser: async (email, password) => {
//     const user = await userService.findByEmail(email);
//     if (user && await bcrypt.compare(password, user.password)) {
//       return user;
//     }
//     return null;
//   }
// }));

CQRS & Domain Events

soap-express ships decorators that register CQRS handlers and domain-event consumers in the DI container at decoration time. wireCqrs (enabled via bootstrap({ cqrs: true })) connects command/query handlers to in-memory buses.

Commands & Queries

import { CommandHandler, QueryHandler } from '@soapjs/soap-express/cqrs';
import { BaseCommand, BaseQuery } from '@soapjs/soap/cqrs';
import { Inject } from '@soapjs/soap';

export class CreateUserCommand extends BaseCommand {
  constructor(public readonly email: string) { super(); }
}

@CommandHandler(CreateUserCommand)
export class CreateUserHandler {
  constructor(@Inject('UserRepository') private readonly repo: UserRepository) {}
  async handle(cmd: CreateUserCommand): Promise<Result<User>> { /* ... */ }
}

With cqrs: true, CommandBus and QueryBus are bound in the container and every decorated handler is registered. Controllers inject the buses and dispatch:

@Inject('CommandBus') private readonly commandBus: CommandBus;
const result = await this.commandBus.dispatch(new CreateUserCommand(email));

Domain Events

import { EventHandler, IEventHandler } from '@soapjs/soap-express/cqrs';
import { BaseDomainEvent } from '@soapjs/soap/domain';

export class UserCreatedEvent extends BaseDomainEvent { /* ... */ }

@EventHandler(UserCreatedEvent)
export class SendWelcomeEmail implements IEventHandler<UserCreatedEvent> {
  async handle(event: UserCreatedEvent): Promise<void> { /* ... */ }
}

Multiple handlers per event (fan-out): since 0.3.1 the default DI token is EventHandler:<eventName>:<handlerClass>, so several handlers can subscribe to the same event without colliding. (Before 0.3.1 the token was the event name alone, so a second handler silently overwrote the first.) Pass { token } to override it.

Note: @EventHandler only registers handlers in DecoratorRegistry — unlike wireCqrs, soap-express does not auto-wire a domain-event bus. Dispatch is up to you: read DecoratorRegistry.getEventHandlers() and bind/subscribe them to your bus (e.g. an in-memory bus that routes by event type).

Request/Response Transformation

Using @CallUseCase

The @CallUseCase decorator allows you to delegate route handling to a use case, keeping controllers clean. Important: Use cases should follow Clean Architecture principles - they receive input and return Result<output>, never directly handle Request or Response objects.

Flow: RequestRouteIO.from()UseCase.execute(input)Result<output>RouteIO.to()Response

import { CallUseCase } from '@soapjs/soap-express';

@Injectable()
class GetUsersUseCase {
  constructor(@Inject('UserService') private userService: UserService) {}

  async execute(input: { page?: number; limit?: number }) {
    return await this.userService.getUsers(input);
  }
}

@Controller('/api/users')
class UserController {
  @Get('/')
  @CallUseCase(GetUsersUseCase)
  @RouteIO({
    from: (req: Request) => ({ page: req.query.page, limit: req.query.limit }),
    to: (res: Response, result: any) => {
      if (result.isSuccess()) {
        res.json({ success: true, data: result.content });
      } else {
        res.status(500).json({ success: false, error: result.failure!.error.message });
      }
    }
  })
  async getUsers() {
    // Method body ignored - RouteIO transforms Request → input, 
    // GetUsersUseCase.execute(input) → result, RouteIO transforms result → Response
  }
}

Using RouteIO for Data Transformation

RouteIO provides powerful request/response transformation:

import { RouteIO, ExpressIO } from '@soapjs/soap-express';

// 1. Using mapping functions
@Post('/')
@RouteIO({
  from: (req: Request) => ({
    name: req.body.name,
    email: req.body.email,
    // Transform request data
  }),
  to: (res: Response, result: any) => {
    res.json({
      success: true,
      data: result,
      timestamp: new Date().toISOString()
    });
  }
})
async createUser() {
  // Use case receives transformed data
}

// 2. Using ExpressIO class
class UserIO implements ExpressIO {
  from<T = Request>(source: T) {
    const req = source as Request;
    return {
      name: req.body.name,
      email: req.body.email,
      // Custom transformation logic
    };
  }

  to<T = Response>(result: any, target: T) {
    const res = target as Response;
    res.json({
      success: true,
      data: result,
      meta: {
        timestamp: new Date().toISOString(),
        version: '1.0'
      }
    });
  }
}

@Post('/')
@RouteIO(new UserIO())
async createUser() {
  // Uses UserIO for transformation
}

Advanced Routing

Using @soapjs/soap Route System

import { Route, GetRoute, PostRoute, RouteGroup, RouteRegistry } from '@soapjs/soap';

// Individual routes
const userRoute = new GetRoute('/api/users/:id', {
  cors: { origin: true },
  rateLimit: { maxRequests: 100, windowMs: 60000 },
  roles: { authenticatedOnly: true }
});

const createUserRoute = new PostRoute('/api/users', {
  validation: {
    request: {
      schema: {
        name: { type: 'string', required: true },
        email: { type: 'string', required: true, format: 'email' }
      }
    }
  },
  roles: { allow: ['admin', 'user'] }
});

// Route groups
const userGroup = new RouteGroup('/api/v2/users', {
  cors: { origin: ['https://myapp.com'] },
  rateLimit: { maxRequests: 200, windowMs: 60000 }
});

userGroup.addRoute(userRoute);
userGroup.addRoute(createUserRoute);

// Registration
app.registerRoute(userRoute);
app.registerRouteGroup(userGroup);

Route Registry

import { RouteRegistry } from '@soapjs/soap';

const registry = app.getRouteRegistry();

// Add routes to registry
registry.addRoute(userRoute);
registry.addRouteGroup(userGroup);

// Get all routes
const allRoutes = registry.getAllRoutes();

// Get routes by method
const getRoutes = registry.getRoutesByMethod('GET');

Middleware System

Built-in Middleware

import { 
  AuthMiddleware, 
  ValidationMiddleware, 
  CorsMiddleware, 
  RateLimitMiddleware,
  LoggingMiddleware,
  CacheMiddleware 
} from '@soapjs/soap-express';

// Global middleware
app.use(new CorsMiddleware({
  origin: ['http://localhost:3000'],
  credentials: true
}));

app.use(new RateLimitMiddleware({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100 // limit each IP to 100 requests per windowMs
}));

app.use(new LoggingMiddleware({
  level: 'info',
  format: 'combined'
}));

Custom Middleware

import { Middleware } from '@soapjs/soap-express';

class CustomMiddleware implements Middleware {
  async execute(req: Request, res: Response, next: NextFunction) {
    // Custom logic
    console.log('Custom middleware executed');
    next();
  }
}

app.use(new CustomMiddleware());

Route-specific Middleware

@Get('/')
@Middleware(new CustomMiddleware())
@Middleware(new ValidationMiddleware({ schema: userSchema }))
async getUsers(req: Request, res: Response) {
  // Route with specific middleware
}

Error Handling

Global Error Handler

const app = new SoapExpressApp({
  errorHandler: (error: Error, req: Request, res: Response, next: NextFunction) => {
    console.error('Global error:', error);
    res.status(500).json({ 
      error: 'Internal Server Error',
      message: error.message 
    });
  },
  errorHandlerOptions: {
    includeStack: process.env.NODE_ENV === 'development',
    includeRequest: true
  }
});

Route-specific Error Handling

@Get('/')
@ErrorHandler((error: Error, req: Request, res: Response) => {
  if (error.name === 'ValidationError') {
    res.status(400).json({ error: 'Validation failed', details: error.message });
  } else {
    res.status(500).json({ error: 'Internal Server Error' });
  }
})
async getUsers(req: Request, res: Response) {
  // Route with custom error handling
}

Use Case Error Handling

@Injectable()
class GetUsersUseCase {
  async execute(input: { page?: number; limit?: number }) {
    try {
      return await this.userService.getUsers(input);
    } catch (error) {
      throw new Error(`Failed to get users: ${error.message}`);
    }
  }
}

Best Practices

1. Service Architecture

// ✅ Good: Separate concerns
@Injectable()
class UserService {
  async getUsers() {
    return await this.userRepository.findAll();
  }
}

@Injectable()
class UserRepository {
  async findAll() {
    // Database logic
  }
}

// ❌ Bad: Mixed concerns
@Injectable()
class UserService {
  async getUsers() {
    // Database logic mixed with business logic
    const users = await db.query('SELECT * FROM users');
    return users.map(user => ({ ...user, fullName: `${user.firstName} ${user.lastName}` }));
  }
}

2. Use Case Pattern

// ✅ Good: Use cases for complex operations
@Injectable()
class CreateUserUseCase {
  constructor(
    @Inject('UserService') private userService: UserService,
    @Inject('EmailService') private emailService: EmailService,
    @Inject('ValidationService') private validationService: ValidationService
  ) {}

  async execute(input: CreateUserInput) {
    // Validate input
    await this.validationService.validateUser(input);
    
    // Create user
    const user = await this.userService.createUser(input);
    
    // Send welcome email
    await this.emailService.sendWelcomeEmail(user.email);
    
    return user;
  }
}

@Controller('/api/users')
class UserController {
  @Post('/')
  @CallUseCase(CreateUserUseCase)
  async createUser() {
    // Clean controller - use case handles everything
  }
}

3. Error Handling

// ✅ Good: Specific error types
class UserNotFoundError extends Error {
  constructor(userId: string) {
    super(`User with ID ${userId} not found`);
    this.name = 'UserNotFoundError';
  }
}

@Injectable()
class GetUserUseCase {
  async execute(input: { id: string }) {
    const user = await this.userService.getUserById(input.id);
    if (!user) {
      throw new UserNotFoundError(input.id);
    }
    return user;
  }
}

4. Authentication

// ✅ Good: Clear auth requirements
@Controller('/api/users')
class UserController {
  @Get('/')
  @Public() // Explicitly public
  async getUsers() { }

  @Get('/profile')
  @Auth('jwt') // Simple auth
  async getProfile() { }

  @Post('/')
  @Auth({ 
    strategy: 'jwt', 
    roles: { allow: ['admin', 'user'] } 
  }) // Complex auth
  async createUser() { }
}

5. Data Transformation

// ✅ Good: Use RouteIO for transformation
  @Post('/')
  @CallUseCase(CreateUserUseCase)
  @RouteIO({
    from: (req: Request) => ({
      name: req.body.name,
      email: req.body.email.toLowerCase().trim()
    }),
    to: (res: Response, result: any) => {
      if (result.isSuccess()) {
        res.status(201).json({
          success: true,
          data: result.content,
          timestamp: new Date().toISOString()
        });
      } else {
        res.status(400).json({
          success: false,
          error: result.failure!.error.message,
          timestamp: new Date().toISOString()
        });
      }
    }
  })
  async createUser() { }

API Reference

SoapExpressApp

Constructor Options

interface SoapExpressOptions {
  container?: DIContainer;
  errorHandler?: (error: Error, req: Request, res: Response, next: NextFunction) => void;
  errorHandlerOptions?: {
    includeStack?: boolean;
    includeRequest?: boolean;
    logger?: (error: Error, req: Request, res: Response) => void;
    sentry?: (error: Error, req: Request, res: Response) => void;
    custom?: (error: Error, req: Request, res: Response) => void;
  };
  middlewares?: any[];
  cors?: any;
  rateLimit?: any;
  logging?: any;
}

Methods

  • registerController(controller) - Register controller(s)
  • registerRouter(router) - Register router
  • registerRoute(route) - Register individual route
  • registerRouteGroup(group) - Register route group
  • registerMiddleware(middleware, ready?) - Register middleware
  • registerAuthStrategy(strategy) - Register a single auth strategy
  • registerAuth(provider) - Register all HTTP strategies from a soap-auth-compatible provider
  • getRouteRegistry() - Get route registry
  • getMiddlewareRegistry() - Get middleware registry
  • getAuthRegistry() - Get auth registry
  • getAuthMiddlewareFactory() - Get auth middleware factory
  • start(port) - Start HTTP server
  • healthCheck() - Add health check endpoint
  • getApp() - Get Express app instance
  • getServer() - Get HTTP server instance
  • getContainer() - Get DI container

Decorators

Route Decorators

  • @Controller(basePath) - Mark class as controller
  • @Get(path, options?) - GET route
  • @Post(path, options?) - POST route
  • @Put(path, options?) - PUT route
  • @Delete(path, options?) - DELETE route
  • @Patch(path, options?) - PATCH route
  • @Head(path, options?) - HEAD route
  • @Options(path, options?) - OPTIONS route
  • @Trace(path, options?) - TRACE route
  • @Connect(path, options?) - CONNECT route
  • @All(path, options?) - All methods route

Auth Decorators

  • @Auth(strategy | options) - Authentication decorator
  • @AdminOnly(strategy?) - Admin only access
  • @RolesOnly(roles, strategy?) - Specific roles only
  • @SelfOnly(strategy?) - Resource owner only
  • @Public() - Public endpoint (no auth)

CQRS Decorators

  • @CommandHandler(commandType, options?) - Register a command handler
  • @QueryHandler(queryType, options?) - Register a query handler
  • @EventHandler(eventType, options?) - Register a domain-event handler (pass { token } for multiple handlers per event)

Other Decorators

  • @CallUseCase(useCaseClass) - Delegate to use case
  • @RouteIO(ioOrMapping) - Request/response transformation
  • @Middleware(middleware) - Route-specific middleware
  • @ErrorHandler(handler) - Route-specific error handling

Types

// Auth types (from @soapjs/soap)
interface AuthUser {
  id: string | number;
  email?: string;
  username?: string;
  roles?: string[];
  permissions?: string[];
  [key: string]: any;
}

interface AuthRequest extends Request {
  user?: AuthUser;
  auth?: {
    token?: string;
    type?: string;
    payload?: any;
  };
  session?: any;
  sessionID?: string;
}

interface RoleConfig {
  authenticatedOnly?: boolean;
  allow?: string[];
  deny?: string[];
  selfOnly?: boolean | ((user: AuthUser, resourceId: string) => boolean);
  customCheck?: (user: AuthUser, req: AuthRequest) => boolean | Promise<boolean>;
}

// Route options
interface RouteAdditionalOptions {
  cors?: RouteCorsOptions;
  security?: RouteSecurityOptions;
  rateLimit?: RouteRateLimitOptions;
  validation?: RouteValidationOptions;
  session?: any;
  cache?: any;
  logging?: any;
  analytics?: any;
  audit?: any;
  roles?: RoleConfig;
  middlewares?: any;
  compression?: any;
}

Migration Guide

From Previous Versions

Removed Features

  • WebSocket support (moved to @soapjs/soap-node-socket)
  • Socket.IO integration
  • WebSocket decorators and controllers
  • Old DI system (DI.registerClass, DI.registerValue, etc.)

New Features in 0.6.5+

  • Modern DI System: New DI.bind().toClass() API
  • Plugin System: Extensible plugin architecture
  • CQRS Decorators: Command, Query, Event handlers
  • Auto Documentation: Automatic API documentation generation
  • Enhanced Type Safety: Full TypeScript support
  • Better Error Handling: Improved error management
  • HTTP-only Focus: Better performance
  • Auth Decorators: Built-in authentication
  • Advanced Routing: @soapjs/soap Route system integration

DI System Migration

// Old way (deprecated)
DI.registerClass(UserService, 'UserService', { scope: Scope.SINGLETON });
DI.registerValue('API_KEY', 'your-api-key');
DI.registerFactory('Database', () => new Database());

// New way (0.6.5+)
DI.bind('UserService').toClass(UserService, { scope: Scope.SINGLETON });
DI.bind('API_KEY').toValue('your-api-key');
DI.bind('Database').toFactory(() => new Database());

Breaking Changes

  • SoapExpressOptions.container is now optional (uses global container by default)
  • WebSocket-related methods removed from SoapExpressApp
  • WebSocket decorators removed
  • Auth system refactored to use @soapjs/soap interfaces

Metrics System

The framework includes a built-in metrics system that's exporter-agnostic. You can use any metrics client by implementing the MetricsClient interface.

Basic Usage

import { SoapExpressApp, MetricsConfig, ConsoleMetricsClient } from '@soapjs/soap-express';

const app = new SoapExpressApp({});

// Enable built-in metrics
app.useMetrics({
  enabled: true,
  metrics: {
    responseTime: true,
    requestCount: true,
    errorRate: true,
    memoryUsage: true,
    cpuUsage: true
  },
  client: new ConsoleMetricsClient(), // or your custom client
  collectInterval: 30000, // 30 seconds
  customLabels: {
    service: 'my-api',
    version: '1.0.0'
  }
});

Custom Metrics Client

import { MetricsClient } from '@soapjs/soap-express';

class PrometheusClient implements MetricsClient {
  counter(name: string, value: number = 1, labels?: Record<string, string | number>): void {
    // Your Prometheus implementation
  }

  histogram(name: string, value: number, labels?: Record<string, string | number>): void {
    // Your Prometheus implementation
  }

  gauge(name: string, value: number, labels?: Record<string, string | number>): void {
    // Your Prometheus implementation
  }

  summary(name: string, value: number, labels?: Record<string, string | number>): void {
    // Your Prometheus implementation
  }
}

app.useMetrics({
  enabled: true,
  metrics: {
    responseTime: true,
    requestCount: true,
    errorRate: true,
    memoryUsage: true,
    cpuUsage: true
  },
  client: new PrometheusClient()
});

Custom Metrics

// Get the metrics collector
const collector = app.getMetricsCollector();

// Record custom metrics
collector!.counter('api_requests_total', 1, { method: 'GET', route: '/api/users' });
collector!.histogram('response_time_seconds', 0.5, { route: '/api/users' });
collector!.gauge('active_connections', 25);
collector!.summary('database_query_time', 0.1, { table: 'users' });

Built-in Metrics

The system automatically collects:

  • Response Time: http_request_duration_seconds (histogram)
  • Request Count: http_requests_total (counter)
  • Error Rate: http_errors_total (counter)
  • Memory Usage: process_memory_usage_bytes, process_memory_total_bytes (gauges)
  • CPU Usage: process_cpu_usage_microseconds (gauge)

Memory Monitoring System

The framework includes a comprehensive memory monitoring system with automatic leak detection and threshold monitoring.

Basic Usage

import { SoapExpressApp, MemoryMonitoringConfig } from '@soapjs/soap-express';

const app = new SoapExpressApp({});

// Enable memory monitoring
app.useMemoryMonitoring({
  enabled: true,
  interval: 30000, // Check every 30s
  threshold: {
    used: 512 * 1024 * 1024, // 512MB
    percentage: 80, // 80%
    heapUsed: 256 * 1024 * 1024, // 256MB
    rss: 512 * 1024 * 1024 // 512MB
  },
  leakDetection: {
    enabled: true,
    consecutiveGrowths: 3,
    growthThreshold: 10, // 10% growth
    maxHistory: 20
  },
  onLeak: (info) => {
    console.warn('Memory leak detected:', info);
    // Auto-restart or alert
  },
  onThreshold: (info) => {
    console.warn('Memory threshold exceeded:', info);
  }
});

Simple Configuration

import { createMemoryConfig } from '@soapjs/soap-express';

// Use helper function for simple setup
const config = createMemoryConfig({
  threshold: '512MB',
  interval: 30000,
  onLeak: (info) => {
    console.warn('Memory leak detected:', info.severity);
  }
});

app.useMemoryMonitoring(config);

Memory Monitoring Features

  • Automatic Leak Detection: Detects memory leaks based on consecutive growth patterns
  • Threshold Monitoring: Alerts when memory usage exceeds configured limits
  • Memory History: Tracks memory usage over time
  • Severity Levels: Categorizes leaks as low, medium, high, or critical
  • Custom Labels: Add custom labels for better monitoring context
  • Force GC: Option to force garbage collection when issues are detected

Memory Information

The system provides detailed memory information:

const monitor = app.getMemoryMonitor();
const stats = monitor!.getStats();
const summary = monitor!.getSummary();

console.log('Current memory:', stats.current);
console.log('Memory status:', summary.status);
console.log('Detected leaks:', summary.leaks);

Memory Thresholds

You can set thresholds for different memory metrics:

  • Used Memory: Total memory used by the process
  • Memory Percentage: Percentage of total system memory
  • Heap Used: Memory used by the JavaScript heap
  • RSS: Resident Set Size (physical memory)

Leak Detection

The system detects memory leaks by monitoring consecutive memory growth patterns:

  • Consecutive Growths: Number of consecutive growths to trigger detection
  • Growth Threshold: Minimum growth percentage to consider
  • Severity Levels: Automatic severity classification based on growth rate

Security System

The framework includes a comprehensive security system with built-in protection against common web vulnerabilities, implemented without external dependencies.

Basic Usage

import { SoapExpressApp, SecurityConfig } from '@soapjs/soap-express';

const app = new SoapExpressApp({});

// Enable security features
app.useSecurity({
  enabled: true,
  headers: {
    enabled: true,
    headers: {
      contentSecurityPolicy: "default-src 'self'",
      frameOptions: 'DENY',
      contentTypeOptions: true,
      xssProtection: '1; mode=block',
      referrerPolicy: 'strict-origin-when-cross-origin',
      strictTransportSecurity: 'max-age=31536000; includeSubDomains'
    }
  },
  csrf: {
    enabled: true,
    secret: 'your-secret-key-change-in-production',
    cookieName: '_csrf',
    cookieOptions: {
      httpOnly: true,
      secure: false, // Set to true in production with HTTPS
      sameSite: 'strict',
      maxAge: 3600000 // 1 hour
    }
  },
  sanitization: {
    enabled: true,
    options: {
      stripHtml: true,
      escapeSql: true,
      escapeHtml: true,
      preventPathTraversal: true,
      validateFileUploads: true,
      maxFileSize: 10 * 1024 * 1024, // 10MB
      allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif']
    }
  }
});

Security Headers

The system automatically sets security headers to protect against common attacks:

  • Content Security Policy (CSP): Prevents XSS attacks
  • X-Frame-Options: Prevents clickjacking
  • X-Content-Type-Options: Prevents MIME type sniffing
  • X-XSS-Protection: Enables browser XSS filtering
  • Referrer-Policy: Controls referrer information
  • Strict-Transport-Security (HSTS): Enforces HTTPS
  • Permissions-Policy: Controls browser features
  • Cross-Origin Policies: Controls cross-origin requests

CSRF Protection

Built-in CSRF protection without external dependencies:

// CSRF token is automatically generated and validated
app.getApp().post('/api/users', (req, res) => {
  // CSRF token is available in res.locals.csrfToken
  res.json({ csrfToken: res.locals.csrfToken });
});

// Get CSRF token endpoint
app.getApp().get('/api/csrf-token', (req, res) => {
  const securityMiddleware = app.getSecurityMiddleware();
  const token = securityMiddleware!.getCSRFMiddleware().generateToken();
  res.json({ csrfToken: token });
});

Input Sanitization

Automatic sanitization of all request data:

// HTML sanitization
app.getApp().post('/api/content', (req, res) => {
  // req.body is automatically sanitized
  // <script> tags are stripped, HTML entities are escaped
  res.json({ content: req.body.content });
});

// Custom sanitizers
app.useSecurity({
  sanitization: {
    enabled: true,
    options: {
      stripHtml: true,
      escapeHtml: true,
      preventPathTraversal: true
    },
    customSanitizers: {
      'email': (value) => value.toLowerCase().trim(),
      'phone': (value) => value.replace(/[^\d+\-\(\)\s]/g, ''),
      'username': (value) => value.replace(/[^a-zA-Z0-9_-]/g, '')
    }
  }
});

Security Presets

Pre-configured security levels:

import { securityPresets } from '@soapjs/soap-express';

// Strict security for production
app.useSecurity({
  headers: securityPresets.strict,
  csrf: { enabled: true, secret: 'production-secret' },
  sanitization: { enabled: true, options: { stripHtml: true } }
});

// Balanced security for development
app.useSecurity({
  headers: securityPresets.balanced,
  csrf: { enabled: false },
  sanitization: { enabled: true, options: { stripHtml: false } }
});

// Minimal security
app.useSecurity({
  headers: securityPresets.minimal,
  csrf: { enabled: false },
  sanitization: { enabled: false }
});

Security Monitoring

Track security violations and get statistics:

const securityMiddleware = app.getSecurityMiddleware();

// Get security violations
const violations = securityMiddleware.getSecurityViolations();

// Get security statistics
const stats = securityMiddleware.getSecurityStats();

// Clear violations
securityMiddleware.clearViolations();

Security Endpoints

Built-in security endpoints for monitoring:

import { createSecurityEndpoints } from '@soapjs/soap-express';

const securityMiddleware = app.getSecurityMiddleware();
const endpoints = createSecurityEndpoints(securityMiddleware);

// Security status endpoint
app.getApp().get('/security/status', endpoints.status);

// CSRF token endpoint
app.getApp().get('/security/csrf-token', endpoints.csrfToken);

// Security violations endpoint
app.getApp().get('/security/violations', endpoints.violations);

File Upload Security

Automatic validation of file uploads:

app.useSecurity({
  sanitization: {
    enabled: true,
    options: {
      validateFileUploads: true,
      maxFileSize: 10 * 1024 * 1024, // 10MB
      allowedMimeTypes: ['image/jpeg', 'image/png', 'image/gif', 'application/pdf']
    }
  }
});

// File uploads are automatically validated
app.getApp().post('/api/upload', (req, res) => {
  // req.files is validated and sanitized
  res.json({ files: req.files });
});

Security Features

  • No External Dependencies: Built using only Node.js built-in modules
  • Automatic Protection: All requests are automatically protected
  • Configurable: Fine-grained control over security features
  • Monitoring: Track security violations and statistics
  • Presets: Pre-configured security levels for different environments
  • Custom Sanitizers: Add your own sanitization logic
  • File Upload Validation: Automatic validation of uploaded files
  • CSRF Protection: Built-in CSRF token generation and validation

WebSocket Support

For WebSocket functionality, use the separate package:

npm install @soapjs/soap-node-socket

This provides Socket.IO and WebSocket support with the same clean architecture.

License

MIT