@actinode/express-activitylog
v0.2.5
Published
Express middleware for activity logging — high quality, well tested, and built for Node.js
Downloads
814
Maintainers
Readme
@actinode/express-activitylog
Express middleware for activity logging — high quality, well tested, and built for Node.js.
📚 Full documentation at docs.actinode.com
Installation
pnpm add @actinode/express-activitylogPick your database adapter:
For detailed installation guide visit docs.actinode.com
# Prisma
pnpm add @prisma/client
# Mongoose
pnpm add mongooseQuick Start
Auto-log every request
import express from 'express'
import { activityLog, prismaAdapter } from '@actinode/express-activitylog'
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
const app = express()
app.use(activityLog({
adapter: prismaAdapter(prisma),
getUserId: (req) => req.user?.id,
}))Manual fluent logging
import { activity, prismaAdapter } from '@actinode/express-activitylog'
const adapter = prismaAdapter(prisma)
await activity(adapter)
.by(user) // who performed the action
.on(post) // what it was performed on
.withProperties({ ip: req.ip })
.log('created')For full API reference and more examples visit docs.actinode.com
Adapters
Prisma
Add the ActivityLog model to your schema.prisma:
model ActivityLog {
id Int @id @default(autoincrement())
description String
causerId String?
causerType String?
subjectId String?
subjectType String?
properties Json @default("{}")
createdAt DateTime @default(now())
}Then run prisma migrate dev.
import { prismaAdapter } from '@actinode/express-activitylog'
const adapter = prismaAdapter(prisma)Mongoose
import mongoose from 'mongoose'
import { mongooseAdapter } from '@actinode/express-activitylog'
const adapter = mongooseAdapter(mongoose)The adapter creates an ActivityLog collection automatically with this shape:
| Field | Type | Description | |-------------|--------|------------------------------| | description | String | What happened | | causerId | String | ID of the actor | | causerType | String | Class/type name of the actor | | subjectId | String | ID of the target | | subjectType | String | Class/type name of the target| | properties | Object | Extra metadata | | createdAt | Date | Timestamp |
Masking sensitive data
Sensitive fields are automatically masked before they reach your database. Masking is applied to req.body in the middleware and to .withProperties() in the fluent builder.
Default masked fields
The following fields are always masked out of the box, even without any configuration:
password · passwordConfirmation · token · accessToken · refreshToken · secret · apiKey · creditCard · cardNumber · cvv · ssn
Middleware mask config
activityLog({
adapter: prismaAdapter(prisma),
mask: {
denyList: ['password', 'token'], // denyList: mask these fields
allowList: ['name', 'email', 'role'], // allowList: mask everything else
replacement: '***', // default: '***masked***'
deep: true, // recurse into nested objects (default: true)
models: {
User: {
denyList: ['password', 'ssn'],
allowList: ['name', 'email'],
},
Payment: {
denyList: ['cardNumber', 'cvv'],
},
},
},
})denyList and allowList can be combined — the denyList is applied first, then the allowList.
Fluent .mask()
Mask specific fields on a single manual log entry:
await activity(adapter)
.by(user)
.on(post)
.withProperties({ title: 'Hello', secret: 'hidden' })
.mask(['secret'])
.log('created')
// saved properties: { title: 'Hello', secret: '***masked***' }Custom replacement string
activityLog({
adapter,
mask: { replacement: '[REDACTED]' },
})Per-model masking
Different models can have different masking rules. The model key matches the causer's or subject's class name:
activityLog({
adapter,
mask: {
models: {
User: {
denyList: ['password', 'ssn'],
allowList: ['name', 'email'],
},
Payment: {
denyList: ['cardNumber', 'cvv'],
},
},
},
})Custom masking logic
You can call maskProperties directly for any use case outside the middleware:
import { maskProperties, DEFAULT_MASKED_FIELDS } from '@actinode/express-activitylog'
const safe = maskProperties(req.body, {
denyList: [...DEFAULT_MASKED_FIELDS, 'myCustomField'],
replacement: '[hidden]',
deep: true,
})API Reference
activityLog(options)
Express middleware factory.
| Option | Type | Required | Description |
|-----------------|-------------------------------|----------|------------------------------------------------|
| adapter | ActivityAdapter | Yes | Database adapter |
| getUserId | (req: Request) => string \| undefined | No | Extract user ID from request |
| getDescription | (req: Request) => string | No | Defaults to "METHOD /path" |
| skip | (req: Request) => boolean | No | Return true to skip logging this request |
activity(adapter)
Returns a fluent ActivityLogger instance.
activity(adapter)
.by(causer) // object with { id, constructor.name }
.on(subject) // object with { id, constructor.name }
.withProperties({ key: val }) // extra metadata
.log('description') // saves and returns Promise<void>Types
interface ActivityAdapter {
save(payload: ActivityPayload): Promise<void>
}
interface ActivityPayload {
description: string
causerId?: string
causerType?: string
subjectId?: string
subjectType?: string
properties?: Record<string, unknown>
createdAt: Date
}
interface ActivityLogOptions {
adapter: ActivityAdapter
getUserId?: (req: Request) => string | undefined
getDescription?: (req: Request) => string
skip?: (req: Request) => boolean
}Issues & Support
- 🐛 Found a bug? Open a bug report
- 🚀 Have a feature idea? Open a feature request
- 📦 Want a new package? Open a package request
- 💬 Questions and discussions? Join the community
- 📚 Full documentation: docs.actinode.com
Documentation
Full documentation, guides and API reference is available at docs.actinode.com
Contributing
Contributions are welcome! Please visit our GitHub repository to get started.
License
MIT © actinode
