Extensions System
This guide explains how to create and use extensions for BAASIX to extend its functionality.
Quick Start with CLI
The easiest way to create extensions is using the Baasix CLI:
# Create a hook extension
npx baasix extension --type hook --name my-hook
# Create an endpoint extension
npx baasix extension --type endpoint --name my-endpoint
# Create a schedule extension
npx baasix extension --type schedule --name my-scheduleThe CLI generates boilerplate code with best practices and access to all Baasix services. See the CLI Guide for more details.
Sample Repository
For complete working examples of extensions, check out our sample repository:
👉 github.com/baasix/baasix/samples/sample
The sample includes ready-to-use hook and endpoint extension examples.
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
import { ItemsService } from '@baasix/baasix';
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) => {
// Delete temporary files older than 7 days
const cutoffDate = new Date();
cutoffDate.setDate(cutoffDate.getDate() - 7);
const filesService = new ItemsService('baasix_File', {
accountability: { bypassPermissions: true },
});
await filesService.deleteByQuery({
temporary: { eq: true },
createdAt: { lt: cutoffDate.toISOString() },
});
// 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 Drizzle ORM database instance:
import { getDatabase, getSqlClient } from '@baasix/baasix';
import { eq, gt, and } from 'drizzle-orm';
const { db } = context;
// Use ItemsService for CRUD operations (recommended)
import { ItemsService } from '@baasix/baasix';
const usersService = new ItemsService('baasix_User', {
accountability: { bypassPermissions: true },
});
const users = await usersService.readByQuery({
filter: { status: { eq: 'active' } },
});
// Use transactions
await db.transaction(async (tx) => {
// Perform database operations within a transaction
// Pass tx to services or use directly
});
// Use raw SQL with getSqlClient for complex queries
const sql = getSqlClient();
const recentUsers = await sql`
SELECT * FROM "baasix_User"
WHERE "createdAt" > NOW() - INTERVAL '7 days'
`;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
import { env, MailService, StorageService } from '@baasix/baasix';
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) => {
try {
// Backup logic - depends on your database
const backupFileName = `backup-${new Date().toISOString().split('T')[0]}.sql`;
// Execute pg_dump using environment variables
await executeBackupCommand(backupFileName);
// Upload backup to storage
await StorageService.saveFile(
'local',
`database-backups/${backupFileName}`,
await require('fs').promises.readFile(backupFileName),
);
// Send notification email
await MailService.sendMail({
to: env.get('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 MailService.sendMail({
to: env.get('ADMIN_EMAIL'),
subject: 'Monthly Database Backup Failed',
templateName: 'backup-failure',
context: {
date: new Date().toLocaleDateString(),
error: error.message,
},
});
}
},
},
],
};
async function executeBackupCommand(filename) {
// Implementation depends on your database and system
// Example for PostgreSQL using environment variables:
const { exec } = require('child_process');
const util = require('util');
const execPromise = util.promisify(exec);
const dbUrl = process.env.DATABASE_URL;
await execPromise(`pg_dump "${dbUrl}" > ${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