CRUD Operations
Lucid Resourceful provides a comprehensive set of CRUD (Create, Read, Update, Delete) operations through static methods on models that have been enhanced with the withResourceful mixin. These operations include built-in access control, validation, query scoping, and field-level security features.
Overview
Once a model is decorated with the resourceful mixin, it gains access to five primary CRUD methods:
$onResourcefulIndex- List and search records with pagination$onResourcefulRead- Retrieve a single record by ID$onResourcefulCreate- Create a new record$onResourcefulUpdate- Update an existing record$onResourcefulDelete- Delete a record
All CRUD operations support:
- Access Control Lists (ACL) - Both model-level and field-level permission checking
- Query Scoping - Automatic filtering based on request context
- Validation - Payload validation using Joi schemas
- Error Handling - Comprehensive error responses with specific error types
- Field Filtering - Response contains only fields the user has read access to
Core Parameters
All CRUD methods share common parameters:
ctx: HttpContext- AdonisJS HTTP context containing request information and authenticationapp: ApplicationService- Application service instance for accessing app-level serviceshooks?: Array- Optional callback functions for additional query scoping or validation
List Records ($onResourcefulIndex)
The index operation provides paginated listing and searching capabilities with comprehensive filtering support.
Method Signature
static async $onResourcefulIndex<SelectedFields, ReturnType>(
filter: string | null | undefined,
page: number,
perPage: number,
fields: SelectedFields | null | undefined,
ctx: HttpContext,
app: ApplicationService,
hooks?: ResourcefulScopeHooks
): Promise<ResourcefulIndexResult<ReturnType>>Parameters
| Parameter | Type | Description |
|---|---|---|
filter | string | null | undefined | Lucene-style query string for filtering records (e.g., "name:john AND email:*.com"). If null or undefined, defaults to empty string (no filtering). |
page | number | The page number for pagination (must be ≥ 1). Used with perPage to calculate offset. |
perPage | number | Number of records per page (must be ≥ 1 and ≤ 100 by default). |
fields | SelectedFields | null | undefined | Array of field names to include in the response. If null, undefined, or empty, defaults to just the primary key field. Field names should use serialized names (as they appear in API responses), not database column names. |
ctx | HttpContext | HTTP context containing request information, authentication, and other request-scoped data. |
app | ApplicationService | Application service instance providing access to application-level services and configuration. |
hooks? | ResourcefulScopeHooks | Optional array of query scope callbacks to apply additional filtering constraints. |
Return Value
Returns a Promise<ResourcefulIndexResult<ReturnType>> containing:
records- Array of partial record objects with only the requested fieldstotal- Total number of records matching the filter (before pagination)page- The requested page number (echoed back)perPage- The requested per-page limit (echoed back)countQuery- SQL query string used for counting total recordsrecordsQuery- SQL query string used for fetching the actual records
Filter Syntax
The filter parameter supports Lucene-style query syntax. For complete syntax documentation, see the official Lucene query parser syntax documentation.
Supported Features:
- Field queries:
name:john,email:user@example.com - Wildcards:
name:jo*,email:*@example.com - Boolean operators:
name:john AND email:*.com,status:active OR status:pending - Date ranges:
createdAt:[2021-01-01T00:00:00Z TO 2021-12-31T23:59:59Z] - Grouping:
(name:john OR name:jane) AND status:active
Unsupported Features:
The following Lucene features are not supported by the underlying query parser:
- Fuzzy Searches - Terms like
name:john~for approximate matching - Proximity Searches - Phrases like
"john doe"~10for word proximity - Boosting a Term - Terms like
name:john^2for relevance boosting
Usage Examples
// Basic pagination
const result = await User.$onResourcefulIndex(
'name:john',
1,
10,
['id', 'name', 'email'],
ctx,
app
);
// Complex filtering with date ranges
const result = await User.$onResourcefulIndex(
'name:john AND createdAt:[2021-01-01T00:00:00Z TO 2021-12-31T23:59:59Z]',
2,
25,
['id', 'name', 'createdAt'],
ctx,
app
);
// With additional query scoping
const result = await User.$onResourcefulIndex(
'status:active',
1,
20,
['id', 'name'],
ctx,
app,
[(ctx, app, query) => query.where('tenant_id', ctx.auth.user.tenantId)]
);Thrown Exceptions
E_MISSING_PRIMARY_KEY_EXCEPTION- When the model has no identifiable primary keyE_FORBIDDEN- When access is denied by model-level or field-level ACL filtersE_INVALID_COLUMN_ACCESS- When no fields are available for access after ACL filteringE_INVALID_RESOUREFUL_INDEX_REQUEST_EXCEPTION- When input validation fails
API Reference
For complete type definitions and additional details, see the ResourcefulModel.$onResourcefulIndex API documentation.
Read Single Record ($onResourcefulRead)
Retrieves a single model record by its unique identifier with comprehensive access control.
Method Signature
static async $onResourcefulRead(
uid: number,
ctx: HttpContext,
app: ApplicationService,
hooks?: ResourcefulScopeHooks
): Promise<ResourcefulModelSerializableAttributes>Parameters
| Parameter | Type | Description |
|---|---|---|
uid | number | The unique identifier of the record to retrieve |
ctx | HttpContext | HTTP context containing request information and authentication |
app | ApplicationService | Application service instance for accessing app-level services |
hooks? | ResourcefulScopeHooks | Optional array of query scope callbacks to apply additional filtering constraints |
Return Value
Returns a Promise<ResourcefulModelSerializableAttributes> containing the record object with only accessible fields based on ACL permissions.
Access Control Flow
- Query Scoping - Applies configured query scope callbacks to verify record exists within user's access scope
- Record Retrieval - Fetches the full model instance for field-level ACL evaluation
- Field-Level ACL - Evaluates read permissions for each field
- Response Filtering - Returns only fields that pass ACL checks
Usage Examples
// Retrieve a user by ID
const user = await User.$onResourcefulRead(123, ctx, app);
// With additional query scoping
const user = await User.$onResourcefulRead(123, ctx, app, [
(ctx, app, query) => query.where('tenant_id', ctx.auth.user.tenantId)
]);
// Result contains only fields the current user can read
console.log(user); // { id: 123, name: "John Doe" } - email field filtered out by ACLThrown Exceptions
E_MISSING_PRIMARY_KEY_EXCEPTION- When the model has no identifiable primary keyE_RECORD_NOT_FOUND_EXCEPTION- When no record exists with the given ID or user lacks accessE_FORBIDDEN- When access is denied by model-level or field-level ACL filters
API Reference
For complete type definitions and additional details, see the ResourcefulModel.$onResourcefulRead API documentation.
Create New Record ($onResourcefulCreate)
Creates a new model record with payload validation and comprehensive access control.
Method Signature
static async $onResourcefulCreate(
payload: any,
ctx: HttpContext,
app: ApplicationService,
hooks?: ResourcefulValidationHooks
): Promise<ResourcefulModelSerializableAttributes>Parameters
| Parameter | Type | Description |
|---|---|---|
payload | any | The data object containing field values for the new record |
ctx | HttpContext | HTTP context containing request information and authentication |
app | ApplicationService | Application service instance for accessing app-level services |
hooks? | ResourcefulValidationHooks | Optional array of validation schema getters for additional payload validation |
Return Value
Returns a Promise<ResourcefulModelSerializableAttributes> containing the created record with only accessible fields (uses $onResourcefulRead internally for consistent field filtering).
Validation & Security Flow
- Access Control - Checks model-level create permissions
- Payload Validation - Validates against both model-level and request-specific schemas
- Field-Level ACL - Verifies write permissions for each field in the payload
- Record Creation - Creates the new record with validated data
- Response Filtering - Returns created record via
$onResourcefulReadfor consistent ACL application
Usage Examples
// Create a new user
const user = await User.$onResourcefulCreate(
{
name: "John Doe",
email: "john@example.com"
},
ctx,
app
);
// With additional validation
const user = await User.$onResourcefulCreate(
payload,
ctx,
app,
[
(ctx, app) => joi.object({
email: joi.string().domain('company.com')
})
]
);
// Fields without write access are ignored, forbidden fields throw errors
const restrictedUser = await User.$onResourcefulCreate(
{
name: "Jane Doe",
email: "jane@example.com",
isAdmin: true // This field may be filtered out or cause an error based on ACL
},
ctx,
app
);Validation Hooks
The hooks parameter accepts an array of validation schema getter functions:
type ResourcefulValidationHooks = Array<
(ctx: HttpContext, app: ApplicationService) => Schema | null
>These functions are called with the current context and can return additional Joi validation schemas that will be applied to the payload.
Thrown Exceptions
E_MISSING_PRIMARY_KEY_EXCEPTION- When the model has no identifiable primary keyE_INVALID_PAYLOAD_EXCEPTION- When core model validation failsE_FORBIDDEN_PAYLOAD_EXCEPTION- When request-specific validation failsE_FORBIDDEN- When access is denied by model-level or field-level ACL filters
API Reference
For complete type definitions and additional details, see the ResourcefulModel.$onResourcefulCreate API documentation.
Update Existing Record ($onResourcefulUpdate)
Updates an existing model record with payload validation and access control.
Method Signature
static async $onResourcefulUpdate(
uid: number,
payload: any,
ctx: HttpContext,
app: ApplicationService,
hooks?: Partial<ResourcefulHooks>
): Promise<ResourcefulModelSerializableAttributes>Parameters
| Parameter | Type | Description |
|---|---|---|
uid | number | The unique identifier of the record to update |
payload | any | The data object containing field values to update |
ctx | HttpContext | HTTP context containing request information and authentication |
app | ApplicationService | Application service instance for accessing app-level services |
hooks? | Partial<ResourcefulHooks> | Optional object containing query scope callbacks and validation schema getters |
Hooks Parameter
The hooks parameter accepts an object with the following optional properties:
{
queryScopeCallbacks?: ResourcefulScopeHooks,
payloadValidationSchemas?: ResourcefulValidationHooks
}queryScopeCallbacks- Additional query scoping for record access verificationpayloadValidationSchemas- Additional validation schemas for the update payload
Return Value
Returns a Promise<ResourcefulModelSerializableAttributes> containing the updated record with only accessible fields.
Update Flow
- Record Verification - Uses
$onResourcefulReadto verify record exists and user has access - Payload Validation - Validates update payload against model and request-specific schemas
- Field-Level ACL - Checks write permissions for each field being updated
- Record Update - Applies validated changes to the existing record
- Response Filtering - Returns updated record via
$onResourcefulReadfor consistent field filtering
Usage Examples
// Update a user
const user = await User.$onResourcefulUpdate(
123,
{
name: "Jane Doe"
},
ctx,
app
);
// With additional scoping and validation
const user = await User.$onResourcefulUpdate(
123,
payload,
ctx,
app,
{
queryScopeCallbacks: [
(ctx, app, query) => query.where('active', true)
],
payloadValidationSchemas: [
(ctx, app) => customValidationSchema
]
}
);
// Partial updates - only provided fields are updated
const partialUpdate = await User.$onResourcefulUpdate(
123,
{ name: "Updated Name" }, // Only name field will be updated
ctx,
app
);Thrown Exceptions
E_MISSING_PRIMARY_KEY_EXCEPTION- When the model has no identifiable primary keyE_RECORD_NOT_FOUND_EXCEPTION- When no record exists with the given ID or user lacks accessE_INVALID_PAYLOAD_EXCEPTION- When core model validation failsE_FORBIDDEN_PAYLOAD_EXCEPTION- When request-specific validation failsE_FORBIDDEN- When access is denied by model-level or field-level ACL filters
API Reference
For complete type definitions and additional details, see the ResourcefulModel.$onResourcefulUpdate API documentation.
Delete Record ($onResourcefulDelete)
Deletes an existing model record with access control verification.
Method Signature
static async $onResourcefulDelete(
uid: number,
ctx: HttpContext,
app: ApplicationService,
hooks?: ResourcefulScopeHooks
): Promise<void>Parameters
| Parameter | Type | Description |
|---|---|---|
uid | number | The unique identifier of the record to delete |
ctx | HttpContext | HTTP context containing request information and authentication |
app | ApplicationService | Application service instance for accessing app-level services |
hooks? | ResourcefulScopeHooks | Optional array of query scope callbacks to apply additional filtering constraints |
Return Value
Returns a Promise<void> that resolves when the record has been successfully deleted.
Deletion Flow
- Query Scoping - Applies configured query scope callbacks to verify record exists within user's access scope
- Record Verification - Fetches the record to confirm it exists and is accessible
- Access Control - Checks delete permissions via model-level ACL filters
- Record Deletion - Removes the record from the database
Usage Examples
// Delete a user
await User.$onResourcefulDelete(123, ctx, app);
// With additional query scoping
await User.$onResourcefulDelete(123, ctx, app, [
(ctx, app, query) => query.where('tenant_id', ctx.auth.user.tenantId)
]);
// Delete with confirmation
try {
await User.$onResourcefulDelete(userId, ctx, app);
console.log('User deleted successfully');
} catch (error) {
if (error instanceof E_RECORD_NOT_FOUND_EXCEPTION) {
console.log('User not found or access denied');
} else if (error instanceof E_FORBIDDEN) {
console.log('Insufficient permissions to delete user');
} else {
throw error;
}
}Security Considerations
- No Field-Level ACL - Delete operations only check model-level permissions, not individual field permissions
- Query Scoping - Records outside the user's query scope are treated as non-existent
- Soft Deletes - If the model uses Lucid's soft delete features, this method respects that configuration
Thrown Exceptions
E_MISSING_PRIMARY_KEY_EXCEPTION- When the model has no identifiable primary keyE_RECORD_NOT_FOUND_EXCEPTION- When no record exists with the given ID or user lacks accessE_FORBIDDEN- When access is denied by model-level ACL filters
API Reference
For complete type definitions and additional details, see the ResourcefulModel.$onResourcefulDelete API documentation.
Access Control and Security
All CRUD operations implement comprehensive security through multiple layers:
Model-Level Access Control
Configured via the accessControlFilters option in the withResourceful mixin:
class User extends withResourceful({
accessControlFilters: {
list: [(ctx) => ctx.auth.user?.isActive],
read: [(ctx, app, instance) => ctx.auth.user?.id === instance.id],
create: [(ctx) => ctx.auth.user?.canCreateUsers],
update: [(ctx, app, instance) => ctx.auth.user?.id === instance.id],
delete: [(ctx, app, instance) => ctx.auth.user?.isAdmin]
}
})(BaseModel) {
// Model definition...
}Field-Level Access Control
Defined per-field using the @resourceful decorator:
@column()
@resourceful({
type: 'string',
nullable: false,
readAccessControlFilters: [(ctx) => ctx.auth.user?.isAdmin],
writeAccessControlFilters: [(ctx) => ctx.auth.user?.isOwner]
})
public sensitiveField: stringQuery Scoping
Automatically constrains database queries based on request context:
class User extends withResourceful({
queryScopeCallbacks: {
list: [(ctx, app, query) => query.where('tenant_id', ctx.auth.user.tenantId)],
access: [(ctx, app, query) => query.where('active', true)]
}
})(BaseModel) {
// Model definition...
}Validation System
CRUD operations support multiple validation layers. See the Validation documentation for comprehensive details on:
- Model-level validation using resourceful decorators
- Request-specific validation via hooks parameters
- Custom validation schemas with Joi integration
- Validation error handling and response formatting
Error Handling
All CRUD operations throw specific error types for different failure scenarios. See the Error Handling documentation for complete details on:
- Error types and inheritance hierarchy
- Type-safe error checking using
isInstanceOftype guards - Error response formatting and HTTP status mapping
- Best practices for error handling in applications
OpenAPI Schema Integration
Models with resourceful CRUD operations can generate OpenAPI schemas that reflect the actual accessible fields based on request context. See the OpenAPI documentation for details on:
- Context-aware schema generation using
$asOpenApiSchemaObject - Field filtering based on ACL permissions
- Type mapping from resourceful decorators to OpenAPI types
- API documentation generation for frontend and testing tools
Performance Considerations
Query Optimization
- Field Selection - The
fieldsparameter in$onResourcefulIndexreduces database load by selecting only required columns - Pagination - Built-in pagination prevents memory issues with large result sets
- Query Scoping - Database-level filtering is more efficient than application-level filtering
ACL Evaluation
- Concurrent Processing - ACL filters are evaluated with limited concurrency to balance performance and early termination
- Caching Strategy - No built-in caching due to security implications; implement application-level caching as needed
- Error Handling - ACL failures can be configured to halt immediately or allow graceful degradation
Validation Performance
- Schema Compilation - Joi schemas are compiled once and reused for better performance
- Validation Scope - Only fields being modified are validated during updates
- Early Termination - Validation stops on first error to minimize processing time
Best Practices
Security
- Always use HTTPS when transmitting sensitive data through CRUD operations
- Implement rate limiting for create, update, and delete operations
- Log security events using the event emitter for ACL failures and validation errors
- Validate user input at multiple layers (client, server, database)
- Use query scoping to prevent data leakage across tenant boundaries
Performance
- Select minimal fields in index operations to reduce database load
- Implement appropriate pagination limits based on your data size and usage patterns
- Use database indexes on fields commonly used in filters and query scoping
- Monitor query performance and optimize complex filters
Maintainability
- Organize ACL logic in separate service classes for complex permissions
- Create reusable validation schemas for common patterns
- Document custom query scopes and their intended use cases
- Test error scenarios to ensure proper error handling and user experience
API Design
- Use consistent field naming between database columns and API responses
- Provide meaningful error messages that help developers understand issues
- Document filter syntax and available fields for frontend developers
- Version your APIs when changing field definitions or access control rules
Related Documentation
- Decorators - Learn how to define resourceful fields and relationships
- Validation - Comprehensive guide to the validation system
- Error Handling - Complete error handling reference
- OpenAPI Integration - Schema generation for API documentation
- Configuration - Model and mixin configuration options