Modules

Modules are the building blocks of a SpaceNode application. Each module is a folder inside modules/ with a module.js configuration file.

module.js Configuration

export default {
  name: 'auth',           // module name (defaults to folder name)
  prefix: '/auth',         // URL prefix for all routes
  pipe: ['cors', 'logger'], // module-level pipes (applied to ALL routes)

  routes: [
    // [METHOD, path, handlerName, pipes?]
    ['POST', '/register', 'register', ['dto:registerDto']],
    ['POST', '/login',    'login',    ['dto:loginDto']],
    ['GET',  '/me',       'me',       ['auth']],
  ],

  // Event listeners (optional)
  on: {
    'order:created': 'onOrderCreated',  // calls controller function
  },
}

Config Properties

PropertyTypeDescription
namestringModule name (defaults to folder name)
prefixstringURL prefix for all routes (e.g. /auth)
routesarrayRoute definitions (see Route Formats below)
pipestring[]Module-level pipes applied to every route
onobjectEvent listeners — maps event names to controller functions
isolatedbooleanIf true, services are only accessible via namespaced key (module.serviceName)
onInitfunctionLifecycle hook — called after all modules are loaded with resolved services
onDestroyfunctionLifecycle hook — called during graceful shutdown
openapiobjectOpenAPI tag metadata: { description: 'Module description for Swagger' }

Route Formats

SpaceNode supports two route definition formats:

1. Tuple Format (recommended)

routes: [
  // [METHOD, path, handlerName, pipes?, openapi?]
  ['GET',    '/',      'list'],
  ['POST',   '/',      'create',  ['auth', 'dto:createDto']],
  ['GET',    '/:id',   'getById'],
  ['PUT',    '/:id',   'update',  ['auth'], { summary: 'Update item' }],
  ['DELETE', '/:id',   'remove',  ['auth', 'role:admin']],
]

2. Object Format

routes: [
  { method: 'GET',  path: '/',    handlerName: 'list' },
  { method: 'POST', path: '/',    handlerName: 'create', pipeNames: ['auth'] },
  { method: 'POST', path: '/login', handlerName: 'login', pipeNames: ['dto:loginDto'], openapi: { summary: 'User login', tags: ['auth'] } },
]

Lifecycle Hooks

Modules can define onInit and onDestroy hooks for setup and teardown logic:

// modules/search/module.js

export function onInit(services) {
  console.log('Search module initialized')
  // Build indexes, warm caches, etc.
}

export function onDestroy() {
  console.log('Search module shutting down')
  // Close connections, flush buffers, etc.
}

export default {
  prefix: '/search',
  routes: [
    ['GET', '/', 'search'],
  ],
}
Execution orderonInit runs after all modules are loaded and services are resolved. onDestroy runs during graceful shutdown (SIGTERM/SIGINT). Hooks can be async.

Module-Level Pipes

Pipes defined in the pipe array apply to every route in the module:

export default {
  name: 'orders',
  prefix: '/orders',
  pipe: ['auth'],   // ALL order routes require authentication

  routes: [
    ['GET',  '/my',       'listUserOrders'],
    ['POST', '/checkout',  'checkout',  ['dto:checkoutDto']],
    ['GET',  '/all',       'listAll',   ['role:admin']],
  ],
}

Recursive Module Discovery

Enable recursive: true in createApp() to auto-discover nested modules in subdirectories:

const app = await createApp({ recursive: true })

// Discovers: modules/api/v1/users/ → prefix: /api/v1/users
// Discovers: modules/api/v2/users/ → prefix: /api/v2/users

Programmatic Modules

You can add modules without the file system using app.addModule():

app.addModule({
  name: 'health',
  prefix: '/health',
  routes: [['GET', '/', 'check']],
  controllers: {
    check: ({ send }) => send({ ok: true }),
  },
})
Pipe execution order — Global pipes → Module pipes → Route pipes → Handler