Docs
Common Patterns
Hooks and Handlers - Common Patterns Common patterns and best practices for working with hooks and handlers. Common Patterns Pattern 1: Validation and Transformation // preHook if (!$ctx.$body.email) { $ctx.$throw['400']('Email is required'); return; } $ctx.$body.email = $ctx.$bo
Hooks and Handlers - Common Patterns
Common patterns and best practices for working with hooks and handlers.
Common Patterns
Pattern 1: Validation and Transformation
// preHook
if (!$ctx.$body.email) {
$ctx.$throw['400']('Email is required');
return;
}
$ctx.$body.email = $ctx.$body.email.toLowerCase().trim();
$ctx.$share.validationPassed = true;
// Handler uses normalized data automatically
Pattern 2: Permission-Based Filtering
// preHook
if ($ctx.$user.role !== 'admin') {
// Non-admins only see their own records
$ctx.$query.filter = {
...($ctx.$query.filter || {}),
userId: { _eq: $ctx.$user.id }
};
}
// Handler/Default CRUD uses filtered query
Pattern 3: Response Enhancement
// postHook
if ($ctx.$data && Array.isArray($ctx.$data.data)) {
$ctx.$data.data = $ctx.$data.data.map(item => ({
...item,
displayName: `${item.firstName} ${item.lastName}`,
formattedPrice: `$${item.price.toFixed(2)}`
}));
}
Pattern 4: Audit Trail
// postHook — runs on both success and error
await #audit_logs.create({
data: {
action: `${@API.request.method} ${@API.request.url}`,
userId: @USER?.id,
statusCode: @STATUS,
error: @ERROR ? @ERROR.message : null,
timestamp: new Date()
}
});
Pattern 5: Shared Context
// preHook
$ctx.$share.processStartTime = Date.now();
$ctx.$share.userId = $ctx.$user.id;
// postHook
if ($ctx.$share.processStartTime) {
const processingTime = Date.now() - $ctx.$share.processStartTime;
$ctx.$data.processingTime = processingTime;
}
Pattern 6: Error Logging
// postHook — runs even when preHook/handler throws
if (@ERROR) {
@LOGS(`Error occurred: ${@ERROR.message}`);
await #error_logs.create({
data: {
errorMessage: @ERROR.message,
statusCode: @ERROR.statusCode,
userId: @USER?.id,
url: @API.request.url
}
});
}
Pattern 7: Rate Limiting
// preHook - Protect endpoints from abuse
const result = await $ctx.$helpers.$rateLimit.byIp({
maxRequests: 100,
perSeconds: 60
});
if (!result.allowed) {
$ctx.$throw['429'](`Rate limit exceeded. Try again in ${result.retryAfter}s`);
return;
}
Pattern 8: Rate Limiting with Admin Skip
// preHook - Skip rate limit for admin users
if (!$ctx.$user?.isRootAdmin) {
const result = await $ctx.$helpers.$rateLimit.byUser({
maxRequests: 1000,
perSeconds: 3600
});
if (!result.allowed) {
$ctx.$throw['429']('API rate limit exceeded');
return;
}
}
Pattern 9: Login Attempt Rate Limiting
// preHook - Protect login from brute force
const result = await $ctx.$helpers.$rateLimit.byIp({
maxRequests: 5,
perSeconds: 60
});
if (!result.allowed) {
$ctx.$throw['429'](`Too many login attempts. Try again in ${result.retryAfter}s`);
return;
}
Best Practices
Hook Organization
- Global hooks for cross-cutting concerns (auth, logging)
- Route hooks for route-specific logic
- Keep hooks focused - one responsibility per hook
- Use descriptive names in hook definitions
Code Quality
- Validate early in preHooks
- Transform data in preHooks before handler
- Enhance responses in postHooks after handler
- Use shared context to pass data between hooks
- Log important steps for debugging
Error Handling
- Throw errors early in preHooks for fast failure
- Handle errors gracefully in postHooks
- Provide meaningful error messages
- Use appropriate HTTP status codes
Performance
- Minimize database calls - batch operations when possible
- Cache expensive operations in shared context
- Use early returns to avoid unnecessary processing
- Consider execution order for optimal performance
Next Steps
- See preHooks for pre-handler operations
- Learn about postHooks for post-handler operations
- Check Custom Handlers for custom business logic
- Learn about Repository Methods for database operations
- See Context Reference for all available properties
- Check API Lifecycle to understand execution order