Skip to main content

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

HookArgumentsTriggered
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.