SpaceNode
Services contain your business logic. They're exported as named exports from *.service.js files and automatically registered in the DI container. Services can be plain objects, factory functions, or classes.
// modules/auth/auth.service.js
export const userStore = {
async create({ name, email, password }) {
return await User.create({ name, email, password })
},
async getByEmail(email) {
return User.findOne({ email })
},
async verifyPassword(user, password) {
return user.verifyPassword(password)
},
}
export const tokenStore = {
async create(userId) {
const token = randomBytes(32).toString('hex')
await Token.create({ token, userId })
return token
},
async get(token) {
return Token.findOne({ token })
},
}
export const userStore = {}). The export name becomes the DI key used in controllers. You can export multiple services from a single file.The DI container accepts three types of service registrations:
| Type | Example | Behavior |
|---|---|---|
| Plain object | export const userStore = { ... } | Registered directly as singleton instance |
| Factory function | export const db = (container) => new Database() | Called lazily on first resolve, cached as singleton |
| Class | export const cache = new CacheService() | Instance registered directly |
Services are the second argument to every handler. Destructure services at the top of the function body to declare dependencies:
// modules/orders/orders.controller.js
export async function checkout(request, services) {
const { body, user, send } = request
const { orderStore, productStore } = services
const product = await productStore.getById(body.productId)
const order = await orderStore.create({ userId: user.id, product })
send(201, order)
}
orderStore from the orders module and productStore from the products module — both accessible everywhere.*.service.js files are imported.moduleName.serviceName) for disambiguation.container.getAll() returns a frozen object with all resolved services.By default, services are globally accessible. To prevent name collisions, set isolated: true in the module config — services will only be accessible via the namespaced key:
// modules/payments/module.js
export default {
prefix: '/payments',
isolated: true, // services only as 'payments.paymentStore'
routes: [...]
}
// In controllers:
export async function pay(request, services) {
const { send } = request
const paymentStore = services['payments.paymentStore']
// ...
}