Extensions System
This guide explains how to create and use extensions for BAASIX to extend its functionality.
Table of Contents
- Overview
- Extension Types
- Extension Structure
- Creating Custom Endpoints
- Creating Hook Extensions
- Creating Schedule Extensions
- Accessing Core Services
- Extension Configuration
- Examples
- Best Practices
Overview
BAASIX Extensions provide a powerful way to extend the functionality of the BAASIX API without modifying the core codebase. Extensions allow you to:
- Create custom API endpoints
- Add business logic via hooks that execute before/after specific operations
- Schedule recurring tasks using cron syntax
- Access and utilize core BAASIX services
Extension Types
BAASIX supports three main types of extensions:
- Endpoint Extensions: Create custom API routes and endpoints
- Hook Extensions: Execute custom code when specific events occur
- Schedule Extensions: Run tasks on a recurring schedule
Extension Structure
Extensions follow a consistent directory structure:
extensions/
baasix-endpoint-{name}/ # For custom endpoints
index.js # Main entry point
package.json (optional) # For dependencies
baasix-hook-{name}/ # For custom hooks
index.js # Main entry point
package.json (optional) # For dependencies
baasix-schedule-{name}/ # For scheduled tasks
index.js # Main entry point
package.json (optional) # For dependenciesEach extension must have a unique name to avoid conflicts.
Creating Custom Endpoints
Custom endpoints allow you to add new API routes to the BAASIX API.
Basic Structure
// extensions/baasix-endpoint-my-custom-route/index.js
export default {
id: 'my-custom-route',
name: 'My Custom Route',
version: '1.0.0',
description: 'Adds a custom API route',
register: ({ app, services, database, env }) => {
// Define your routes here
app.get('/custom-endpoint', async (req, res) => {
try {
// Your custom logic here
const data = { message: 'This is a custom endpoint' };
// Send response
res.json({ data });
} catch (error) {
res.status(500).json({
error: { message: error.message }
});
}
});
}
};Authentication and Permissions
Custom endpoints can utilize BAASIX's authentication and permission systems:
export default {
// ... other properties
register: ({ app, services, database, env }) => {
const { authenticate, authorize } = services.auth;
app.get('/custom-endpoint',
authenticate, // Require authentication
authorize(['admin']), // Require admin role
async (req, res) => {
// Access authenticated user
const userId = req.accountability.user.id;
// Your custom logic here
res.json({ data: result });
}
);
}
};Using Services
Access BAASIX core services in your custom endpoints:
export default {
// ... other properties
register: ({ app, services, database, env }) => {
const { ItemsService } = services;
app.get('/custom-report', authenticate, async (req, res) => {
try {
const accountability = req.accountability;
const itemsService = new ItemsService('orders', { accountability });
// Get orders
const orders = await itemsService.readMany({
filter: { status: 'completed' },
sort: { createdAt: 'desc' },
limit: 100
});
// Process data
const report = processOrders(orders);
res.json({ data: report });
} catch (error) {
res.status(500).json({
error: { message: error.message }
});
}
});
}
};Creating Hook Extensions
Hook extensions allow you to execute custom code before or after specific operations in BAASIX.
Basic Structure
// extensions/baasix-hook-user-activity/index.js
export default {
id: 'user-activity-log',
name: 'User Activity Log',
version: '1.0.0',
description: 'Logs user activities',
events: [
{
collection: 'baasix_User', // Target collection
action: 'items.update.after', // Event to listen for
handler: async (data, context) => {
const { services, database, accountability } = context;
// Log the activity
await database.models.activity_log.create({
user_id: accountability.user?.id || null,
action: 'user.update',
details: {
user_id: data.id,
changes: data._previousDataValues
}
});
return data; // Return data to continue the flow
}
}
]
};Multiple Event Handlers
You can define multiple event handlers in a single hook extension:
export default {
id: 'data-validation',
name: 'Data Validation',
description: 'Validates data before saving',
events: [
{
collection: 'products',
action: 'items.create.before',
handler: async (data, context) => {
// Validate product data
validateProductData(data);
return data;
}
},
{
collection: 'orders',
action: 'items.create.before',
handler: async (data, context) => {
// Validate order data
validateOrderData(data);
return data;
}
}
]
};Creating Schedule Extensions
Schedule extensions allow you to run tasks on a recurring schedule using cron syntax.
Basic Structure
// extensions/baasix-schedule-daily-cleanup/index.js
export default {
id: 'daily-cleanup',
name: 'Daily Cleanup Task',
version: '1.0.0',
description: 'Clean up temporary files and old logs daily',
schedules: [
{
id: 'cleanup-files',
schedule: '0 0 * * *', // Run at midnight every day
handler: async (context) => {
const { database, services } = context;
const { Op } = database.Sequelize;
// Delete temporary files older than 7 days
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 7);
await database.models.baasix_Files.destroy({
where: {
temporary: true,
createdAt: { [Op.lt]: cutoffDate }
}
});
// Log the cleanup
console.log('Temporary files cleanup completed');
}
}
]
};Multiple Schedules
You can define multiple scheduled tasks in a single extension:
export default {
id: 'maintenance-tasks',
name: 'Maintenance Tasks',
schedules: [
{
id: 'daily-cleanup',
schedule: '0 0 * * *', // Run at midnight every day
handler: async (context) => {
// Daily cleanup logic
}
},
{
id: 'weekly-report',
schedule: '0 9 * * MON', // Run at 9 AM every Monday
handler: async (context) => {
// Generate weekly report
}
}
]
};Accessing Core Services
Extension handlers receive a context object that provides access to core BAASIX services:
Available Services
services.items: ItemsService for CRUD operationsservices.auth: Authentication serviceservices.mail: Email sending serviceservices.storage: File storage serviceservices.permissions: Permission management serviceservices.hooks: Hooks management serviceservices.socket: Socket.IO service (if enabled)services.tasks: TasksService for background task management
TasksService Integration
The TasksService helps coordinate background tasks across extensions:
// Import TasksService directly in extensions
import tasksService from "../../baasix/services/TasksService";
// Example: Schedule extension with task coordination
export default {
id: 'task-processor',
name: 'Background Task Processor',
schedules: [
{
id: 'process-tasks',
schedule: '*/5 * * * *', // Every 5 minutes
handler: async (context) => {
// Check if a task is already running
if (await tasksService.isTaskRunning()) {
console.log("Task already running, skipping...");
return;
}
// Mark task as running
await tasksService.setTaskRunning(true);
try {
// Get cached "Not started" tasks
const tasks = await tasksService.getNotStartedTasks();
if (tasks.length === 0) {
console.log("No tasks to process");
return;
}
// Process first available task
const task = tasks[0];
await processBackgroundTask(task, context); // Your custom task processing logic
} catch (error) {
console.error("Task processing failed:", error);
} finally {
// Always reset running state
await tasksService.setTaskRunning(false);
}
}
}
]
};For complete TasksService documentation, see TasksService Documentation.
Database Access
Access the Sequelize ORM instance:
const { database } = context;
const { models, sequelize, Sequelize } = database;
// Use models directly
const users = await models.baasix_User.findAll({
where: { status: 'active' }
});
// Use transactions
await sequelize.transaction(async (transaction) => {
// Perform database operations in a transaction
});
// Use Sequelize operators
const { Op } = Sequelize;
const recentUsers = await models.baasix_User.findAll({
where: {
createdAt: { [Op.gt]: new Date(Date.now() - 7 * 24 * 60 * 60 * 1000) }
}
});Extension Configuration
Extensions can be configured through environment variables:
export default {
id: 'email-reports',
name: 'Email Reports',
register: ({ app, services, env }) => {
// Access environment variables
const reportRecipients = env.REPORT_RECIPIENTS?.split(',') || [];
const reportFrequency = env.REPORT_FREQUENCY || 'daily';
// Use configuration in your extension
}
};Examples
Custom Report Endpoint
// extensions/baasix-endpoint-sales-report/index.js
export default {
id: 'sales-report',
name: 'Sales Report Endpoint',
register: ({ app, services, database }) => {
const { authenticate } = services.auth;
const { ItemsService } = services;
app.get('/sales-report', authenticate, async (req, res) => {
try {
const { startDate, endDate } = req.query;
// Validate dates
if (!startDate || !endDate) {
return res.status(400).json({
error: { message: 'startDate and endDate are required' }
});
}
// Get sales data
const itemsService = new ItemsService('orders', {
accountability: req.accountability
});
const orders = await itemsService.readMany({
filter: {
createdAt: { between: [startDate, endDate] },
status: 'completed'
}
});
// Generate report
const report = {
totalOrders: orders.length,
totalRevenue: orders.reduce((sum, order) => sum + order.total, 0),
averageOrderValue: orders.length > 0
? orders.reduce((sum, order) => sum + order.total, 0) / orders.length
: 0,
ordersByDate: groupOrdersByDate(orders)
};
res.json({ data: report });
} catch (error) {
res.status(500).json({
error: { message: error.message }
});
}
});
function groupOrdersByDate(orders) {
// Implementation details
}
}
};Automated Data Enrichment Hook
// extensions/baasix-hook-product-enrichment/index.js
export default {
id: 'product-enrichment',
name: 'Product Data Enrichment',
events: [
{
collection: 'products',
action: 'items.create.before',
handler: async (data, context) => {
// Add metadata based on product name
if (!data.metadata) data.metadata = {};
// Extract category from name
const categories = {
'phone': 'Electronics',
'laptop': 'Electronics',
'shirt': 'Clothing',
'shoes': 'Footwear'
};
for (const [keyword, category] of Object.entries(categories)) {
if (data.name.toLowerCase().includes(keyword)) {
data.metadata.suggestedCategory = category;
break;
}
}
// Set default values
data.inStock = data.inStock ?? true;
data.popularity = data.popularity ?? 0;
return data;
}
}
]
};Monthly Database Backup Schedule
// extensions/baasix-schedule-db-backup/index.js
export default {
id: 'db-backup',
name: 'Database Backup',
schedules: [
{
id: 'monthly-backup',
schedule: '0 0 1 * *', // First day of each month at midnight
handler: async (context) => {
const { database, env, services } = context;
const { mail } = services;
try {
// Backup logic - depends on your database
const backupFileName = `backup-${new Date().toISOString().split('T')[0]}.sql`;
// This is just an example - implementation depends on your DB setup
await executeBackupCommand(database.sequelize, backupFileName);
// Upload backup to storage
const storageService = new services.StorageService();
await storageService.upload(
backupFileName,
'database-backups',
{ isPrivate: true }
);
// Send notification email
await mail.sendMail({
to: env.ADMIN_EMAIL,
subject: 'Monthly Database Backup Completed',
templateName: 'backup-notification',
context: {
date: new Date().toLocaleDateString(),
backupName: backupFileName,
size: '10MB' // Replace with actual size
}
});
console.log(`Backup completed: ${backupFileName}`);
} catch (error) {
console.error('Backup failed:', error);
// Send failure notification
await mail.sendMail({
to: env.ADMIN_EMAIL,
subject: 'Monthly Database Backup Failed',
templateName: 'backup-failure',
context: {
date: new Date().toLocaleDateString(),
error: error.message
}
});
}
}
}
]
};
async function executeBackupCommand(sequelize, filename) {
// Implementation depends on your database and system
// Example for PostgreSQL:
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const { database, host, username, password } = sequelize.config;
await execPromise(
`PGPASSWORD=${password} pg_dump -U ${username} -h ${host} ${database} > ${filename}`
);
}Best Practices
-
Unique IDs: Ensure each extension has a unique ID to avoid conflicts.
-
Error Handling: Implement proper error handling in all extensions to prevent disruptions.
-
Graceful Degradation: Design extensions to fail gracefully and not crash the entire API.
-
Performance: Be mindful of performance implications, especially for hooks that run frequently.
-
Service Usage: Use the provided services rather than implementing duplicate functionality.
-
Versioning: Include version information in your extensions for better management.
-
Documentation: Document your extensions with clear descriptions of functionality and configuration options.
-
Isolation: Keep extensions focused on specific functionality rather than creating monolithic extensions.
-
Security: Be careful with permissions in custom endpoints; always use authentication and authorization where appropriate.
-
Testing: Create tests for your extensions to ensure they work correctly.
Related Documentation
- TasksService Documentation - Complete TasksService guide
- Hooks System - Details about the hooks system
- API Routes Reference - Complete list of all API endpoints
- Additional Features - Email templates, storage services, and more