Overview
Bindings provide a powerful dependency injection system for handlers, allowing you to inject models, values, and custom dependencies into handler contexts.
BindingsHelpers
Helper methods available in the bindings factory function.
bind
Wraps a binding factory for type consistency.
bindings: (bind) => ({
currentUser: bind((options) => ({
resolve: async ({ request, fail }) => {
const token = request?.headers.authorization;
const user = await verifyToken(token);
if (!user) throw fail('unauthorized', {});
return user;
},
})),
})
A function that returns a binding definition
Optional explicit name for the binding (for literal type preservation)
The wrapped binding factory with proper typing
model
Creates a model binding for automatic entity loading.
bindings: (bind) => ({
user: bind.model(User, {
from: 'params',
fromKey: 'userId',
primaryKey: 'id',
async load({ id, fail }) {
const user = await User.findById(id);
if (!user) throw fail('notFound', { id });
return user;
},
}),
})
The model class or identifier to bind
options
BindingModelOptions | BindingModelOptions[]
Configuration for model loadingfrom
'params' | 'query' | 'headers' | 'body'
default:"params"
Source to extract the identifier from
Key to extract from the source (defaults to model name)
Additional where conditions to apply
Function to transform the extracted value
load
(ctx: { [key]: unknown, fail }) => Promise<Model>
Custom loader function for fetching the model
notFound
(ctx: { [key]: unknown, fail }) => Promise<Model | undefined>
Handler for when the model is not found
A binding factory that loads and validates the model
value
Creates a static value binding.
bindings: (bind) => ({
config: bind.value({
apiUrl: 'https://api.example.com',
timeout: 5000,
}),
})
The value to inject into the handler context
A binding factory that returns the static value
BindingDefinition
The core binding definition interface.
interface BindingDefinition<
TPayloadSchema,
TResult,
THandler,
TResponseHandler,
TMode
> {
mode?: TMode;
payload?: TPayloadSchema;
inject?: (ctx: BindingInjectContext) => PromiseOr<boolean>;
resolve: (ctx: BindingResolveContext) => Promise<TResult>;
}
mode
BindingMode
default:"toInject"
How the binding is injected into handlers
"toInject": Requires explicit opt-in via handler options
"alwaysInjected": Always injected into every handler
"variativeInject": Conditionally injected based on inject function
Schema for validating the binding’s payload
inject
(ctx: BindingInjectContext) => Promise<boolean> | boolean
Function to determine if the binding should be injected (for variativeInject mode)The current request object
resolve
(ctx: BindingResolveContext) => Promise<TResult>
required
Function that resolves the binding valueThe current request object
ctx.fail
(name: string, input: any) => never
Function to throw formatted errors
BindingInstance
Marker type to prevent object destructuring in handler context.
type BindingInstance<T> = T & {
[bindingInstanceSymbol]: never;
};
When a binding returns a BindingInstance, it will be injected under its binding name instead of being spread into the context.
// Without BindingInstance - fields are spread
const binding1 = () => ({
resolve: async () => ({ name: 'John', age: 30 })
});
// Context: { name: 'John', age: 30 }
// With BindingInstance - kept under binding name
const binding2 = () => ({
resolve: async () => ({
name: 'John',
age: 30,
[bindingInstanceSymbol]: ''
})
});
// Context: { user: { name: 'John', age: 30 } }
Usage Examples
Custom Authentication Binding
import { createOptions } from '@apisr/controller';
import { verifyJWT } from './auth';
const options = createOptions({
bindings: (bind) => ({
auth: bind((required = true) => ({
mode: 'variativeInject',
async inject({ request }) {
// Only inject if authorization header exists
return !!request?.headers.authorization;
},
async resolve({ request, fail }) {
const token = request?.headers.authorization?.replace('Bearer ', '');
if (!token && required) {
throw fail('unauthorized', { message: 'Missing token' });
}
if (!token) return null;
const user = await verifyJWT(token);
if (!user && required) {
throw fail('unauthorized', { message: 'Invalid token' });
}
return user;
},
})),
}),
});
// Use in handler
const handler = createHandler(options);
const protectedRoute = handler(
async ({ auth }) => {
return { userId: auth.id };
},
{ auth: true } // required auth
);
const optionalRoute = handler(
async ({ auth }) => {
return { userId: auth?.id };
},
{ auth: false } // optional auth
);
Database Transaction Binding
const options = createOptions({
bindings: (bind) => ({
transaction: bind(() => ({
mode: 'alwaysInjected',
async resolve() {
const trx = await db.transaction();
return {
db: trx,
async commit() {
await trx.commit();
},
async rollback() {
await trx.rollback();
},
[bindingInstanceSymbol]: '',
};
},
})),
}),
});
Multi-Option Model Binding
const options = createOptions({
bindings: (bind) => ({
post: bind.model(Post, [
{
from: 'params',
fromKey: 'postId',
primaryKey: 'id',
},
{
where: { published: true },
},
{
async load({ id, fail }) {
const post = await Post.findById(id);
if (!post) throw fail('notFound', { id });
return post;
},
},
]),
}),
});