The HTTP Server component provides a high-performance HTTP adapter for NestJS using uWebSockets.js. It offers Express-compatible APIs with significantly better performance.
- Overview
- Quick Start
- Configuration
- Server Methods
- Static File Serving
- CORS Configuration
- Performance Tuning
The UwsPlatformAdapter is a drop-in replacement for the default NestJS HTTP adapter (@nestjs/platform-express). It provides:
- Up to 10x faster than Express for HTTP requests
- Lower memory footprint for handling concurrent connections
- Native backpressure handling for streaming responses
- Built-in static file serving with advanced caching
- Full Express compatibility for existing NestJS applications
Replace your existing HTTP adapter in main.ts:
import { NestFactory } from '@nestjs/core';
import { UwsPlatformAdapter } from 'uwestjs';
import { AppModule } from './app.module';
async function bootstrap() {
const adapter = new UwsPlatformAdapter();
const app = await NestFactory.create(AppModule, adapter);
// Initialize the app (sets up routes and middleware)
await app.init();
// Start the server
adapter.listen(3000, () => {
console.log('Application is running on: http://localhost:3000');
});
}
bootstrap();Important: Unlike Express, you must call
app.init()beforeadapter.listen(). This is because uWebSockets.js doesn't implement Node.js EventEmitter interface that NestJS'sapp.listen()expects. See Server Initialization for details.
import { NestFactory } from '@nestjs/core';
import { UwsPlatformAdapter } from 'uwestjs';
import { AppModule } from './app.module';
async function bootstrap() {
const adapter = new UwsPlatformAdapter({
maxBodySize: 10 * 1024 * 1024, // 10MB
trustProxy: true,
etag: 'weak',
bodyParser: {
json: true,
urlencoded: true,
raw: false,
text: false,
},
});
const app = await NestFactory.create(AppModule, adapter);
// Initialize the app
await app.init();
// Start the server
adapter.listen(3000, () => {
console.log('Server started on port 3000');
});
}
bootstrap();Why the different initialization pattern?
Unlike Express or Fastify, uWebSockets.js doesn't implement Node.js's EventEmitter interface. NestJS's app.listen() expects the HTTP server to emit a 'listening' event:
// What NestJS tries to do internally (doesn't work with uWS)
this.httpServer.once('listening', callback); // uWS doesn't have .once()The Solution:
We split initialization into two steps:
app.init()- NestJS sets up routes, middleware, and dependency injectionadapter.listen()- uWS starts the server directly
// Correct pattern for uWS
await app.init();
adapter.listen(3000, callback);This is the standard pattern for uWS adapters - it's not a limitation, but rather the proper way to integrate uWebSockets.js with NestJS's lifecycle.
What happens if you call listen() before init()?
If you forget to call app.init() before adapter.listen(), your routes won't be registered and the server will respond with 404 for all requests:
// WRONG - Routes not registered yet
const adapter = new UwsPlatformAdapter();
const app = await NestFactory.create(AppModule, adapter);
adapter.listen(3000); // Server starts but has no routes!
// CORRECT - Routes registered before listening
const adapter = new UwsPlatformAdapter();
const app = await NestFactory.create(AppModule, adapter);
await app.init(); // Register routes first
adapter.listen(3000); // Now routes are availableSymptoms of missing app.init():
- All requests return 404 Not Found
- No error message in console
- Server appears to be running normally
- Controllers and routes are not registered
Quick fix: Always call await app.init() before adapter.listen().
To run both HTTP and WebSocket on the same port, share the uWS instance:
import { NestFactory } from '@nestjs/core';
import { UwsPlatformAdapter } from 'uwestjs';
import { UwsAdapter } from 'uwestjs/websocket';
import { AppModule } from './app.module';
async function bootstrap() {
// Create HTTP adapter
const httpAdapter = new UwsPlatformAdapter({
maxBodySize: 10 * 1024 * 1024,
});
const app = await NestFactory.create(AppModule, httpAdapter);
// Initialize WebSocket adapter with shared uWS instance
const wsAdapter = httpAdapter.initWebSocketAdapter(app.getHttpServer());
app.useWebSocketAdapter(wsAdapter);
// Initialize the app
await app.init();
// Start server (HTTP + WebSocket on same port)
httpAdapter.listen(3000, () => {
console.log('HTTP + WebSocket server running on port 3000');
});
}
bootstrap();If you need HTTP and WebSocket on different ports:
import { NestFactory } from '@nestjs/core';
import { UwsPlatformAdapter } from 'uwestjs';
import { UwsAdapter } from 'uwestjs/websocket';
import { AppModule } from './app.module';
async function bootstrap() {
// HTTP on port 3000
const httpAdapter = new UwsPlatformAdapter();
const app = await NestFactory.create(AppModule, httpAdapter);
// WebSocket on port 8099
const wsAdapter = new UwsAdapter(app, { port: 8099 });
app.useWebSocketAdapter(wsAdapter);
// Initialize the app
await app.init();
// Start HTTP server
httpAdapter.listen(3000, () => {
console.log('HTTP server running on port 3000');
console.log('WebSocket server running on port 8099');
});
}
bootstrap();Configuration options for the HTTP adapter.
interface PlatformOptions {
// HTTP options
maxBodySize?: number;
trustProxy?: boolean | number | string | string[] | ((ip: string, hopIndex: number) => boolean);
etag?: false | 'weak' | 'strong';
bodyParser?: {
json?: boolean;
urlencoded?: boolean;
raw?: boolean;
text?: boolean;
};
// uWebSockets.js options
key_file_name?: string;
cert_file_name?: string;
passphrase?: string;
dh_params_file_name?: string;
ssl_prefer_low_memory_usage?: boolean;
// CORS options
cors?: CorsOptions;
// Logger
logger?: Logger;
}Maximum request body size in bytes.
Default: 1048576 (1MB)
Example:
new UwsPlatformAdapter({
maxBodySize: 10 * 1024 * 1024, // 10MB
})Trust proxy headers (X-Forwarded-For, X-Forwarded-Proto, X-Forwarded-Host). This setting controls whether the server trusts these headers to determine the client's real IP address, protocol, and hostname.
Security Warning: Incorrect configuration can allow IP spoofing! Only enable this when your application is behind a trusted reverse proxy (nginx, Apache, load balancer, etc.). Never use trustProxy: true in production without understanding the security implications.
Options:
false: Do not trust any proxy (default, safest)true: Trust all proxies (dangerous - only use in development)number: Trust N hops from the clientstring | string[]: Trust specific proxy IPs/CIDRs(ip, hopIndex) => boolean: Custom validation function
Default: false
When to Enable:
- Your app is behind nginx, Apache, or a cloud load balancer
- You need accurate client IP addresses for rate limiting, logging, or geolocation
- You need to detect HTTPS connections when SSL is terminated at the proxy
When to Keep Disabled:
- Your app is directly exposed to the internet
- You're not behind a reverse proxy
- You don't need X-Forwarded-* headers
Examples:
// Development only - trust all proxies (NOT for production!)
new UwsPlatformAdapter({ trustProxy: true })
// Production - trust first proxy (common with single reverse proxy)
new UwsPlatformAdapter({ trustProxy: 1 })
// Production - trust specific proxy IPs (recommended)
new UwsPlatformAdapter({
trustProxy: ['127.0.0.1', '::1', '10.0.0.1']
})
// Production - trust private network proxies
new UwsPlatformAdapter({
trustProxy: (ip, hopIndex) => {
// Trust proxies in private IP ranges
return ip.startsWith('10.') ||
ip.startsWith('192.168.') ||
ip.startsWith('172.16.');
}
})
// Cloud deployment - trust cloud provider's load balancer
new UwsPlatformAdapter({
trustProxy: ['10.0.0.0/8'] // AWS VPC, adjust for your provider
})Security Best Practices:
- Never use
trustProxy: truein production - Always specify exact proxy IPs or use a validation function
- Regularly audit your proxy configuration
- Monitor for suspicious X-Forwarded-For values in logs
Impact on Request Properties:
When trustProxy is enabled, these properties respect X-Forwarded-* headers:
req.ip- Uses X-Forwarded-Forreq.ips- Parses X-Forwarded-For chainreq.protocol- Uses X-Forwarded-Protoreq.hostname- Uses X-Forwarded-Hostreq.secure- Checks X-Forwarded-Proto === 'https'
Enable ETag generation for responses.
Default: 'weak'
Options:
false: Disable ETags'weak': Generate weak ETags (W/"...")'strong': Generate strong ETags
Example:
new UwsPlatformAdapter({
etag: 'weak', // Enable weak ETags
})Configure automatic body parsing for different content types.
Default:
{
json: true,
urlencoded: true,
raw: false,
text: false,
}Example:
new UwsPlatformAdapter({
bodyParser: {
json: true,
urlencoded: true,
raw: true, // Enable raw buffer parsing
text: true, // Enable text parsing
},
})Configure HTTPS server with SSL certificates.
Example:
new UwsPlatformAdapter({
key_file_name: '/path/to/private-key.pem',
cert_file_name: '/path/to/certificate.pem',
passphrase: 'your-passphrase',
})Start the HTTP server on the specified port.
listen(port: number, callback?: (error?: Error) => void): void
listen(port: number, hostname: string, callback?: (error?: Error) => void): voidParameters:
port- Port number to listen on (0-65535)hostname- Optional hostname (default: '0.0.0.0')callback- Optional error-first callback when server starts or fails
Important: You must call app.init() before calling adapter.listen().
Example:
const adapter = new UwsPlatformAdapter();
const app = await NestFactory.create(AppModule, adapter);
await app.init();
// Simple
adapter.listen(3000);
// With error handling
adapter.listen(3000, (error) => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log('Server started on port 3000');
});
// With hostname and error handling
adapter.listen(3000, '127.0.0.1', (error) => {
if (error) {
console.error('Failed to start server:', error);
process.exit(1);
}
console.log('Server started on localhost:3000');
});Close the HTTP server and all connections.
close(): Promise<void>Usage:
You can close the server in two ways:
- Using
app.close()(Recommended) - Closes the entire NestJS application, including the adapter:
// Graceful shutdown - closes app and adapter
process.on('SIGTERM', async () => {
await app.close(); // NestJS calls adapter.close() internally
process.exit(0);
});- Using
adapter.close()(Direct) - Closes only the HTTP server:
// Direct adapter close
process.on('SIGTERM', async () => {
await adapter.close(); // Only closes the HTTP server
process.exit(0);
});Recommendation: Use app.close() for graceful shutdown as it properly cleans up all NestJS resources (modules, providers, connections) before closing the server. Use adapter.close() only if you need to close the HTTP server independently while keeping the NestJS application running.
Get the underlying uWebSockets.js server instance.
getHttpServer(): uWS.TemplatedAppExample:
const adapter = app.getHttpAdapter();
const uwsApp = adapter.getHttpServer();Alias for getHttpServer().
getInstance(): uWS.TemplatedAppGet the adapter type identifier.
getType(): stringReturns: 'uws'
Serve static files with advanced caching, compression, and range request support.
Enable static file serving from a directory.
useStaticAssets(path: string, options?: StaticFileOptions): voidParameters:
path- Directory path to serve files fromoptions- Optional configuration
Example:
// Basic usage
app.useStaticAssets('public');
// With options
app.useStaticAssets('public', {
prefix: '/static',
index: ['index.html', 'index.htm'],
maxAge: '1d', // or milliseconds: 86400000
etag: true,
lastModified: true,
cacheControl: true,
immutable: false,
dotfiles: 'ignore',
redirect: true,
silent: false,
});interface StaticFileOptions {
prefix?: string; // URL prefix (default: '/')
index?: string | string[]; // Index files (default: ['index.html'])
maxAge?: number | string; // Cache max-age: number in milliseconds or string ('1d', '2h', '30m') (default: 0)
etag?: boolean | 'weak' | 'strong'; // Enable ETags: true/'weak' for weak, 'strong' for strong, false to disable (default: true)
lastModified?: boolean; // Send Last-Modified header (default: true)
cacheControl?: boolean; // Send Cache-Control header (default: true)
immutable?: boolean; // Add immutable directive (default: false)
dotfiles?: 'allow' | 'deny' | 'ignore' | 'ignore_files'; // Dotfile handling (default: 'ignore')
redirect?: boolean; // Redirect to trailing slash (default: true)
silent?: boolean; // Suppress logging (default: false)
}// Serve from multiple directories
app.useStaticAssets('public', { prefix: '/public' });
app.useStaticAssets('uploads', { prefix: '/uploads' });
app.useStaticAssets('assets', { prefix: '/assets' });// Cache static assets for 1 year
app.useStaticAssets('public/assets', {
prefix: '/assets',
maxAge: '1y', // or milliseconds: 31536000000
immutable: true, // Add immutable directive
});app.useStaticAssets('docs', {
prefix: '/docs',
index: ['README.md', 'index.html', 'index.htm'],
});if (process.env.NODE_ENV === 'development') {
app.useStaticAssets('public', {
maxAge: 0,
etag: false,
cacheControl: false,
});
}Configure Cross-Origin Resource Sharing for your HTTP server.
Enable CORS with optional configuration.
enableCors(options?: CorsOptions): voidExample:
// Enable CORS for all origins
app.enableCors();
// Specific origin
app.enableCors({
origin: 'https://example.com',
credentials: true,
});
// Multiple origins
app.enableCors({
origin: ['https://example.com', 'https://app.example.com'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization'],
});
// Dynamic origin validation
app.enableCors({
origin: (origin) => {
return origin?.endsWith('.example.com') ?? false;
},
credentials: true,
});interface CorsOptions {
origin?: string | string[] | ((origin: string | null) => boolean);
credentials?: boolean;
methods?: string | string[];
allowedHeaders?: string | string[];
exposedHeaders?: string | string[];
maxAge?: number;
}See CORS.md for detailed CORS documentation.
Adjust based on your application needs:
// For APIs with small payloads
new UwsPlatformAdapter({
maxBodySize: 100 * 1024, // 100KB
})
// For file uploads
new UwsPlatformAdapter({
maxBodySize: 50 * 1024 * 1024, // 50MB
})Optimize static file serving:
// Production: aggressive caching
app.useStaticAssets('public', {
maxAge: '1y', // or milliseconds: 31536000000
immutable: true,
etag: true,
lastModified: true,
});
// Development: no caching
app.useStaticAssets('public', {
maxAge: 0,
etag: false,
cacheControl: false,
});The adapter automatically uses a worker pool for static file operations. The default pool size is CPU-aware:
// Default: Math.max(1, Math.min(4, os.cpus().length - 1))
// On 8-core machine: 4 workers
// On 2-core machine: 1 workerFor HTTPS, consider:
new UwsPlatformAdapter({
key_file_name: '/path/to/key.pem',
cert_file_name: '/path/to/cert.pem',
ssl_prefer_low_memory_usage: true, // Reduce memory usage
})import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.enableCors();
app.useStaticAssets('public');
await app.listen(3000);
}
bootstrap();import { NestFactory } from '@nestjs/core';
import { UwsPlatformAdapter } from 'uwestjs';
import { AppModule } from './app.module';
async function bootstrap() {
const adapter = new UwsPlatformAdapter({
maxBodySize: 10 * 1024 * 1024,
trustProxy: true,
});
const app = await NestFactory.create(AppModule, adapter);
app.enableCors();
app.useStaticAssets('public');
// Key difference: init() before listen()
await app.init();
adapter.listen(3000, () => {
console.log('Server running on port 3000');
});
}
bootstrap();Key Changes:
- Create adapter instance separately to access
listen()method - Call
app.init()before starting the server - Use
adapter.listen()instead ofapp.listen()
Your controllers, services, and business logic remain unchanged!
- Set appropriate body size limits based on your use case
- Enable trustProxy when behind a reverse proxy
- Use long-term caching for static assets in production
- Enable compression for large responses (handled automatically)
- Configure CORS properly - never use
origin: '*'withcredentials: true - Use ETags for efficient caching
- Monitor backpressure for streaming responses
- Request - HTTP Request object documentation
- Response - HTTP Response object documentation
- CORS - Detailed CORS documentation
- Static Files - Advanced static file serving