Docs

Express

A shared client, a req.audit() helper so route code stays one-line, a feed-token endpoint, and clean shutdown.

1. Install and initialize

npm install @softechlog/node, set SOFTECHLOG_SECRET_KEY in your environment, and export one instance:

lib/softechlog.jsNode SDK
// lib/softechlog.js
const { Softechlog } = require('@softechlog/node');

const log = new Softechlog({
  secretKey: process.env.SOFTECHLOG_SECRET_KEY, // stl_sk_…
});

module.exports = { log };

2. A one-line audit helper

The actor is almost always req.user, so scope it once in middleware and keep route code to a single line:

middleware/audit.jsmiddleware
// middleware/audit.js — attach a pre-scoped helper per request
const { log } = require('../lib/softechlog');

function audit(req, res, next) {
  req.audit = (action, target, metadata) =>
    log.track({
      actor: req.user
        ? { id: req.user.id, name: req.user.name, email: req.user.email }
        : { id: 'anonymous' },
      action,
      target,
      metadata,
    });
  next();
}

module.exports = { audit };
routes/members.jsusage
// routes/members.js
const router = require('express').Router();

router.post('/workspaces/:id/members', async (req, res) => {
  const invitee = await inviteMember(req.params.id, req.body);

  req.audit('workspace.member.invited',
    { type: 'workspace', id: req.params.id },
    { invited_email: invitee.email, role: invitee.role });

  res.status(201).json(invitee);
});

track() never throws and retries transient failures internally — a logging outage can't take a request down, so there's no try/catch to write.

3. Feed token endpoint

routes/activity.jsserver
// routes/activity.js — feed token for the signed-in user
const { log } = require('../lib/softechlog');

router.get('/activity-token', requireAuth, async (req, res, next) => {
  try {
    const { token, expires_at } = await log.feedToken({
      actorId: req.user.id,
      ttlSeconds: 3600,
    });
    res.json({ token, expires_at });
  } catch (err) {
    next(err); // feedToken throws on failure — a missing token is a real error
  }
});

The browser exchanges nothing but this token — see the Feed Component guide for the front-end half.

4. Graceful shutdown

Long-running servers rarely need this, but draining on SIGTERM guarantees no events are lost during deploys:

server.jsflush on SIGTERM
// server.js — drain in-flight events on shutdown
process.on('SIGTERM', async () => {
  await log.flush();
  server.close(() => process.exit(0));
});