feat: auto-inject middlewares from armcorc.json
Some checks failed
armco-org/node-starter-kit/pipeline/head There was a failure building this commit
Some checks failed
armco-org/node-starter-kit/pipeline/head There was a failure building this commit
- Add bodyParser and cookieParser middleware support - Auto-inject configured middlewares during Application.build() - Add [NSK][MIDDLEWARE] logger prefixes for all middlewares - Middlewares inject in correct order: bodyParser → cookieParser → helmet → cors → rateLimit - Supports both rateLimit and rateLimiter config keys - Zero boilerplate required in host apps - just Application.create(app).build() - armcorc.json is single source of truth for middleware configuration
This commit is contained in:
2
build.ts
2
build.ts
@@ -106,7 +106,7 @@ import pkg from "./package.json";
|
||||
logger.info('✅ Build completed successfully!');
|
||||
logger.info('📦 Package ready in ./dist folder');
|
||||
} catch (err) {
|
||||
logger.err('❌ Build failed:', err);
|
||||
logger.err('❌ Build failed:', err as any);
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
"compilerOptions": {
|
||||
"declaration": true,
|
||||
"declarationDir": "dist",
|
||||
"ignoreDeprecations": "6.0",
|
||||
"target": "ES2020",
|
||||
"module": "commonjs",
|
||||
"moduleResolution": "node",
|
||||
|
||||
@@ -276,6 +276,13 @@ export class ApplicationBuilder {
|
||||
* Build and return the application
|
||||
*/
|
||||
async build(): Promise<Application> {
|
||||
// Import middleware initializers
|
||||
const { initHelmet } = await import('../middlewares/security/helmet')
|
||||
const { initCors } = await import('../middlewares/security/cors')
|
||||
const { initRateLimiter } = await import('../middlewares/security/rateLimiter')
|
||||
const { initCookieParser } = await import('../middlewares/utils/cookieParser')
|
||||
const { initBodyParser } = await import('../middlewares/utils/bodyParser')
|
||||
|
||||
// Load configuration
|
||||
let finalConfig: AppConfig
|
||||
|
||||
@@ -349,10 +356,41 @@ export class ApplicationBuilder {
|
||||
application.plugins(this.pluginRegistrations)
|
||||
}
|
||||
|
||||
// Initialize and start
|
||||
// Initialize and start plugins
|
||||
await application.initialize()
|
||||
await application.start()
|
||||
|
||||
// Auto-inject middlewares from config (AFTER plugins so logger is available)
|
||||
const logger = application.getContainer().tryResolve<any>('logger')
|
||||
|
||||
// Inject middlewares in order (IMPORTANT: body parsers first, then security, then auth)
|
||||
if (finalConfig.middlewares) {
|
||||
// 1. Body parser (must be early to parse request bodies)
|
||||
if (finalConfig.middlewares.bodyParser) {
|
||||
initBodyParser(this.app, finalConfig.middlewares.bodyParser as any, logger)
|
||||
}
|
||||
|
||||
// 2. Cookie parser (must be early to parse cookies)
|
||||
if (finalConfig.middlewares.cookieParser) {
|
||||
initCookieParser(this.app, finalConfig.middlewares.cookieParser as any, logger)
|
||||
}
|
||||
|
||||
// 3. Security middlewares
|
||||
if (finalConfig.middlewares.helmet) {
|
||||
initHelmet(this.app, finalConfig.middlewares.helmet as any, logger)
|
||||
}
|
||||
|
||||
if (finalConfig.middlewares.cors) {
|
||||
initCors(this.app, finalConfig.middlewares.cors as any, logger)
|
||||
}
|
||||
|
||||
// 4. Rate limiting (after CORS)
|
||||
const rateLimitConfig = finalConfig.middlewares.rateLimit || finalConfig.middlewares.rateLimiter
|
||||
if (rateLimitConfig) {
|
||||
initRateLimiter(this.app, rateLimitConfig as any, logger)
|
||||
}
|
||||
}
|
||||
|
||||
return application
|
||||
}
|
||||
}
|
||||
|
||||
@@ -153,6 +153,29 @@ const RateLimiterConfigSchema = z.object({
|
||||
handler: z.function().optional(),
|
||||
}).passthrough()
|
||||
|
||||
/**
|
||||
* Cookie parser middleware configuration schema
|
||||
*/
|
||||
const CookieParserConfigSchema = z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
secret: z.union([z.string(), z.array(z.string())]).optional(),
|
||||
options: z.record(z.unknown()).optional(),
|
||||
}).passthrough()
|
||||
|
||||
/**
|
||||
* Body parser middleware configuration schema
|
||||
*/
|
||||
const BodyParserConfigSchema = z.object({
|
||||
json: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
options: z.record(z.unknown()).optional(),
|
||||
}).optional(),
|
||||
urlencoded: z.object({
|
||||
enabled: z.boolean().optional(),
|
||||
options: z.record(z.unknown()).optional(),
|
||||
}).optional(),
|
||||
}).passthrough()
|
||||
|
||||
/**
|
||||
* Middleware configuration schema
|
||||
*/
|
||||
@@ -162,6 +185,9 @@ const MiddlewareConfigSchema = z.object({
|
||||
csrf: CsrfConfigSchema.optional(),
|
||||
jwt: JwtConfigSchema.optional(),
|
||||
rateLimiter: RateLimiterConfigSchema.optional(),
|
||||
rateLimit: RateLimiterConfigSchema.optional(), // Alias for rateLimiter
|
||||
cookieParser: CookieParserConfigSchema.optional(),
|
||||
bodyParser: BodyParserConfigSchema.optional(),
|
||||
}).passthrough()
|
||||
|
||||
/**
|
||||
|
||||
@@ -46,6 +46,8 @@ export { initCors, type CorsConfig } from './middlewares/security/cors'
|
||||
export { initCsrf, type CsrfConfig } from './middlewares/security/csrf'
|
||||
export { initRateLimiter, type RateLimiterConfig } from './middlewares/security/rateLimiter'
|
||||
export { initJwt, signToken, type JwtConfig } from './middlewares/auth/jwt'
|
||||
export { initCookieParser, type CookieParserConfig } from './middlewares/utils/cookieParser'
|
||||
export { initBodyParser, type BodyParserConfig } from './middlewares/utils/bodyParser'
|
||||
|
||||
// Re-export Application.create as default
|
||||
export { Application as default } from './core/Application'
|
||||
|
||||
@@ -19,11 +19,11 @@ export interface CorsConfig {
|
||||
*/
|
||||
export function initCors(app: Application, config: CorsConfig, logger?: Logger): void {
|
||||
if (config.enabled === false) {
|
||||
logger?.debug('CORS middleware disabled')
|
||||
logger?.debug('[NSK][CORS] Middleware disabled')
|
||||
return
|
||||
}
|
||||
|
||||
logger?.info('Initializing CORS middleware')
|
||||
logger?.info('[NSK][CORS] Initializing...')
|
||||
|
||||
const corsOptions: CorsOptions = {
|
||||
credentials: config.credentials ?? true,
|
||||
@@ -49,11 +49,11 @@ export function initCors(app: Application, config: CorsConfig, logger?: Logger):
|
||||
}
|
||||
}
|
||||
|
||||
logger?.info('CORS allowed origins:', { origins: config.allowedOrigins })
|
||||
logger?.info('[NSK][CORS] Allowed origins:', { origins: config.allowedOrigins })
|
||||
} else {
|
||||
// Default: allow all origins
|
||||
corsOptions.origin = true
|
||||
logger?.warn('CORS allowing all origins (not recommended for production)')
|
||||
logger?.warn('[NSK][CORS] Allowing all origins (not recommended for production)')
|
||||
}
|
||||
|
||||
if (config.allowedMethods) {
|
||||
@@ -70,5 +70,5 @@ export function initCors(app: Application, config: CorsConfig, logger?: Logger):
|
||||
|
||||
app.use(cors(corsOptions) as RequestHandler)
|
||||
|
||||
logger?.info('CORS middleware initialized')
|
||||
logger?.info('[NSK][CORS] Initialized')
|
||||
}
|
||||
|
||||
@@ -17,11 +17,11 @@ export interface HelmetConfig {
|
||||
*/
|
||||
export function initHelmet(app: Application, config: HelmetConfig, logger?: Logger): void {
|
||||
if (config.enabled === false) {
|
||||
logger?.debug('Helmet middleware disabled')
|
||||
logger?.debug('[NSK][HELMET] Middleware disabled')
|
||||
return
|
||||
}
|
||||
|
||||
logger?.info('Initializing Helmet middleware')
|
||||
logger?.info('[NSK][HELMET] Initializing...')
|
||||
|
||||
const options: HelmetOptions = config.options || {}
|
||||
|
||||
@@ -40,5 +40,5 @@ export function initHelmet(app: Application, config: HelmetConfig, logger?: Logg
|
||||
|
||||
app.use(helmet(options) as RequestHandler)
|
||||
|
||||
logger?.info('Helmet middleware initialized')
|
||||
logger?.info('[NSK][HELMET] Initialized')
|
||||
}
|
||||
|
||||
@@ -21,11 +21,11 @@ export interface RateLimiterConfig {
|
||||
*/
|
||||
export function initRateLimiter(app: Application, config: RateLimiterConfig, logger?: Logger): void {
|
||||
if (config.enabled === false) {
|
||||
logger?.debug('Rate limiter middleware disabled')
|
||||
logger?.debug('[NSK][RATELIMITER] Middleware disabled')
|
||||
return
|
||||
}
|
||||
|
||||
logger?.info('Initializing rate limiter middleware')
|
||||
logger?.info('[NSK][RATELIMITER] Initializing...')
|
||||
|
||||
const skipPaths = config.skipPaths || []
|
||||
|
||||
@@ -43,7 +43,7 @@ export function initRateLimiter(app: Application, config: RateLimiterConfig, log
|
||||
return skipPaths.some(path => req.path.startsWith(path))
|
||||
}),
|
||||
handler: config.handler || ((req: Request, res: Response) => {
|
||||
logger?.warn('Rate limit exceeded', {
|
||||
logger?.warn('[NSK][RATELIMITER] Rate limit exceeded', {
|
||||
ip: req.ip,
|
||||
path: req.path,
|
||||
method: req.method
|
||||
@@ -58,7 +58,7 @@ export function initRateLimiter(app: Application, config: RateLimiterConfig, log
|
||||
|
||||
app.use(rateLimit(options))
|
||||
|
||||
logger?.info('Rate limiter middleware initialized', {
|
||||
logger?.info('[NSK][RATELIMITER] Initialized', {
|
||||
windowMs: options.windowMs,
|
||||
max: options.max,
|
||||
})
|
||||
|
||||
37
v2/middlewares/utils/bodyParser.ts
Normal file
37
v2/middlewares/utils/bodyParser.ts
Normal file
@@ -0,0 +1,37 @@
|
||||
import express, { Application } from 'express'
|
||||
import { Logger } from '../../types/Logger'
|
||||
|
||||
export interface BodyParserConfig {
|
||||
json?: {
|
||||
enabled?: boolean
|
||||
options?: any
|
||||
}
|
||||
urlencoded?: {
|
||||
enabled?: boolean
|
||||
options?: any
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize body parser middleware
|
||||
* Parses request body as JSON and URL-encoded
|
||||
*/
|
||||
export function initBodyParser(app: Application, config: BodyParserConfig, logger?: Logger): void {
|
||||
logger?.info('[NSK][BODYPARSER] Initializing...')
|
||||
|
||||
// JSON parser
|
||||
if (config.json?.enabled !== false) {
|
||||
const jsonOptions = config.json?.options || {}
|
||||
app.use(express.json(jsonOptions))
|
||||
logger?.debug('[NSK][BODYPARSER] JSON parser enabled')
|
||||
}
|
||||
|
||||
// URL-encoded parser
|
||||
if (config.urlencoded?.enabled !== false) {
|
||||
const urlencodedOptions = config.urlencoded?.options || { extended: true }
|
||||
app.use(express.urlencoded(urlencodedOptions))
|
||||
logger?.debug('[NSK][BODYPARSER] URL-encoded parser enabled')
|
||||
}
|
||||
|
||||
logger?.info('[NSK][BODYPARSER] Initialized')
|
||||
}
|
||||
30
v2/middlewares/utils/cookieParser.ts
Normal file
30
v2/middlewares/utils/cookieParser.ts
Normal file
@@ -0,0 +1,30 @@
|
||||
import cookieParser from 'cookie-parser'
|
||||
import { Application, RequestHandler } from 'express'
|
||||
import { Logger } from '../../types/Logger'
|
||||
|
||||
export interface CookieParserConfig {
|
||||
enabled?: boolean
|
||||
secret?: string | string[]
|
||||
options?: cookieParser.CookieParseOptions
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize cookie parser middleware
|
||||
* Parses cookies from request headers
|
||||
*/
|
||||
export function initCookieParser(app: Application, config: CookieParserConfig, logger?: Logger): void {
|
||||
if (config.enabled === false) {
|
||||
logger?.debug('[NSK][COOKIEPARSER] Middleware disabled')
|
||||
return
|
||||
}
|
||||
|
||||
logger?.info('[NSK][COOKIEPARSER] Initializing...')
|
||||
|
||||
if (config.secret) {
|
||||
app.use(cookieParser(config.secret, config.options) as RequestHandler)
|
||||
logger?.info('[NSK][COOKIEPARSER] Initialized with secret')
|
||||
} else {
|
||||
app.use(cookieParser(undefined, config.options) as RequestHandler)
|
||||
logger?.info('[NSK][COOKIEPARSER] Initialized without secret')
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user