SpaceNode
Modules are the building blocks of a SpaceNode application. Each module is a folder inside modules/ with a module.js configuration file.
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
},
}
| Property | Type | Description |
|---|---|---|
name | string | Module name (defaults to folder name) |
prefix | string | URL prefix for all routes (e.g. /auth) |
routes | array | Route definitions (see Route Formats below) |
pipe | string[] | Module-level pipes applied to every route |
on | object | Event listeners — maps event names to controller functions |
isolated | boolean | If true, services are only accessible via namespaced key (module.serviceName) |
onInit | function | Lifecycle hook — called after all modules are loaded with resolved services |
onDestroy | function | Lifecycle hook — called during graceful shutdown |
openapi | object | OpenAPI tag metadata: { description: 'Module description for Swagger' } |
SpaceNode supports two route definition formats:
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']],
]
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'] } },
]
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'],
],
}
onInit runs after all modules are loaded and services are resolved. onDestroy runs during graceful shutdown (SIGTERM/SIGINT). Hooks can be async.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']],
],
}
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
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 }),
},
})