Skip to content

express-activitylog

@actinode/express-activitylog logs every request (or any custom event) to your database with a fluent, readable API.

Installation

sh
pnpm add @actinode/express-activitylog
sh
npm install @actinode/express-activitylog
sh
yarn add @actinode/express-activitylog

Then install an adapter. See Adapters for full setup guides.

sh
pnpm add @prisma/client
sh
pnpm add mongoose

Quick start

1. Set up your adapter

ts
import { PrismaClient } from '@prisma/client'
import { prismaAdapter } from '@actinode/express-activitylog'

const prisma = new PrismaClient()
const adapter = prismaAdapter(prisma)
ts
import mongoose from 'mongoose'
import { mongooseAdapter } from '@actinode/express-activitylog'

await mongoose.connect(process.env.MONGODB_URI!)
const adapter = mongooseAdapter(mongoose)

2. Register the middleware

ts
import express from 'express'
import { activityLog } from '@actinode/express-activitylog'

const app = express()

app.use(
  activityLog({
    adapter,
    getUserId: (req) => req.user?.id,
    skip: (req) => req.method === 'GET',
  }),
)

Every non-GET request is now logged automatically.

3. Log manually with the fluent builder

ts
import { activity } from '@actinode/express-activitylog'

await activity(adapter)
  .by(req.user)       // causer
  .on(post)           // subject
  .withProperties({ ip: req.ip })
  .log('published post')

Masking sensitive data

Sensitive fields are masked before they reach your database — both in the middleware (applied to req.body) and in manual log entries (applied to .withProperties()).

TIP

Even without any mask config, common sensitive fields like password, token, and apiKey are masked automatically. See the full list.

denyList — mask specific fields, keep everything else:

ts
activityLog({
  adapter,
  mask: { denyList: ['password', 'token'] },
})

allowList — declare which fields are safe to log, mask everything else:

ts
activityLog({
  adapter,
  mask: { allowList: ['name', 'email', 'role'] },
})

For the full masking reference — deep masking, per-model config, custom replacement strings, and security best practices — see the Masking page.

Minimal working example

ts
import express from 'express'
import { PrismaClient } from '@prisma/client'
import { prismaAdapter, activityLog, activity } from '@actinode/express-activitylog'

const prisma = new PrismaClient()
const adapter = prismaAdapter(prisma)
const app = express()

app.use(express.json())

// Auto-log every mutating request
app.use(
  activityLog({
    adapter,
    getUserId: (req) => (req as any).user?.id,
    getDescription: (req) => `${req.method} ${req.path}`,
    skip: (req) => req.method === 'GET',
  }),
)

app.post('/posts/:id/publish', async (req, res) => {
  const post = await prisma.post.update({
    where: { id: req.params.id },
    data: { published: true },
  })

  // Manual log with full context
  await activity(adapter)
    .on(post)
    .log('published post')

  res.json(post)
})

app.listen(3000)

Community & Support

Need an admin panel?

Get a beautiful admin UI for all actinode packages. Contact us to learn more about our premium admin package.

No spam, ever. Unsubscribe at any time.

Released under the MIT License.