CRUD Hooks
CRUD hooks allow you to add custom logic that executes before or after CRUD operations. This guide explains how to use hooks effectively in your GoCRUD applications.
Available Hooks
GoCRUD provides both "before" and "after" hooks for each CRUD operation:
Before Hooks
BeforeGet
: Executes before retrieving resourcesBeforePut
: Executes before updating resourcesBeforePost
: Executes before creating resourcesBeforeDelete
: Executes before deleting resources
After Hooks
AfterGet
: Executes after retrieving resourcesAfterPut
: Executes after updating resourcesAfterPost
: Executes after creating resourcesAfterDelete
: Executes after deleting resources
Hook Signatures
Each hook type has a specific function signature:
// Get operation hooks
BeforeGet func(ctx context.Context, where *map[string]any, order *map[string]any, limit *int, skip *int) error
AfterGet func(ctx context.Context, models *[]Model) error
// Put operation hooks
BeforePut func(ctx context.Context, models *[]Model) error
AfterPut func(ctx context.Context, models *[]Model) error
// Post operation hooks
BeforePost func(ctx context.Context, models *[]Model) error
AfterPost func(ctx context.Context, models *[]Model) error
// Delete operation hooks
BeforeDelete func(ctx context.Context, where *map[string]any) error
AfterDelete func(ctx context.Context, models *[]Model) error
Using Hooks
Basic Hook Configuration
Here's how to configure hooks when registering your model:
gocrud.Register(api, repo, &gocrud.Config[User]{
BeforePost: func(ctx context.Context, models *[]User) error {
// Add validation logic here
return nil
},
AfterPost: func(ctx context.Context, models *[]User) error {
// Add post-processing logic here
return nil
},
})
Common Use Cases
Input Validation
BeforePost: func(ctx context.Context, models *[]User) error {
for _, user := range *models {
if user.Age != nil && *user.Age < 18 {
return fmt.Errorf("users must be 18 or older")
}
}
return nil
}
Data Enrichment
AfterGet: func(ctx context.Context, models *[]User) error {
for i := range *models {
if (*models)[i].Name == nil {
defaultName := "Anonymous"
(*models)[i].Name = &defaultName
}
}
return nil
}
Access Control
BeforeGet: func(ctx context.Context, where *map[string]any, order *map[string]any, limit *int, skip *int) error {
userID := ctx.Value("userID").(string)
if userID == "" {
return fmt.Errorf("unauthorized")
}
return nil
}
Audit Logging
AfterDelete: func(ctx context.Context, models *[]User) error {
for _, user := range *models {
log.Printf("User deleted: ID=%v", *user.ID)
}
return nil
}
Hook Execution Order
-
Before hooks execute first, allowing you to:
- Validate input
- Modify query parameters
- Check permissions
- Cancel the operation by returning an error
-
The main operation executes only if the before hook succeeds
-
After hooks execute last, allowing you to:
- Modify returned data
- Trigger side effects
- Log operations
- Send notifications
Error Handling
- Any error returned from a hook will stop the operation
- Before hook errors prevent the main operation from executing
- After hook errors are returned to the client even though the main operation succeeded
Example error handling:
BeforePut: func(ctx context.Context, models *[]User) error {
for _, user := range *models {
if err := validateUser(user); err != nil {
return fmt.Errorf("validation failed: %w", err)
}
}
return nil
}
Best Practices
-
Keep Hooks Focused: Each hook should have a single responsibility
-
Handle Errors Gracefully: Always return meaningful error messages
-
Use Context: Leverage context for request-scoped data
-
Consider Performance: Avoid expensive operations in hooks
-
Be Careful with Mutations:
- Before hooks: Modify input parameters only when necessary
- After hooks: Be cautious when modifying returned data
Example: Complete Hook Configuration
config := &gocrud.Config[User]{
BeforeGet: func(ctx context.Context, where *map[string]any, order *map[string]any, limit *int, skip *int) error {
// Add query restrictions
return nil
},
AfterGet: func(ctx context.Context, models *[]User) error {
// Enrich returned data
return nil
},
BeforePost: func(ctx context.Context, models *[]User) error {
// Validate new resources
return nil
},
AfterPost: func(ctx context.Context, models *[]User) error {
// Send notifications
return nil
},
BeforePut: func(ctx context.Context, models *[]User) error {
// Validate updates
return nil
},
AfterPut: func(ctx context.Context, models *[]User) error {
// Log changes
return nil
},
BeforeDelete: func(ctx context.Context, where *map[string]any) error {
// Check delete permissions
return nil
},
AfterDelete: func(ctx context.Context, models *[]User) error {
// Cleanup related resources
return nil
},
}