Skip to main content
Version: 0.13.0 (Previous)

๐Ÿ› ๏ธ 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 ยป.