🧩 Features
A feature is a convenience wrapper that groups together a schema with one or more routes. It allows you to compose complex behavior while keeping service factories concise.
1️⃣ Anatomy
Features are composed using the compose
function, which takes multiple components (schemas, routes, or other features) and combines them into a single composable unit. This composition pattern enables you to build complex functionality from simple, reusable parts.
import { compose } from '../primitives';
import { createUserSchema, updateUserSchema } from '../schemas/user';
import { createUserRoute, getUserRoute, findUsersRoute, updateUserRoute } from '../routes/user';
export const createUserFeature = compose(createUserSchema, createUserRoute);
export const getUserFeatures = compose(getUserRoute, findUsersRoute);
export const editUserFeatures = compose(updateUserSchema, updateUserRoute);
compose
flattens any number of arguments (schemas, routes, even other features) into a single Composable object that the service can consume. The composition order matters - schemas are typically composed first to ensure validation happens before route execution.
2️⃣ Why Use Features?
- Reusability – share the same feature across multiple services.
- Override-friendly – replace or extend a feature in downstream apps.
- Clarity – hide low-level details inside the feature.
3️⃣ Good Practices
- One responsibility – create separate features for create, read, update, delete.
- Name consistently –
<verb><Entity>Feature(s)
helps discovery. - Compose schemas first – validation should always precede route execution.
4️⃣ Using in a Service
Features are consumed by services through the defService
function, which provides the necessary context (like database connections) to the composed features. The partial
function from Ramda is used to pre-apply the database configuration, creating a service factory that can be easily integrated into your Express application.
import { compose, defService } from '../primitives';
import { partial } from 'lodash';
import { createUserFeature, getUserFeatures, editUserFeatures } from '../features/user';
export const userService = (db: any, configuration: any) =>
defService(
partial(
compose(editUserFeatures, getUserFeatures, createUserFeature),
{ dataStores: db, configuration }
)
);
This pattern allows you to create clean, modular services that are easy to test, maintain, and extend. The service factory pattern ensures that database connections and other dependencies are properly injected into your features.
➡️ Next
Learn about the Service Component to understand how features are composed into complete services. All services follow the same patterns and conventions!