API-driven framework for building realtime apps, using MVC conventions (based on Express and Socket.io)
—
Sails.js inherits from Node.js EventEmitter and provides a comprehensive event system for managing application lifecycle, routing events, and custom functionality. The event system enables loose coupling between components and supports both built-in framework events and custom application events.
Sails applications inherit from Node.js EventEmitter, providing full event functionality:
// Sails extends EventEmitter
class Sails extends EventEmitter { ... }
// Event listener methods
sails.on(event: String, listener: Function): Sails
sails.once(event: String, listener: Function): Sails
sails.off(event: String, listener?: Function): Sails
sails.emit(event: String, ...args): Boolean
sails.removeAllListeners(event?: String): Sails
sails.listenerCount(event: String): Number
sails.listeners(event: String): Function[]Basic Event Usage:
const sails = require('sails');
// Listen for events
sails.on('lifted', () => {
console.log('Sails app is ready!');
});
// Listen once
sails.once('ready', () => {
console.log('App components loaded');
});
// Remove listener
const handler = () => console.log('Event fired');
sails.on('custom-event', handler);
sails.off('custom-event', handler);
// Emit custom events
sails.emit('user-registered', { userId: 123, email: 'user@example.com' });// Application successfully lifted
sails.on('lifted', callback: Function): void
// Application shutdown initiated
sails.on('lower', callback: Function): void
// Application components loaded (internal)
sails.on('ready', callback: Function): voidLifecycle Event Examples:
// Application startup complete
sails.on('lifted', () => {
console.log(`Sails app lifted on port ${sails.config.port}`);
console.log(`Environment: ${sails.config.environment}`);
// Start background jobs
startBackgroundTasks();
// Register with service discovery
registerWithLoadBalancer();
});
// Application shutdown initiated
sails.on('lower', () => {
console.log('Sails app is shutting down...');
// Cleanup background jobs
stopBackgroundTasks();
// Close external connections
closeExternalConnections();
});
// Internal ready state (before HTTP server starts)
sails.on('ready', () => {
console.log('App components loaded, starting HTTP server...');
// Perform pre-startup tasks
validateConfiguration();
initializeServices();
});The application startup follows a specific event sequence:
// 1. Configuration loaded
sails.on('hook:userconfig:loaded', () => {
console.log('User configuration loaded');
});
// 2. Hooks initialized
sails.on('hook:*:loaded', (hookName) => {
console.log(`Hook loaded: ${hookName}`);
});
// 3. Components ready
sails.on('ready', () => {
console.log('All components ready');
});
// 4. Application lifted
sails.on('lifted', () => {
console.log('Application fully operational');
});// Before static routes are bound
sails.on('router:before', callback: Function): void
// After static routes are bound
sails.on('router:after', callback: Function): void
// Router reset event
sails.on('router:reset', callback: Function): voidRouter Event Examples:
// Before route binding - modify routes
sails.on('router:before', () => {
console.log('Binding routes...');
// Add dynamic routes
sails.get('/health', (req, res) => {
return res.json({ status: 'ok', timestamp: Date.now() });
});
// Modify existing routes
modifyApiRoutes();
});
// After route binding - log route information
sails.on('router:after', () => {
console.log('Routes bound successfully');
const routes = sails.router.getSortedRouteAddresses();
console.log(`Total routes: ${routes.length}`);
// Log all routes in development
if (sails.config.environment === 'development') {
routes.forEach(route => console.log(` ${route}`));
}
});
// Router reset - cleanup custom routes
sails.on('router:reset', () => {
console.log('Router reset, clearing custom routes');
clearCustomRoutes();
});// Virtual request routing
sails.on('router:request', (req, res): void
// Server error handling
sails.on('router:request:500', (req, res, err): void
// Not found handling
sails.on('router:request:404', (req, res): voidRequest Event Examples:
// Log all virtual requests
sails.on('router:request', (req, res) => {
console.log(`Virtual request: ${req.method} ${req.url}`);
});
// Custom 500 error handling
sails.on('router:request:500', (req, res, err) => {
console.error('Server error:', err);
// Log error details
console.error(`URL: ${req.url}`);
console.error(`Method: ${req.method}`);
console.error(`User: ${req.session?.userId || 'anonymous'}`);
// Send custom error response
if (!res.headersSent) {
return res.status(500).json({
error: 'Internal server error',
requestId: req.id,
timestamp: Date.now()
});
}
});
// Custom 404 handling
sails.on('router:request:404', (req, res) => {
console.log(`404 Not Found: ${req.method} ${req.url}`);
// Track 404s for analytics
trackNotFoundRequest(req);
// Send custom 404 response
if (!res.headersSent) {
return res.status(404).json({
error: 'Resource not found',
path: req.url,
suggestion: 'Check the API documentation'
});
}
});Hooks can define and emit custom events during their lifecycle:
// Hook-specific events
sails.on('hook:${hookName}:loaded', callback: Function): void
sails.on('hook:${hookName}:ready', callback: Function): voidHook Event Examples:
// Listen for specific hook events
sails.on('hook:orm:loaded', () => {
console.log('ORM hook loaded, models available');
// Perform database initialization
initializeDatabase();
});
sails.on('hook:sockets:loaded', () => {
console.log('Sockets hook loaded, WebSocket support available');
// Configure real-time features
setupRealtimeFeatures();
});
// Listen for all hook load events
sails.on('hook:*:loaded', (hookName) => {
console.log(`Hook loaded: ${hookName}`);
// Track loading progress
trackHookLoadingProgress(hookName);
});Custom hooks can emit their own events:
// api/hooks/my-hook/index.js
module.exports = function(sails) {
return {
initialize: function(cb) {
// Emit custom hook event
sails.emit('hook:my-hook:initialized', {
message: 'My hook is ready',
features: ['feature1', 'feature2']
});
// Set up periodic events
setInterval(() => {
sails.emit('hook:my-hook:heartbeat', { timestamp: Date.now() });
}, 30000);
return cb();
}
};
};
// Listen for custom hook events
sails.on('hook:my-hook:initialized', (data) => {
console.log('Custom hook initialized:', data.message);
console.log('Available features:', data.features);
});
sails.on('hook:my-hook:heartbeat', (data) => {
console.log('Hook heartbeat:', new Date(data.timestamp));
});Applications can define and emit custom events for business logic:
// User registration event
sails.on('user:registered', (userData) => {
console.log('New user registered:', userData.email);
// Send welcome email
EmailService.sendWelcomeEmail(userData.email, userData.name);
// Create user profile
UserProfileService.createProfile(userData.id);
// Track registration
AnalyticsService.track('user_registered', {
userId: userData.id,
source: userData.registrationSource
});
});
// User login event
sails.on('user:login', (loginData) => {
console.log('User logged in:', loginData.userId);
// Update last login timestamp
User.updateOne(loginData.userId).set({ lastLoginAt: Date.now() });
// Log security events
SecurityService.logLogin(loginData);
});
// Emit user events
const newUser = await User.create({
email: 'user@example.com',
name: 'John Doe'
}).fetch();
sails.emit('user:registered', {
id: newUser.id,
email: newUser.email,
name: newUser.name,
registrationSource: 'web'
});// Order events
sails.on('order:created', async (orderData) => {
console.log('New order created:', orderData.id);
// Send confirmation email
await EmailService.sendOrderConfirmation(orderData);
// Update inventory
await InventoryService.reserveItems(orderData.items);
// Process payment
await PaymentService.processPayment(orderData.paymentInfo);
});
sails.on('order:shipped', (orderData) => {
console.log('Order shipped:', orderData.id);
// Send shipping notification
NotificationService.sendShippingNotification(orderData);
// Update tracking
TrackingService.createTrackingRecord(orderData);
});
// Payment events
sails.on('payment:successful', (paymentData) => {
console.log('Payment successful:', paymentData.transactionId);
// Fulfill order
OrderService.fulfillOrder(paymentData.orderId);
// Send receipt
EmailService.sendReceipt(paymentData);
});
sails.on('payment:failed', (paymentData) => {
console.error('Payment failed:', paymentData.error);
// Cancel order
OrderService.cancelOrder(paymentData.orderId);
// Notify customer
NotificationService.sendPaymentFailureNotification(paymentData);
});// Collect multiple related events
const EventAggregator = {
events: [],
collect(eventName, eventData) {
this.events.push({ name: eventName, data: eventData, timestamp: Date.now() });
},
flush() {
const events = this.events.slice();
this.events = [];
return events;
}
};
// Listen to multiple events
['user:registered', 'user:login', 'user:logout'].forEach(eventName => {
sails.on(eventName, (data) => {
EventAggregator.collect(eventName, data);
});
});
// Periodic processing
setInterval(() => {
const events = EventAggregator.flush();
if (events.length > 0) {
AnalyticsService.batchProcess(events);
}
}, 60000); // Every minute// Event middleware pattern
const EventMiddleware = {
use(eventName, middleware) {
const originalEmit = sails.emit;
sails.emit = function(event, ...args) {
if (event === eventName) {
// Run middleware before emitting
middleware(event, args, () => {
originalEmit.call(this, event, ...args);
});
} else {
originalEmit.call(this, event, ...args);
}
};
}
};
// Use middleware
EventMiddleware.use('user:registered', (event, args, next) => {
console.log('Middleware: Processing user registration');
// Validate event data
if (!args[0].email) {
console.error('Invalid user registration data');
return; // Don't proceed
}
// Add metadata
args[0].processedAt = Date.now();
next();
});// Simple event sourcing implementation
const EventStore = {
events: [],
append(event) {
this.events.push({
...event,
id: require('uuid').v4(),
timestamp: Date.now()
});
},
getEvents(aggregateId) {
return this.events.filter(e => e.aggregateId === aggregateId);
},
replay(aggregateId) {
const events = this.getEvents(aggregateId);
return events.reduce((state, event) => {
return applyEvent(state, event);
}, {});
}
};
// Store all user events
sails.on('user:*', (eventData) => {
EventStore.append({
type: 'user_event',
aggregateId: eventData.userId,
data: eventData
});
});// Handle event errors
sails.on('error', (err) => {
console.error('Unhandled event error:', err);
// Log to external service
ErrorReportingService.reportError(err);
});
// Safe event emission
function safeEmit(eventName, eventData) {
try {
sails.emit(eventName, eventData);
} catch (err) {
console.error(`Error emitting event ${eventName}:`, err);
sails.emit('event:error', { eventName, eventData, error: err });
}
}
// Error recovery
sails.on('event:error', ({ eventName, eventData, error }) => {
console.log(`Attempting to recover from event error: ${eventName}`);
// Retry logic
setTimeout(() => {
safeEmit(eventName, eventData);
}, 5000);
});// Event performance monitoring
const EventMetrics = {
counts: new Map(),
timings: new Map(),
track(eventName) {
this.counts.set(eventName, (this.counts.get(eventName) || 0) + 1);
},
time(eventName, duration) {
if (!this.timings.has(eventName)) {
this.timings.set(eventName, []);
}
this.timings.get(eventName).push(duration);
},
getStats() {
const stats = {};
for (const [event, count] of this.counts) {
stats[event] = { count };
if (this.timings.has(event)) {
const times = this.timings.get(event);
stats[event].avgTime = times.reduce((a, b) => a + b, 0) / times.length;
}
}
return stats;
}
};
// Monitor all events
const originalEmit = sails.emit;
sails.emit = function(event, ...args) {
const start = Date.now();
EventMetrics.track(event);
const result = originalEmit.call(this, event, ...args);
EventMetrics.time(event, Date.now() - start);
return result;
};
// Log metrics periodically
setInterval(() => {
console.log('Event Statistics:', EventMetrics.getStats());
}, 300000); // Every 5 minutesThe Sails.js event system provides a robust foundation for building event-driven applications with comprehensive lifecycle management, routing events, and custom business logic events, supporting modern architectural patterns and real-time functionality.
Install with Tessl CLI
npx tessl i tessl/npm-sails