Hooks / Middleware
Lifecycle hooks let you run custom logic at various points in the document lifecycle — perfect for validation, auditing, logging, or data transformation.
Available Hooks
| Hook | Arguments | Triggered |
|---|---|---|
before:insert | (doc) | Before a document is inserted |
after:insert | (doc) | After a document is inserted |
before:update | (filter, changes) | Before documents are updated |
after:update | (filter, changes, count) | After documents are updated |
before:delete | (filter) | Before documents are deleted |
after:delete | (filter, count) | After documents are deleted |
Registering Hooks
const users = db.collection('users', userSchema)
users.hook('before:insert', async (doc) => {
console.log(`Inserting user: ${doc.name}`)
doc.createdAt = new Date() // override the timestamp
})
users.hook('after:insert', async (doc) => {
console.log(`User created: ${doc.id}`)
await auditLog.insert({ action: 'user.created', userId: doc.id })
})
Practical Examples
Password Hashing
import { hash } from 'bcrypt'
users.hook('before:insert', async (doc) => {
if (doc.password) {
doc.password = await hash(doc.password, 10)
}
})
users.hook('before:update', async (filter, changes) => {
if (changes.password) {
changes.password = await hash(changes.password, 10)
}
})
Timestamp Management
users.hook('before:insert', async (doc) => {
doc.createdAt = new Date()
doc.updatedAt = new Date()
})
users.hook('before:update', async (filter, changes) => {
changes.updatedAt = new Date()
})
Soft Delete
users.hook('before:delete', async (filter) => {
// Instead of hard-deleting, mark as deleted
await users.update(filter, { deletedAt: new Date(), active: false })
// Prevent the actual delete
throw new Error('Soft delete only — use update instead')
})
Validation
users.hook('before:insert', async (doc) => {
if (!doc.email?.includes('@')) {
throw new Error('Invalid email address')
}
})
Audit Logging
users.hook('after:insert', async (doc) => {
await auditLog.insert({
action: 'INSERT',
collection: 'users',
documentId: doc.id,
timestamp: new Date(),
})
})
users.hook('after:update', async (filter, changes, count) => {
await auditLog.insert({
action: 'UPDATE',
collection: 'users',
filter: JSON.stringify(filter),
changes: JSON.stringify(changes),
count,
timestamp: new Date(),
})
})
Multiple Hooks
You can register multiple handlers for the same hook:
users
.hook('before:insert', hashPassword)
.hook('before:insert', setTimestamps)
.hook('before:insert', validateEmail)
They run in the order they were registered.
Hook Execution Order
before:insert → [validation] → [insert] → after:insert
before:update → [validation] → [update] → after:update
before:delete → [delete] → after:delete
If a before:* hook throws, the operation is cancelled.