@naman_deep_singh/server-utils 1.0.8 → 1.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +401 -11
- package/dist/{index.d.ts → cjs/index.d.ts} +1 -1
- package/dist/{index.js → cjs/index.js} +3 -1
- package/dist/{middleware.d.ts → cjs/middleware.d.ts} +2 -0
- package/dist/{middleware.js → cjs/middleware.js} +137 -10
- package/dist/{server.d.ts → cjs/server.d.ts} +1 -0
- package/dist/{server.js → cjs/server.js} +119 -4
- package/dist/{types.d.ts → cjs/types.d.ts} +17 -0
- package/dist/{utils.js → cjs/utils.js} +14 -8
- package/dist/esm/health.d.ts +5 -0
- package/dist/esm/health.js +40 -0
- package/dist/esm/index.d.ts +46 -0
- package/dist/esm/index.js +58 -0
- package/dist/esm/middleware.d.ts +39 -0
- package/dist/esm/middleware.js +327 -0
- package/dist/esm/periodic-health.d.ts +11 -0
- package/dist/esm/periodic-health.js +64 -0
- package/dist/esm/server.d.ts +70 -0
- package/dist/esm/server.js +386 -0
- package/dist/esm/shutdown.d.ts +5 -0
- package/dist/esm/shutdown.js +52 -0
- package/dist/esm/types.d.ts +87 -0
- package/dist/esm/types.js +1 -0
- package/dist/esm/utils.d.ts +3 -0
- package/dist/esm/utils.js +38 -0
- package/dist/types/health.d.ts +5 -0
- package/dist/types/index.d.ts +46 -0
- package/dist/types/middleware.d.ts +39 -0
- package/dist/types/periodic-health.d.ts +11 -0
- package/dist/types/server.d.ts +70 -0
- package/dist/types/shutdown.d.ts +5 -0
- package/dist/types/types.d.ts +87 -0
- package/dist/types/utils.d.ts +3 -0
- package/package.json +26 -10
- package/src/health.ts +0 -47
- package/src/index.ts +0 -126
- package/src/middleware.ts +0 -275
- package/src/periodic-health.ts +0 -83
- package/src/server.ts +0 -412
- package/src/shutdown.ts +0 -69
- package/src/types.ts +0 -80
- package/src/utils.ts +0 -34
- package/tsconfig.json +0 -21
- /package/dist/{health.d.ts → cjs/health.d.ts} +0 -0
- /package/dist/{health.js → cjs/health.js} +0 -0
- /package/dist/{periodic-health.d.ts → cjs/periodic-health.d.ts} +0 -0
- /package/dist/{periodic-health.js → cjs/periodic-health.js} +0 -0
- /package/dist/{shutdown.d.ts → cjs/shutdown.d.ts} +0 -0
- /package/dist/{shutdown.js → cjs/shutdown.js} +0 -0
- /package/dist/{types.js → cjs/types.js} +0 -0
- /package/dist/{utils.d.ts → cjs/utils.d.ts} +0 -0
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { PeriodicHealthCheckConfig } from './types';
|
|
2
|
+
export declare class PeriodicHealthMonitor {
|
|
3
|
+
private intervals;
|
|
4
|
+
private config;
|
|
5
|
+
private serviceName;
|
|
6
|
+
constructor(config: PeriodicHealthCheckConfig, serviceName: string);
|
|
7
|
+
start(): void;
|
|
8
|
+
stop(): void;
|
|
9
|
+
private checkServiceHealth;
|
|
10
|
+
getHealthStatus(): Promise<Record<string, boolean>>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
import { Server } from 'http';
|
|
3
|
+
import { ServerConfig, SocketIOConfig } from './types';
|
|
4
|
+
export interface GrpcService {
|
|
5
|
+
service: Record<string, unknown>;
|
|
6
|
+
implementation: Record<string, (...args: unknown[]) => unknown>;
|
|
7
|
+
}
|
|
8
|
+
export interface RpcMethod {
|
|
9
|
+
[key: string]: (params: unknown[], callback: (error: Error | null, result?: unknown) => void) => void;
|
|
10
|
+
}
|
|
11
|
+
export interface WebhookConfig {
|
|
12
|
+
path: string;
|
|
13
|
+
secret?: string;
|
|
14
|
+
handler: (payload: Record<string, unknown>, headers: Record<string, string | string[]>) => void | Promise<void>;
|
|
15
|
+
}
|
|
16
|
+
export interface GrpcServerInstance {
|
|
17
|
+
start(): void;
|
|
18
|
+
forceShutdown(): void;
|
|
19
|
+
addService(service: unknown, implementation: unknown): void;
|
|
20
|
+
bindAsync(address: string, credentials: unknown, callback: () => void): void;
|
|
21
|
+
}
|
|
22
|
+
export interface ServerInstanceConfig extends Required<Omit<ServerConfig, 'socketIO' | 'name' | 'version'>> {
|
|
23
|
+
name: string;
|
|
24
|
+
version: string;
|
|
25
|
+
startTime: Date;
|
|
26
|
+
socketIO?: SocketIOConfig;
|
|
27
|
+
}
|
|
28
|
+
export interface ServerInstance {
|
|
29
|
+
app: express.Application;
|
|
30
|
+
server?: Server;
|
|
31
|
+
config: ServerInstanceConfig;
|
|
32
|
+
start(): Promise<ServerInstance>;
|
|
33
|
+
stop(): Promise<void>;
|
|
34
|
+
getInfo(): ServerInfo;
|
|
35
|
+
addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port?: number): void;
|
|
36
|
+
addRpcMethods(methods: RpcMethod, path?: string): void;
|
|
37
|
+
addWebhook(config: WebhookConfig): void;
|
|
38
|
+
addSocketIO(config?: SocketIOConfig): unknown;
|
|
39
|
+
}
|
|
40
|
+
export interface ServerInfo {
|
|
41
|
+
name: string;
|
|
42
|
+
version: string;
|
|
43
|
+
port: number;
|
|
44
|
+
uptime: number;
|
|
45
|
+
status: 'starting' | 'running' | 'stopping' | 'stopped';
|
|
46
|
+
startTime: Date;
|
|
47
|
+
}
|
|
48
|
+
export declare class ExpressServer implements ServerInstance {
|
|
49
|
+
app: express.Application;
|
|
50
|
+
server?: Server;
|
|
51
|
+
config: ServerInstanceConfig;
|
|
52
|
+
private status;
|
|
53
|
+
private grpcServices;
|
|
54
|
+
private grpcServer?;
|
|
55
|
+
private rpcMethods;
|
|
56
|
+
private socketIO?;
|
|
57
|
+
private healthMonitor?;
|
|
58
|
+
constructor(name?: string, version?: string, config?: ServerConfig);
|
|
59
|
+
private setupMiddleware;
|
|
60
|
+
private setupCacheAndSession;
|
|
61
|
+
private setupPeriodicHealthMonitoring;
|
|
62
|
+
start(): Promise<ServerInstance>;
|
|
63
|
+
stop(): Promise<void>;
|
|
64
|
+
getInfo(): ServerInfo;
|
|
65
|
+
addGrpcService(service: Record<string, unknown>, implementation: Record<string, (...args: unknown[]) => unknown>, port?: number): void;
|
|
66
|
+
addRpcMethods(methods: RpcMethod, path?: string): void;
|
|
67
|
+
addWebhook(config: WebhookConfig): void;
|
|
68
|
+
addSocketIO(config?: SocketIOConfig): unknown;
|
|
69
|
+
}
|
|
70
|
+
export declare function createServer(name?: string, version?: string, config?: ServerConfig): ServerInstance;
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import { Server } from 'http';
|
|
2
|
+
import { GracefulShutdownConfig, ServerPlugin } from './types';
|
|
3
|
+
export declare function createGracefulShutdown(server: Server, config?: GracefulShutdownConfig): void;
|
|
4
|
+
export declare function withGracefulShutdown(config?: GracefulShutdownConfig): ServerPlugin;
|
|
5
|
+
export declare function startServerWithShutdown(app: any, port: number, shutdownConfig?: GracefulShutdownConfig, serverName?: string, serverVersion?: string): Server;
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import express from 'express';
|
|
2
|
+
export interface CorsOptions {
|
|
3
|
+
origin?: string | string[] | boolean | RegExp | ((origin: string | undefined, callback: (err: Error | null, allow?: boolean) => void) => void);
|
|
4
|
+
methods?: string | string[];
|
|
5
|
+
allowedHeaders?: string | string[];
|
|
6
|
+
exposedHeaders?: string | string[];
|
|
7
|
+
credentials?: boolean;
|
|
8
|
+
maxAge?: number;
|
|
9
|
+
preflightContinue?: boolean;
|
|
10
|
+
optionsSuccessStatus?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface ServerConfig {
|
|
13
|
+
port?: number;
|
|
14
|
+
cors?: boolean | CorsOptions;
|
|
15
|
+
helmet?: boolean;
|
|
16
|
+
json?: boolean;
|
|
17
|
+
cookieParser?: boolean;
|
|
18
|
+
customMiddleware?: express.RequestHandler[];
|
|
19
|
+
healthCheck?: boolean | string;
|
|
20
|
+
gracefulShutdown?: boolean;
|
|
21
|
+
socketIO?: SocketIOConfig;
|
|
22
|
+
periodicHealthCheck?: PeriodicHealthCheckConfig;
|
|
23
|
+
name?: string;
|
|
24
|
+
version?: string;
|
|
25
|
+
cache?: {
|
|
26
|
+
enabled?: boolean;
|
|
27
|
+
adapter?: 'redis' | 'memcache' | 'memory';
|
|
28
|
+
options?: unknown;
|
|
29
|
+
defaultTTL?: number;
|
|
30
|
+
};
|
|
31
|
+
session?: {
|
|
32
|
+
enabled?: boolean;
|
|
33
|
+
cookieName?: string;
|
|
34
|
+
ttl?: number;
|
|
35
|
+
cookieOptions?: {
|
|
36
|
+
path?: string;
|
|
37
|
+
httpOnly?: boolean;
|
|
38
|
+
secure?: boolean;
|
|
39
|
+
sameSite?: 'lax' | 'strict' | 'none';
|
|
40
|
+
};
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
export interface PeriodicHealthCheckConfig {
|
|
44
|
+
enabled?: boolean;
|
|
45
|
+
interval?: number;
|
|
46
|
+
services?: HealthCheckService[];
|
|
47
|
+
}
|
|
48
|
+
export interface HealthCheckService {
|
|
49
|
+
name: string;
|
|
50
|
+
url: string;
|
|
51
|
+
timeout?: number;
|
|
52
|
+
}
|
|
53
|
+
export interface SocketIOConfig {
|
|
54
|
+
enabled?: boolean;
|
|
55
|
+
cors?: boolean | {
|
|
56
|
+
origin?: string | string[] | boolean;
|
|
57
|
+
methods?: string[];
|
|
58
|
+
credentials?: boolean;
|
|
59
|
+
};
|
|
60
|
+
onConnection?: (socket: unknown) => void;
|
|
61
|
+
onDisconnection?: (socket: unknown, reason: string) => void;
|
|
62
|
+
path?: string;
|
|
63
|
+
}
|
|
64
|
+
export interface HealthCheckConfig {
|
|
65
|
+
path?: string;
|
|
66
|
+
customChecks?: HealthCheck[];
|
|
67
|
+
}
|
|
68
|
+
export interface HealthCheck {
|
|
69
|
+
name: string;
|
|
70
|
+
check: () => Promise<boolean>;
|
|
71
|
+
}
|
|
72
|
+
export interface GracefulShutdownConfig {
|
|
73
|
+
timeout?: number;
|
|
74
|
+
onShutdown?: () => Promise<void>;
|
|
75
|
+
serverName?: string;
|
|
76
|
+
serverVersion?: string;
|
|
77
|
+
}
|
|
78
|
+
export interface SocketInstance {
|
|
79
|
+
id: string;
|
|
80
|
+
emit: (event: string, data?: unknown) => void;
|
|
81
|
+
on: (event: string, handler: (...args: unknown[]) => void) => void;
|
|
82
|
+
broadcast: {
|
|
83
|
+
emit: (event: string, data?: unknown) => void;
|
|
84
|
+
};
|
|
85
|
+
disconnect: () => void;
|
|
86
|
+
}
|
|
87
|
+
export type ServerPlugin = (app: express.Application, config: ServerConfig) => void;
|
package/package.json
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@naman_deep_singh/server-utils",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.2.0",
|
|
4
4
|
"description": "Extensible server utilities for Express.js microservices with TypeScript",
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/cjs/index.js",
|
|
7
|
+
"module": "./dist/esm/index.js",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/esm/index.js",
|
|
11
|
+
"require": "./dist/cjs/index.js",
|
|
12
|
+
"types": "./dist/types/index.d.ts"
|
|
13
|
+
}
|
|
9
14
|
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
10
20
|
"keywords": [
|
|
11
21
|
"server",
|
|
12
22
|
"express",
|
|
@@ -16,16 +26,22 @@
|
|
|
16
26
|
],
|
|
17
27
|
"author": "Naman Deep Singh",
|
|
18
28
|
"license": "ISC",
|
|
19
|
-
"packageManager": "pnpm@10.20.0",
|
|
20
29
|
"dependencies": {
|
|
21
|
-
"
|
|
22
|
-
"
|
|
23
|
-
"helmet": "^8.1.0",
|
|
30
|
+
"@naman_deep_singh/cache": "^1.2.0",
|
|
31
|
+
"@types/express": "^5.0.5",
|
|
24
32
|
"cookie-parser": "^1.4.6",
|
|
25
|
-
"
|
|
33
|
+
"cors": "^2.8.5",
|
|
34
|
+
"express": "^5.1.0",
|
|
35
|
+
"helmet": "^8.1.0"
|
|
26
36
|
},
|
|
27
37
|
"devDependencies": {
|
|
28
38
|
"@types/cors": "^2.8.19",
|
|
39
|
+
"rimraf": "^5.0.5",
|
|
29
40
|
"typescript": "^5.9.3"
|
|
41
|
+
},
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "pnpm run build:types && tsc -p tsconfig.cjs.json && tsc -p tsconfig.esm.json",
|
|
44
|
+
"build:types": "tsc -p tsconfig.base.json --emitDeclarationOnly --outDir dist/types",
|
|
45
|
+
"clean": "rimraf dist"
|
|
30
46
|
}
|
|
31
47
|
}
|
package/src/health.ts
DELETED
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { HealthCheckConfig, HealthCheck, ServerPlugin } from './types';
|
|
3
|
-
|
|
4
|
-
export function createHealthCheck(config: HealthCheckConfig = {}): express.RequestHandler {
|
|
5
|
-
const { customChecks = [] } = config;
|
|
6
|
-
|
|
7
|
-
return async (req: express.Request, res: express.Response) => {
|
|
8
|
-
try {
|
|
9
|
-
const checks: Record<string, boolean> = {
|
|
10
|
-
server: true,
|
|
11
|
-
timestamp: Date.now() as any
|
|
12
|
-
};
|
|
13
|
-
|
|
14
|
-
// Run custom health checks
|
|
15
|
-
for (const check of customChecks) {
|
|
16
|
-
try {
|
|
17
|
-
checks[check.name] = await check.check();
|
|
18
|
-
} catch (error) {
|
|
19
|
-
checks[check.name] = false;
|
|
20
|
-
}
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
const isHealthy = Object.values(checks).every(status => status === true || typeof status === 'number');
|
|
24
|
-
|
|
25
|
-
res.status(isHealthy ? 200 : 503).json({
|
|
26
|
-
status: isHealthy ? 'healthy' : 'unhealthy',
|
|
27
|
-
checks
|
|
28
|
-
});
|
|
29
|
-
} catch (error) {
|
|
30
|
-
res.status(503).json({
|
|
31
|
-
status: 'unhealthy',
|
|
32
|
-
error: 'Health check failed'
|
|
33
|
-
});
|
|
34
|
-
}
|
|
35
|
-
};
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export function withHealthCheck(path: string = '/health', config: HealthCheckConfig = {}): ServerPlugin {
|
|
39
|
-
return (app: express.Application) => {
|
|
40
|
-
app.get(path, createHealthCheck(config));
|
|
41
|
-
};
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// Convenience function for direct use
|
|
45
|
-
export function addHealthCheck(app: express.Application, path: string = '/health', config: HealthCheckConfig = {}): void {
|
|
46
|
-
app.get(path, createHealthCheck(config));
|
|
47
|
-
}
|
package/src/index.ts
DELETED
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
// Core server utilities
|
|
2
|
-
export { ExpressServer, createServer } from './server';
|
|
3
|
-
export type { ServerInstance, ServerInfo, GrpcService, RpcMethod, WebhookConfig } from './server';
|
|
4
|
-
|
|
5
|
-
// Express re-exports (to avoid direct Express dependency in services)
|
|
6
|
-
export { Request, Response, NextFunction, Router, Application } from 'express';
|
|
7
|
-
export type { RequestHandler, ErrorRequestHandler } from 'express';
|
|
8
|
-
|
|
9
|
-
// Health check utilities
|
|
10
|
-
export { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
11
|
-
|
|
12
|
-
// Graceful shutdown utilities
|
|
13
|
-
export { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
14
|
-
|
|
15
|
-
// Middleware utilities
|
|
16
|
-
export {
|
|
17
|
-
createLoggingMiddleware,
|
|
18
|
-
createErrorHandler,
|
|
19
|
-
createRequestIdMiddleware,
|
|
20
|
-
createValidationMiddleware,
|
|
21
|
-
createRateLimitMiddleware,
|
|
22
|
-
createAuthMiddleware,
|
|
23
|
-
withLogging,
|
|
24
|
-
withErrorHandler,
|
|
25
|
-
withRequestId,
|
|
26
|
-
withValidation,
|
|
27
|
-
withRateLimit,
|
|
28
|
-
withAuth,
|
|
29
|
-
validateFields,
|
|
30
|
-
rateLimit,
|
|
31
|
-
requireAuth,
|
|
32
|
-
type ValidationRule,
|
|
33
|
-
type RateLimitConfig,
|
|
34
|
-
type AuthConfig
|
|
35
|
-
} from './middleware';
|
|
36
|
-
|
|
37
|
-
// Utility functions
|
|
38
|
-
export {
|
|
39
|
-
getEnv,
|
|
40
|
-
getEnvNumber,
|
|
41
|
-
getEnvBoolean
|
|
42
|
-
} from './utils';
|
|
43
|
-
|
|
44
|
-
// Periodic health monitoring
|
|
45
|
-
export { PeriodicHealthMonitor } from './periodic-health';
|
|
46
|
-
|
|
47
|
-
// Types
|
|
48
|
-
export type {
|
|
49
|
-
ServerConfig,
|
|
50
|
-
HealthCheckConfig,
|
|
51
|
-
HealthCheck,
|
|
52
|
-
GracefulShutdownConfig,
|
|
53
|
-
ServerPlugin,
|
|
54
|
-
SocketIOConfig,
|
|
55
|
-
SocketInstance,
|
|
56
|
-
PeriodicHealthCheckConfig,
|
|
57
|
-
HealthCheckService
|
|
58
|
-
} from './types';
|
|
59
|
-
|
|
60
|
-
// Import all exports for default export
|
|
61
|
-
import { ExpressServer, createServer } from './server';
|
|
62
|
-
import { createHealthCheck, withHealthCheck, addHealthCheck } from './health';
|
|
63
|
-
import { createGracefulShutdown, withGracefulShutdown, startServerWithShutdown } from './shutdown';
|
|
64
|
-
import {
|
|
65
|
-
createLoggingMiddleware,
|
|
66
|
-
createErrorHandler,
|
|
67
|
-
createRequestIdMiddleware,
|
|
68
|
-
createValidationMiddleware,
|
|
69
|
-
createRateLimitMiddleware,
|
|
70
|
-
createAuthMiddleware,
|
|
71
|
-
withLogging,
|
|
72
|
-
withErrorHandler,
|
|
73
|
-
withRequestId,
|
|
74
|
-
withValidation,
|
|
75
|
-
withRateLimit,
|
|
76
|
-
withAuth,
|
|
77
|
-
validateFields,
|
|
78
|
-
rateLimit,
|
|
79
|
-
requireAuth
|
|
80
|
-
} from './middleware';
|
|
81
|
-
import { getEnv, getEnvNumber, getEnvBoolean } from './utils';
|
|
82
|
-
import { PeriodicHealthMonitor } from './periodic-health';
|
|
83
|
-
|
|
84
|
-
// Default export for namespace usage
|
|
85
|
-
const ServerUtils = {
|
|
86
|
-
// Server creation
|
|
87
|
-
createServer,
|
|
88
|
-
ExpressServer,
|
|
89
|
-
|
|
90
|
-
// Health checks
|
|
91
|
-
createHealthCheck,
|
|
92
|
-
withHealthCheck,
|
|
93
|
-
addHealthCheck,
|
|
94
|
-
|
|
95
|
-
// Graceful shutdown
|
|
96
|
-
createGracefulShutdown,
|
|
97
|
-
withGracefulShutdown,
|
|
98
|
-
startServerWithShutdown,
|
|
99
|
-
|
|
100
|
-
// Middleware
|
|
101
|
-
createLoggingMiddleware,
|
|
102
|
-
createErrorHandler,
|
|
103
|
-
createRequestIdMiddleware,
|
|
104
|
-
createValidationMiddleware,
|
|
105
|
-
createRateLimitMiddleware,
|
|
106
|
-
createAuthMiddleware,
|
|
107
|
-
withLogging,
|
|
108
|
-
withErrorHandler,
|
|
109
|
-
withRequestId,
|
|
110
|
-
withValidation,
|
|
111
|
-
withRateLimit,
|
|
112
|
-
withAuth,
|
|
113
|
-
validateFields,
|
|
114
|
-
rateLimit,
|
|
115
|
-
requireAuth,
|
|
116
|
-
|
|
117
|
-
// Utils
|
|
118
|
-
getEnv,
|
|
119
|
-
getEnvNumber,
|
|
120
|
-
getEnvBoolean,
|
|
121
|
-
|
|
122
|
-
// Periodic Health Monitoring
|
|
123
|
-
PeriodicHealthMonitor,
|
|
124
|
-
};
|
|
125
|
-
|
|
126
|
-
export default ServerUtils;
|
package/src/middleware.ts
DELETED
|
@@ -1,275 +0,0 @@
|
|
|
1
|
-
import express from 'express';
|
|
2
|
-
import { ServerPlugin } from './types';
|
|
3
|
-
|
|
4
|
-
// Logging middleware
|
|
5
|
-
export function createLoggingMiddleware(format: 'simple' | 'detailed' = 'simple'): express.RequestHandler {
|
|
6
|
-
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
7
|
-
const start = Date.now();
|
|
8
|
-
|
|
9
|
-
res.on('finish', () => {
|
|
10
|
-
const duration = Date.now() - start;
|
|
11
|
-
|
|
12
|
-
if (format === 'detailed') {
|
|
13
|
-
console.log(`${req.method} ${req.url} - ${res.statusCode} - ${duration}ms - ${req.ip}`);
|
|
14
|
-
} else {
|
|
15
|
-
console.log(`${req.method} ${req.url} - ${res.statusCode}`);
|
|
16
|
-
}
|
|
17
|
-
});
|
|
18
|
-
|
|
19
|
-
next();
|
|
20
|
-
};
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
// Error handling middleware
|
|
24
|
-
export function createErrorHandler(): express.ErrorRequestHandler {
|
|
25
|
-
return (err: unknown, req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
26
|
-
console.error('Error:', err);
|
|
27
|
-
|
|
28
|
-
if (res.headersSent) {
|
|
29
|
-
return next(err);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
// Type guard for error objects
|
|
33
|
-
const errorObj = err as { status?: number; statusCode?: number; message?: string; stack?: string };
|
|
34
|
-
|
|
35
|
-
const status = errorObj.status || errorObj.statusCode || 500;
|
|
36
|
-
const message = process.env.NODE_ENV === 'production'
|
|
37
|
-
? 'Internal Server Error'
|
|
38
|
-
: errorObj.message || 'Unknown error';
|
|
39
|
-
|
|
40
|
-
res.status(status).json({
|
|
41
|
-
status: false,
|
|
42
|
-
message,
|
|
43
|
-
...(process.env.NODE_ENV !== 'production' && { stack: errorObj.stack })
|
|
44
|
-
});
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
// Request ID middleware
|
|
49
|
-
export function createRequestIdMiddleware(): express.RequestHandler {
|
|
50
|
-
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
51
|
-
const requestId = Math.random().toString(36).substring(2, 15);
|
|
52
|
-
(req as express.Request & { requestId: string }).requestId = requestId;
|
|
53
|
-
res.setHeader('X-Request-ID', requestId);
|
|
54
|
-
next();
|
|
55
|
-
};
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
// Validation middleware
|
|
59
|
-
export interface ValidationRule {
|
|
60
|
-
field: string;
|
|
61
|
-
required?: boolean;
|
|
62
|
-
type?: 'string' | 'number' | 'email' | 'boolean';
|
|
63
|
-
minLength?: number;
|
|
64
|
-
maxLength?: number;
|
|
65
|
-
pattern?: RegExp;
|
|
66
|
-
custom?: (value: unknown) => boolean | string;
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
export function createValidationMiddleware(rules: ValidationRule[]): express.RequestHandler {
|
|
70
|
-
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
71
|
-
const errors: string[] = [];
|
|
72
|
-
|
|
73
|
-
for (const rule of rules) {
|
|
74
|
-
const value = req.body[rule.field];
|
|
75
|
-
|
|
76
|
-
if (rule.required && (value === undefined || value === null || value === '')) {
|
|
77
|
-
errors.push(`${rule.field} is required`);
|
|
78
|
-
continue;
|
|
79
|
-
}
|
|
80
|
-
|
|
81
|
-
if (value === undefined || value === null) continue;
|
|
82
|
-
|
|
83
|
-
if (rule.type) {
|
|
84
|
-
switch (rule.type) {
|
|
85
|
-
case 'email':
|
|
86
|
-
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
|
87
|
-
if (!emailRegex.test(value)) {
|
|
88
|
-
errors.push(`${rule.field} must be a valid email`);
|
|
89
|
-
}
|
|
90
|
-
break;
|
|
91
|
-
case 'string':
|
|
92
|
-
if (typeof value !== 'string') {
|
|
93
|
-
errors.push(`${rule.field} must be a string`);
|
|
94
|
-
}
|
|
95
|
-
break;
|
|
96
|
-
case 'number':
|
|
97
|
-
if (typeof value !== 'number' && isNaN(Number(value))) {
|
|
98
|
-
errors.push(`${rule.field} must be a number`);
|
|
99
|
-
}
|
|
100
|
-
break;
|
|
101
|
-
case 'boolean':
|
|
102
|
-
if (typeof value !== 'boolean') {
|
|
103
|
-
errors.push(`${rule.field} must be a boolean`);
|
|
104
|
-
}
|
|
105
|
-
break;
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
if (rule.minLength && value.length < rule.minLength) {
|
|
110
|
-
errors.push(`${rule.field} must be at least ${rule.minLength} characters`);
|
|
111
|
-
}
|
|
112
|
-
if (rule.maxLength && value.length > rule.maxLength) {
|
|
113
|
-
errors.push(`${rule.field} must be no more than ${rule.maxLength} characters`);
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
if (rule.pattern && !rule.pattern.test(value)) {
|
|
117
|
-
errors.push(`${rule.field} format is invalid`);
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
if (rule.custom) {
|
|
121
|
-
const result = rule.custom(value);
|
|
122
|
-
if (result !== true) {
|
|
123
|
-
errors.push(typeof result === 'string' ? result : `${rule.field} is invalid`);
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (errors.length > 0) {
|
|
129
|
-
return res.status(400).json({
|
|
130
|
-
status: false,
|
|
131
|
-
message: 'Validation failed',
|
|
132
|
-
errors
|
|
133
|
-
});
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
next();
|
|
137
|
-
};
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
// Rate limiting middleware
|
|
141
|
-
export interface RateLimitConfig {
|
|
142
|
-
windowMs?: number;
|
|
143
|
-
maxRequests?: number;
|
|
144
|
-
message?: string;
|
|
145
|
-
keyGenerator?: (req: express.Request) => string;
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
const rateLimitStore = new Map<string, { count: number; resetTime: number }>();
|
|
149
|
-
|
|
150
|
-
export function createRateLimitMiddleware(config: RateLimitConfig = {}): express.RequestHandler {
|
|
151
|
-
const {
|
|
152
|
-
windowMs = 15 * 60 * 1000,
|
|
153
|
-
maxRequests = 100,
|
|
154
|
-
message = 'Too many requests, please try again later',
|
|
155
|
-
keyGenerator = (req) => req.ip || 'unknown'
|
|
156
|
-
} = config;
|
|
157
|
-
|
|
158
|
-
return (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
159
|
-
const key = keyGenerator(req);
|
|
160
|
-
const now = Date.now();
|
|
161
|
-
const record = rateLimitStore.get(key);
|
|
162
|
-
|
|
163
|
-
if (!record || now > record.resetTime) {
|
|
164
|
-
rateLimitStore.set(key, {
|
|
165
|
-
count: 1,
|
|
166
|
-
resetTime: now + windowMs
|
|
167
|
-
});
|
|
168
|
-
return next();
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
if (record.count >= maxRequests) {
|
|
172
|
-
return res.status(429).json({
|
|
173
|
-
status: false,
|
|
174
|
-
message,
|
|
175
|
-
retryAfter: Math.ceil((record.resetTime - now) / 1000)
|
|
176
|
-
});
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
record.count++;
|
|
180
|
-
next();
|
|
181
|
-
};
|
|
182
|
-
}
|
|
183
|
-
|
|
184
|
-
// Authentication middleware helper
|
|
185
|
-
export interface AuthConfig {
|
|
186
|
-
tokenExtractor?: (req: express.Request) => string | null;
|
|
187
|
-
tokenValidator?: (token: string) => Promise<unknown> | unknown;
|
|
188
|
-
unauthorizedMessage?: string;
|
|
189
|
-
}
|
|
190
|
-
|
|
191
|
-
export function createAuthMiddleware(config: AuthConfig): express.RequestHandler {
|
|
192
|
-
const {
|
|
193
|
-
tokenExtractor = (req) => {
|
|
194
|
-
const authHeader = req.headers.authorization;
|
|
195
|
-
if (authHeader && authHeader.startsWith('Bearer ')) {
|
|
196
|
-
return authHeader.substring(7);
|
|
197
|
-
}
|
|
198
|
-
return req.cookies?.token || null;
|
|
199
|
-
},
|
|
200
|
-
tokenValidator = () => { throw new Error('Token validator not implemented'); },
|
|
201
|
-
unauthorizedMessage = 'Unauthorized access'
|
|
202
|
-
} = config;
|
|
203
|
-
|
|
204
|
-
return async (req: express.Request, res: express.Response, next: express.NextFunction) => {
|
|
205
|
-
try {
|
|
206
|
-
const token = tokenExtractor(req);
|
|
207
|
-
|
|
208
|
-
if (!token) {
|
|
209
|
-
return res.status(401).json({
|
|
210
|
-
status: false,
|
|
211
|
-
message: unauthorizedMessage
|
|
212
|
-
});
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
const user = await tokenValidator(token);
|
|
216
|
-
(req as express.Request & { user: unknown }).user = user;
|
|
217
|
-
next();
|
|
218
|
-
} catch (error) {
|
|
219
|
-
return res.status(401).json({
|
|
220
|
-
status: false,
|
|
221
|
-
message: unauthorizedMessage
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
};
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
// Plugin versions
|
|
228
|
-
export function withLogging(format: 'simple' | 'detailed' = 'simple'): ServerPlugin {
|
|
229
|
-
return (app: express.Application) => {
|
|
230
|
-
app.use(createLoggingMiddleware(format));
|
|
231
|
-
};
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
export function withErrorHandler(): ServerPlugin {
|
|
235
|
-
return (app: express.Application) => {
|
|
236
|
-
app.use(createErrorHandler());
|
|
237
|
-
};
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
export function withRequestId(): ServerPlugin {
|
|
241
|
-
return (app: express.Application) => {
|
|
242
|
-
app.use(createRequestIdMiddleware());
|
|
243
|
-
};
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
export function withValidation(rules: ValidationRule[]): ServerPlugin {
|
|
247
|
-
return (app: express.Application) => {
|
|
248
|
-
app.use(createValidationMiddleware(rules));
|
|
249
|
-
};
|
|
250
|
-
}
|
|
251
|
-
|
|
252
|
-
export function withRateLimit(config: RateLimitConfig = {}): ServerPlugin {
|
|
253
|
-
return (app: express.Application) => {
|
|
254
|
-
app.use(createRateLimitMiddleware(config));
|
|
255
|
-
};
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
export function withAuth(config: AuthConfig): ServerPlugin {
|
|
259
|
-
return (app: express.Application) => {
|
|
260
|
-
app.use(createAuthMiddleware(config));
|
|
261
|
-
};
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Convenience functions for route-specific middleware
|
|
265
|
-
export function validateFields(rules: ValidationRule[]): express.RequestHandler {
|
|
266
|
-
return createValidationMiddleware(rules);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
export function rateLimit(config: RateLimitConfig = {}): express.RequestHandler {
|
|
270
|
-
return createRateLimitMiddleware(config);
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
export function requireAuth(config: AuthConfig): express.RequestHandler {
|
|
274
|
-
return createAuthMiddleware(config);
|
|
275
|
-
}
|