ãƒĄã‚¤ãƒŗã‚ŗãƒŗãƒ†ãƒŗãƒ„ãžã§ã‚šã‚­ãƒƒãƒ—
バãƒŧã‚¸ãƒ§ãƒŗ: 🚧 Canary

đŸ› ī¸ Service

A service is a naming convention for a factory function that composes domain features and mounts them as an Express router via defService. There is no service registration API — the SDK exports ready-made factories from src/services/.

Each factory returns express.Router with HTTP (and optionally WebSocket) endpoints. Services are not in-process domain APIs you call directly from application code.


🔍 What is a Service?​

A service bundles one or more features — each feature is compose(withSchema(...), withRoute(...)) — into a single mountable router:

schemas/*.ts + routes/*.ts → features/*.ts → services/*.ts → defService()
(withSchema/withRoute) (compose) (compose many) (router)
┌─────────────┐
│ Service │ identitiesService(dataStores, configuration)
├─────────────┤
│ Features ▷ getIdentityFeature
│ ▷ findIdentitiesFeature
│ ▷ updateIdentityFeature
│ ▷ ...
└─────────────┘

Why use services:

  • One factory per domain — mount a complete REST (and optional WS) surface in one line
  • Dependencies injected via partial — no global state
  • Composable — reuse features across services or build custom factories with the same pattern
  • Typed dataStores and configuration per service (MongoDB Collection interfaces)

Import services via the single namespace export:

import { services } from '@nodeblocks/backend-sdk';

const { identitiesService, authService, chatService } = services;

See Feature Âģ for how features are defined and composed.


âš™ī¸ How It Works (defService)​

defService turns a composed Composable into a running Express router. Every shipped service factory follows the same internal wiring:

defService(
partial(compose(...features), [{ dataStores, configuration, authenticate, ...drivers }]),
webSocketServer? // only chatService passes this today
);

When a request arrives, defService:

  1. Runs the composed adapter to build a ServiceDefinition (routes, schemas, middleware, and injected dependencies)
  2. Creates an Express router with express.json() body parsing
  3. Applies optional service.middleware from the composed definition
  4. Registers HTTP routes (GET / POST / PUT / PATCH / DELETE) — validators run before handlers
  5. Binds WebSocket routes to the provided webSocketServer when present

WebSocket routes assert if no webSocketServer is passed to defService. chatService forwards the webSocketServer property of its optional third argument as that second argument. It is not an export of the drivers namespace.

Errors thrown during validation or handling are normalized to NodeblocksError and passed to Express via next. defService does not send the final error response; mount nodeBlocksErrorMiddleware() at the app level.

See Route Âģ for response handling ({ statusCode, data } terminators) and the WebSocket Service Guide Âģ for chat streaming setup.


🧑‍đŸ’ģ Standard Factory Pattern​

All SDK services share the same structure:

import { partial } from 'ramda';
import { Collection } from 'mongodb';

import { features, primitives, utils } from '@nodeblocks/backend-sdk';

const {
getIdentityFeature,
findIdentitiesFeature,
updateIdentityFeature,
deleteIdentityFeature,
lockIdentityFeature,
unlockIdentityFeature,
} = features;
const { compose, defService } = primitives;
const { getBearerTokenInfo, getCookieTokenInfo } = utils;

export const identitiesService = (
dataStores: { identities: Collection },
configuration: {
authSecrets: { authEncSecret: string; authSignSecret: string };
authMode?: 'bearer' | 'cookie';
identity?: { typeIds?: { admin: string; guest: string; regular: string } };
}
) => {
return defService(
partial(
compose(
getIdentityFeature,
findIdentitiesFeature,
updateIdentityFeature,
deleteIdentityFeature,
lockIdentityFeature,
unlockIdentityFeature
),
[
{
dataStores,
configuration,
authenticate:
configuration.authMode === 'cookie'
? getCookieTokenInfo
: getBearerTokenInfo,
},
]
)
);
};

How partial + defService work:

  1. partial(compose(...features), [deps]) pre-applies dataStores, configuration, authenticate, and optional drivers as the initial ServiceDefinition
  2. Each feature composer adds routes (and schema metadata) to that definition
  3. defService turns the final ServiceDefinition into an Express router

Commonly injected fields: dataStores, configuration, authenticate.

Optional drivers (accepted by the factory third argument and forwarded into the service context): mailService, fileStorageDriver, OAuth drivers (googleOAuthDriver, twitterOAuthDriver, lineOAuthDriver), and findAddressDriver. webSocketServer is handled separately: chatService passes it as the second argument to defService rather than injecting it into the request context.

When using cookie authentication (authMode: 'cookie'), register the cookie-parser Express middleware before mounting the service router.


📑 SDK Service Catalog​

Services are organized by domain in the SDK services namespace, matching src/services/index.ts:

ServiceExportResponsibilityDrivers (3rd arg)Docs
AuthenticationauthServiceRegistration, login/logout, MFA, tokens, invitations, OAuthmailService, OAuth driversDocs Âģ
IdentityidentitiesServiceIdentity CRUD, lock/unlock—Docs Âģ
ProfileprofileServiceProfiles, avatars, follows, likesfileStorageDriverDocs Âģ
OrganizationorganizationServiceOrganizations, members, change requestsfileStorageDriverDocs Âģ
ProductproductServiceProduct CRUD, batch/copyfileStorageDriverDocs Âģ
AttributesattributesServiceAttributes and groups—Docs Âģ
CategorycategoryServiceCategory CRUD and status—Docs Âģ
LocationlocationServiceHierarchical locations—Docs Âģ
OrderorderServiceOrder management—Docs Âģ
ChatchatServiceChannels, messages, templates, attachments, WS streamingfileStorageDriver, webSocketServerDocs Âģ
NotificationnotificationServiceList notifications, mark read (single/batch)—Docs Âģ
AddressaddressServiceAddress lookupfindAddressDriverDocs Âģ

â„šī¸ Import services from @nodeblocks/backend-sdk via the services namespace: import { services } from '@nodeblocks/backend-sdk'.

Configuration notes:

  • authService accepts Partial<AuthenticationServiceConfiguration> — defaults are merged internally via getMergedAuthConfig(). See the Authentication Service doc for the full config surface.
  • dataStores types mark some MongoDB collections as optional (e.g. invitations, onetimetokens for auth; productVariants for products; organizationChangeRequests for organizations). See each per-service doc for required collections.

🧑‍đŸ’ģ Mounting a Service​

All services are factory functions with this signature:

(dataStores, configuration, drivers?) => express.Router

The optional third drivers argument varies by service — see the catalog table above.

Basic HTTP service​

import express from 'express';

import { middlewares, services, drivers } from '@nodeblocks/backend-sdk';

const { nodeBlocksErrorMiddleware } = middlewares;
const { identitiesService } = services;
const { withMongo } = drivers;

const connectToDatabase = withMongo(
'mongodb://localhost:27017',
'dev',
'user',
'password'
);

express()
.use(
'/api/identities',
identitiesService(
{
identities: await connectToDatabase('identities'),
},
{
authSecrets: {
authEncSecret: 'your-encryption-secret',
authSignSecret: 'your-signing-secret',
},
authMode: 'bearer', // or 'cookie' — requires cookie-parser middleware
identity: {
typeIds: {
admin: 'admin-type-id',
guest: 'guest-type-id',
regular: 'regular-type-id',
},
},
}
)
)
.use(nodeBlocksErrorMiddleware())
.listen(8089, () => console.log('Server running'));

Services with drivers​

Some services require external drivers passed as the third argument:

import { services } from '@nodeblocks/backend-sdk';

const { authService, chatService, profileService } = services;

app.use('/api/auth', authService(dataStores, config, {
mailService,
googleOAuthDriver,
twitterOAuthDriver,
lineOAuthDriver,
}));

app.use('/api/profiles', profileService(dataStores, config, {
fileStorageDriver,
}));

app.use('/api/chat', chatService(dataStores, config, {
fileStorageDriver,
webSocketServer, // required for streamChatMessagesFeature
}));

🔧 Building a Custom Service​

Compose any subset of features into your own factory — the SDK shipped services use the same pattern:

import { partial } from 'ramda';

import { features, primitives, utils } from '@nodeblocks/backend-sdk';

const { getIdentityFeature, findIdentitiesFeature } = features;
const { compose, defService } = primitives;
const { getBearerTokenInfo } = utils;

export const myIdentityService = (dataStores, configuration) =>
defService(
partial(
compose(getIdentityFeature, findIdentitiesFeature),
[{ dataStores, configuration, authenticate: getBearerTokenInfo }]
)
);

Pick features from the features namespace, inject only the drivers your selected routes need, and mount the returned router on Express. See Feature Âģ for feature composition details.


📐 Good Practices​

  1. One domain per service factory — each shipped service targets one business area.
  2. Compose features, don't register routes manually — let defService wire validators and handlers from composed features.
  3. Inject auth and drivers via partial — avoid globals; pass authenticate, mailService, fileStorageDriver, etc. in the deps object.
  4. Mount error middleware at app level — nodeBlocksErrorMiddleware() is the host app's responsibility, not part of the service factory.
  5. Check driver requirements — file upload routes need fileStorageDriver; OAuth and email flows need their respective drivers; chat WebSocket streaming needs webSocketServer.

âžĄī¸ Next​

Start with the Authentication Service, or explore other services from the catalog above. For component-level details, see Feature Âģ, Route Âģ, Schema Âģ, and Blocks Âģ. For real-time chat, see the WebSocket Service Guide Âģ.