Configuration
The withResourceful mixin accepts a configuration object that controls all aspects of the resourceful functionality. This guide provides comprehensive documentation for each configuration option, organized by logical sections.
For complete API reference, see the withResourceful function documentation and ResourcefulMixinOptions interface.
Basic Configuration
Model Identity
name
- Type:
string - Default: The class name of the model
- Required: No
The display name for the model used in OpenAPI documentation and error messages. If not provided, it defaults to the class name.
export default class User extends compose(BaseModel, withResourceful({
name: 'User' // Will default to 'User' if omitted
})) {
// ...
}description
- Type:
string - Default:
undefined - Required: No
Optional description for the model that appears in OpenAPI documentation.
export default class User extends compose(BaseModel, withResourceful({
name: 'User',
description: 'Application user with role-based permissions'
})) {
// ...
}example
- Type:
string - Default:
undefined - Required: No
Optional example value for the model schema in OpenAPI documentation.
export default class User extends compose(BaseModel, withResourceful({
name: 'User',
example: '{ "id": 1, "name": "John Doe", "email": "john@example.com" }'
})) {
// ...
}externalDocs
- Type:
ExternalDocumentationObject - Default:
undefined - Required: No
Optional external documentation reference for OpenAPI schema. See ExternalDocumentationObject for details.
export default class User extends compose(BaseModel, withResourceful({
name: 'User',
externalDocs: {
description: 'User Management API Documentation',
url: 'https://docs.example.com/api/users'
}
})) {
// ...
}Access Control Configuration
Access Control Filters
Access control filters are functions that determine whether a user can perform specific operations. They return true to allow access or false to deny it.
accessControlFilters.list
- Type:
ResourcefulGeneralAccessControlFilter[] - Default:
[](no restrictions) - Signature:
(ctx: HttpContext, app: ApplicationService) => Promise<boolean> | boolean
Controls access to listing/index operations. These filters run before any database queries. See ResourcefulGeneralAccessControlFilter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
accessControlFilters: {
list: [
// Only authenticated users can list users
(ctx) => !!ctx.auth.user,
// Only admins can list all users
(ctx) => ctx.auth.user?.role === 'admin'
]
}
})) {
// ...
}accessControlFilters.create
- Type:
ResourcefulGeneralAccessControlFilter[] - Default:
[](no restrictions) - Signature:
(ctx: HttpContext, app: ApplicationService) => Promise<boolean> | boolean
Controls access to create operations. These filters run before payload validation. See ResourcefulGeneralAccessControlFilter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
accessControlFilters: {
create: [
// Only admins can create users
(ctx) => ctx.auth.user?.role === 'admin',
// Rate limiting check
async (ctx, app) => {
const rateLimiter = await app.container.make('RateLimiter')
return rateLimiter.check(ctx.auth.user?.id)
}
]
}
})) {
// ...
}accessControlFilters.read
- Type:
ResourcefulResourceAccessControlFilter[] - Default:
[](no restrictions) - Signature:
(ctx: HttpContext, app: ApplicationService, instance: ModelInstance) => Promise<boolean> | boolean
Controls access to read operations for specific model instances. These filters receive the actual model instance. See ResourcefulResourceAccessControlFilter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
accessControlFilters: {
read: [
// Users can read their own profile
(ctx, app, user) => ctx.auth.user?.id === user.id,
// Admins can read any profile
(ctx, app, user) => ctx.auth.user?.role === 'admin'
]
}
})) {
// ...
}accessControlFilters.update
- Type:
ResourcefulResourceAccessControlFilter[] - Default:
[](no restrictions) - Signature:
(ctx: HttpContext, app: ApplicationService, instance: ModelInstance) => Promise<boolean> | boolean
Controls access to update operations for specific model instances. See ResourcefulResourceAccessControlFilter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
accessControlFilters: {
update: [
// Users can update their own profile
(ctx, app, user) => ctx.auth.user?.id === user.id,
// Admins can update any profile
(ctx, app, user) => ctx.auth.user?.role === 'admin'
]
}
})) {
// ...
}accessControlFilters.delete
- Type:
ResourcefulResourceAccessControlFilter[] - Default:
[](no restrictions) - Signature:
(ctx: HttpContext, app: ApplicationService, instance: ModelInstance) => Promise<boolean> | boolean
Controls access to delete operations for specific model instances. See ResourcefulResourceAccessControlFilter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
accessControlFilters: {
delete: [
// Only admins can delete users
(ctx, app, user) => ctx.auth.user?.role === 'admin',
// Cannot delete your own account
(ctx, app, user) => ctx.auth.user?.id !== user.id
]
}
})) {
// ...
}ACL Behavior Configuration
readRequiredForWrite
- Type:
boolean - Default:
false
When set to true, read access control filters must pass before write access control filters are evaluated. This ensures users can only modify records they can read.
export default class User extends compose(BaseModel, withResourceful({
readRequiredForWrite: true, // Read ACL must pass before write ACL
accessControlFilters: {
read: [(ctx, app, user) => ctx.auth.user?.tenantId === user.tenantId],
update: [(ctx, app, user) => ctx.auth.user?.role === 'admin']
}
})) {
// ...
}Query Scoping
Query scope callbacks modify database queries to enforce data boundaries based on request context. They run before access control filters and help implement multi-tenancy, soft deletes, or other data filtering requirements.
queryScopeCallbacks.list
- Type:
ResourcefulQueryScopeCallback[] - Default:
[] - Signature:
(ctx: HttpContext, app: ApplicationService, query: DatabaseQueryBuilderContract) => Promise<void> | void
Applied to queries for listing/index operations. See ResourcefulQueryScopeCallback for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
queryScopeCallbacks: {
list: [
// Multi-tenancy: only show users from current tenant
(ctx, app, query) => {
query.where('tenant_id', ctx.auth.user?.tenantId)
},
// Only show active users
(ctx, app, query) => {
query.where('is_active', true)
}
]
}
})) {
// ...
}queryScopeCallbacks.access
- Type:
ResourcefulQueryScopeCallback[] - Default:
[] - Signature:
(ctx: HttpContext, app: ApplicationService, query: DatabaseQueryBuilderContract) => Promise<void> | void
Applied to queries for individual record access (read, update, delete operations). See ResourcefulQueryScopeCallback for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
queryScopeCallbacks: {
access: [
// Multi-tenancy: only access users from current tenant
(ctx, app, query) => {
query.where('tenant_id', ctx.auth.user?.tenantId)
}
]
}
})) {
// ...
}Payload Validation
payloadValidationSchemaBuilders
Payload validation schema builders generate context-specific Joi schemas for validating request payloads during create and update operations.
payloadValidationSchemaBuilders.create
- Type:
ResourcefulPayloadValidatorGetter[] - Default:
[] - Signature:
(ctx: HttpContext, app: ApplicationService) => Promise<AnySchema> | AnySchema
Generates validation schemas for create operation payloads. See ResourcefulPayloadValidatorGetter for the complete interface.
import { joi } from '@nhtio/lucid-resourceful/joi'
export default class User extends compose(BaseModel, withResourceful({
payloadValidationSchemaBuilders: {
create: [
// Base validation for user creation
() => joi.object({
name: joi.string().min(2).max(100).required(),
email: joi.string().email().required(),
password: joi.string().min(8).required()
}),
// Role-based validation
(ctx) => {
if (ctx.auth.user?.role === 'admin') {
return joi.object({
role: joi.string().valid('user', 'admin').optional()
})
}
return joi.object({})
}
]
}
})) {
// ...
}payloadValidationSchemaBuilders.update
- Type:
ResourcefulPayloadValidatorGetter[] - Default:
[] - Signature:
(ctx: HttpContext, app: ApplicationService) => Promise<AnySchema> | AnySchema
Generates validation schemas for update operation payloads. See ResourcefulPayloadValidatorGetter for the complete interface.
export default class User extends compose(BaseModel, withResourceful({
payloadValidationSchemaBuilders: {
update: [
// Base validation for user updates
() => joi.object({
name: joi.string().min(2).max(100).optional(),
email: joi.string().email().optional(),
password: joi.string().min(8).optional()
}),
// Prevent non-admins from changing roles
(ctx) => {
if (ctx.auth.user?.role !== 'admin') {
return joi.object({
role: joi.forbidden()
})
}
return joi.object({})
}
]
}
})) {
// ...
}Error Handling
onACLError
- Type:
'bubble' | 'pass' | 'fail' - Default:
'bubble'
Controls how ACL evaluation errors are handled. See ResourcefulErrorHandlerMethod for details:
'bubble': Re-throw the error (default behavior)'pass': Treat errors as passing ACL checks (allow access)'fail': Treat errors as failing ACL checks (deny access)
export default class User extends compose(BaseModel, withResourceful({
onACLError: 'fail', // Deny access when ACL evaluation fails
accessControlFilters: {
read: [
async (ctx, app, user) => {
// If this throws an error, access will be denied
const permission = await app.container.make('PermissionService')
return permission.canRead(ctx.auth.user, user)
}
]
}
})) {
// ...
}onValidationScopeError
- Type:
'bubble' | 'pass' | 'fail' - Default:
'bubble'
Controls how validation scope evaluation errors are handled. See ResourcefulErrorHandlerMethod for details:
'bubble': Re-throw the error (default behavior)'pass': Skip validation when scope evaluation fails'fail': Fail validation when scope evaluation fails
export default class User extends compose(BaseModel, withResourceful({
onValidationScopeError: 'pass', // Skip validation if scope evaluation fails
// ...
})) {
// ...
}Performance Tuning
advanced.propertyEvaluationConcurrency
- Type:
number - Default:
10 - Minimum:
1
Controls the concurrency limit for evaluating property-level ACL filters. Higher values can improve performance for models with many fields, but may increase memory usage. See AdvancedResourcefulMixinOptions for details.
advanced.aclEvaluationConcurrency
- Type:
number - Default:
2 - Minimum:
1
Controls the concurrency limit for evaluating model-level ACL filters. Lower values help with early termination when access is denied. See AdvancedResourcefulMixinOptions for details.
export default class User extends compose(BaseModel, withResourceful({
advanced: {
propertyEvaluationConcurrency: 15, // Evaluate up to 15 field ACLs concurrently
aclEvaluationConcurrency: 1 // Evaluate model ACLs sequentially for fast failure
}
})) {
// ...
}Complete Example
Here's a comprehensive example combining multiple configuration options:
import { DateTime } from 'luxon'
import { compose } from '@adonisjs/core/helpers'
import { BaseModel } from '@adonisjs/lucid/orm'
import { withResourceful, resourcefulColumn } from '@nhtio/lucid-resourceful'
import { ResourcefulUnsignedIntegerType, ResourcefulStringType } from '@nhtio/lucid-resourceful/definitions'
import { joi } from '@nhtio/lucid-resourceful/joi'
export default class User extends compose(BaseModel, withResourceful({
// Basic configuration
name: 'User',
description: 'Application user with role-based permissions',
example: '{ "id": 1, "name": "John Doe", "email": "john@example.com" }',
// ACL behavior
readRequiredForWrite: true,
// Access control filters
accessControlFilters: {
list: [
(ctx) => !!ctx.auth.user
],
create: [
(ctx) => ctx.auth.user?.role === 'admin'
],
read: [
(ctx, app, user) => ctx.auth.user?.id === user.id,
(ctx, app, user) => ctx.auth.user?.role === 'admin'
],
update: [
(ctx, app, user) => ctx.auth.user?.id === user.id,
(ctx, app, user) => ctx.auth.user?.role === 'admin'
],
delete: [
(ctx, app, user) => ctx.auth.user?.role === 'admin'
]
},
// Query scoping
queryScopeCallbacks: {
list: [
(ctx, app, query) => query.where('tenant_id', ctx.auth.user?.tenantId)
],
access: [
(ctx, app, query) => query.where('tenant_id', ctx.auth.user?.tenantId)
]
},
// Payload validation
payloadValidationSchemaBuilders: {
create: [
() => joi.object({
name: joi.string().min(2).max(100).required(),
email: joi.string().email().required()
})
],
update: [
() => joi.object({
name: joi.string().min(2).max(100).optional(),
email: joi.string().email().optional()
})
]
},
// Error handling
onACLError: 'fail',
onValidationScopeError: 'pass',
// Performance tuning
advanced: {
propertyEvaluationConcurrency: 15,
aclEvaluationConcurrency: 1
}
})) {
@resourcefulColumn({ isPrimary: true, type: ResourcefulUnsignedIntegerType({ readOnly: true }) })
declare id: number
@resourcefulColumn({ type: ResourcefulStringType() })
declare name: string
@resourcefulColumn({ type: ResourcefulStringType() })
declare email: string
@resourcefulColumn.dateTime({ autoCreate: true })
declare createdAt: DateTime
@resourcefulColumn.dateTime({ autoCreate: true, autoUpdate: true })
declare updatedAt: DateTime
}