Feathers services automatically emit events when resources are created, updated, patched, or removed. This allows you to build real-time applications by listening to these events through various transports.
Default Events
Services automatically emit the following events:
- created - Emitted after a resource is created
- updated - Emitted after a resource is updated
- patched - Emitted after a resource is patched
- removed - Emitted after a resource is removed
These events are emitted on both the service and the application.
Event Listeners
Services extend Node’s EventEmitter, so you can use all standard EventEmitter methods:
on()
Listen for an event:
service.on(event: string, listener: (data: any, context?: HookContext) => void)
The event name (e.g., 'created', 'patched')
Callback function that receives the event data and optional hook context
const userService = app.service('users')
userService.on('created', (user, context) => {
console.log('New user created:', user)
console.log('Created by:', context.params.user)
})
once()
Listen for an event once:
service.once(event: string, listener: (data: any, context?: HookContext) => void)
userService.once('created', (user) => {
console.log('First user created:', user)
})
removeListener()
Remove a specific event listener:
service.removeListener(event: string, listener: Function)
const handleCreated = (user) => {
console.log('User created:', user)
}
userService.on('created', handleCreated)
userService.removeListener('created', handleCreated)
removeAllListeners()
Remove all listeners for an event or all events:
service.removeAllListeners(event?: string)
// Remove all 'created' listeners
userService.removeAllListeners('created')
// Remove all listeners
userService.removeAllListeners()
Event Emission
Events are automatically emitted after successful service method calls unless:
- The service’s
events option includes the event name (indicating the service handles emission itself)
- The
context.event property is set to null in a hook
Automatic Event Emission
// This will emit a 'created' event
const user = await app.service('users').create({
name: 'John Doe',
email: 'john@example.com'
})
// Listen for the event
app.service('users').on('created', (user) => {
console.log('User created:', user)
})
Controlling Event Emission
You can control event emission in hooks:
// Prevent event emission
const skipEvent = async (context) => {
context.event = null
}
// Change the event name
const customEvent = async (context) => {
context.event = 'user-registered'
}
service.hooks({
after: {
create: [customEvent]
}
})
service.on('user-registered', (user) => {
console.log('Custom event:', user)
})
Custom Events
You can define custom events when registering a service:
app.use('/messages', messageService, {
events: ['typing', 'delivered', 'read']
})
// Emit custom events
app.service('messages').emit('typing', {
userId: 123,
messageId: 456
})
// Listen for custom events
app.service('messages').on('typing', (data) => {
console.log('User typing:', data)
})
List of custom event names. These are merged with the default events (created, updated, patched, removed).
Service Events Configuration
You can completely replace the default events:
app.use('/notifications', notificationService, {
serviceEvents: ['sent', 'failed'] // Only these events, no defaults
})
Complete list of events for this service. Unlike events, this replaces the default events entirely.
Event Data
Events receive two arguments:
- data - The resource that was created, updated, patched, or removed
- context - The hook context (optional)
service.on('created', (user, context) => {
console.log('Created user:', user)
console.log('Service path:', context.path)
console.log('Method:', context.method)
console.log('Params:', context.params)
})
Multiple Results
When a method returns multiple results (e.g., create with an array), the event is emitted once for each result:
const users = await app.service('users').create([
{ name: 'John' },
{ name: 'Jane' }
])
// 'created' event is emitted twice, once for each user
app.service('users').on('created', (user) => {
console.log('User created:', user.name)
})
// Output:
// User created: John
// User created: Jane
Application-Level Events
The application itself is an EventEmitter and can be used for custom application events:
app.on('login', (user) => {
console.log('User logged in:', user)
})
app.emit('login', { id: 1, name: 'John' })
Real-Time Transports
Events are the foundation for real-time updates in Feathers. When using transports like Socket.io or Primus, service events are automatically sent to connected clients.
Server-Side
import { feathers } from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio'
const app = feathers()
app.configure(socketio())
app.use('/messages', {
async create(data) {
return { id: 1, ...data }
}
})
Client-Side
import io from 'socket.io-client'
import { feathers } from '@feathersjs/feathers'
import socketio from '@feathersjs/socketio-client'
const socket = io('http://localhost:3030')
const app = feathers()
app.configure(socketio(socket))
const messageService = app.service('messages')
// Listen for real-time events
messageService.on('created', (message) => {
console.log('New message:', message)
})
// Create a message (will trigger 'created' event for all connected clients)
await messageService.create({ text: 'Hello World' })
Event Filtering
For real-time applications, you often want to filter which clients receive which events. This is done through channels (see Publishing & Channels documentation).
app.on('connection', (connection) => {
app.channel('authenticated').join(connection)
})
app.publish('created', (data, context) => {
return app.channel('authenticated')
})
Event Hook
Feathers uses an internal eventHook to handle event emission:
export function eventHook(context: HookContext, next: NextFunction) {
const { events } = getServiceOptions(context.self)
const defaultEvent = defaultEventMap[context.method] || null
context.event = defaultEvent
return next().then(() => {
if (typeof context.event === 'string' && !events.includes(context.event)) {
const results = Array.isArray(context.result)
? context.result
: [context.result]
results.forEach((element) =>
context.self.emit(context.event, element, context)
)
}
})
}
Examples
Audit Logging
const auditLog = []
app.service('users').on('created', (user, context) => {
auditLog.push({
action: 'created',
resource: 'user',
resourceId: user.id,
userId: context.params.user?.id,
timestamp: new Date()
})
})
Send Notifications
app.service('tasks').on('created', async (task) => {
if (task.assignedTo) {
await app.service('notifications').create({
userId: task.assignedTo,
message: `New task assigned: ${task.title}`
})
}
})
Update Caches
const cache = new Map()
app.service('products').on('patched', (product) => {
cache.set(product.id, product)
})
app.service('products').on('removed', (product) => {
cache.delete(product.id)
})
Webhook Integration
app.service('orders').on('created', async (order) => {
await fetch('https://external-api.com/webhook', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
event: 'order.created',
data: order
})
})
})
Type Definitions
interface ServiceAddons<A = Application, S = Service> extends EventEmitter {
id?: string
hooks(options: HookOptions<A, S>): this
}
type FeathersService<A = FeathersApplication, S = Service> =
S & ServiceAddons<A, S>
// Default event mapping
const defaultEventMap = {
create: 'created',
update: 'updated',
patch: 'patched',
remove: 'removed'
}
// Default events
const defaultServiceEvents = ['created', 'updated', 'patched', 'removed']