Production Features

SpaceNode is built for production. Here's what's included out of the box.

Server Timeouts

const app = await createApp({
  timeout: 30000,         // request timeout (default: 30s)
  keepAliveTimeout: 5000, // keep-alive timeout (default: 5s)
})

The headersTimeout is automatically set to timeout + 1000ms to prevent a known Node.js bug.

Graceful Shutdown

app.close()  // stops accepting new connections

The shutdown sequence runs in this order:

  1. Module onDestroy hooks — all registered hooks are called (async-safe)
  2. WebSocket connections — all open sockets are closed with code 1001 (Going Away)
  3. EventBus cleanupevents.destroy() removes all listeners
  4. HTTP server close — stops accepting new connections, waits for in-flight requests
  5. Force-close — after shutdownTimeout (default 5s), all remaining connections are forcefully terminated

SpaceNode also registers SIGTERM and SIGINT handlers automatically, so Ctrl+C and docker stop trigger a clean shutdown.

Fatal Error Catching

If an error occurs inside the error handler itself, SpaceNode catches it and sends a generic 500 response. The server stays alive.

Auto-204

If a handler doesn't call send(), the framework sends 204 No Content. No hanging connections.

Body Limits

Request bodies limited to 1 MB by default. Configure with bodyLimit:

const app = await createApp({
  bodyLimit: 5 * 1024 * 1024,  // 5 MB
})

Larger bodies trigger 413, connection destroyed.

Body Parsing

SpaceNode automatically parses JSON, URL-encoded forms, and multipart/form-data requests. See Body Parsing for details.

Static File Cache

When static file serving is enabled, files are cached in-memory via an LRU cache with ETag support and 304 Not Modified responses:

const app = await createApp({
  static: './public',
  staticCacheMax: 500,           // max cached files (default: 500)
  staticCacheFileSize: 256 * 1024, // max file size to cache (default: 256 KB)
})

Files exceeding staticCacheFileSize are streamed from disk. See Static Files for details.

Structured Logging

SpaceNode includes a built-in Logger with levels and custom transports:

import { Logger } from 'SpaceNode'

const logger = new Logger({ level: 'info' })
logger.info('Server started')
logger.error('Something failed', { code: 500 })

Pass logger options to createApp config for framework-level logging:

const app = await createApp({
  logger: { level: 'warn' },
})

Child Loggers

Create prefixed child loggers for per-module or per-service context:

const logger = new Logger({ level: 'info' })
const dbLog = logger.child('db')
const authLog = logger.child('auth')

dbLog.info('Connected')       // INFO  [db] Connected
authLog.warn('Token expired')  // WARN  [auth] Token expired

Child loggers inherit the parent’s log level and timestamps. Useful for filtering logs by subsystem in production.

Production Config Example

Warning: Never use watch: true in production. It is a development convenience (like nodemon) that restarts the server on file changes. In production, use a process manager (PM2, systemd, Docker) instead.
import { createApp, defineAuth } from 'SpaceNode'

defineAuth(async (token) => { /* verify */ })

const app = await createApp({
  db: dbConnection,
  timeout: 15000,
  keepAliveTimeout: 5000,
  shutdownTimeout: 10000,
  bodyLimit: 5 * 1024 * 1024,  // 5 MB
  pipe: ['cors:https://mysite.com', 'logger', 'rateLimit:500', 'security', 'compress'],
  openapi: { title: 'My API', version: '1.0.0' },
})

app.onError((err, req) => {
  // Send to monitoring service
})

app.listen(process.env.PORT || 3000)

process.on('SIGINT',  () => app.close(() => process.exit(0)))
process.on('SIGTERM', () => app.close(() => process.exit(0)))