@replaystack/sdk
v1.0.7
Published
Official ReplayStack SDK for Node.js 18+: capture HTTP requests, responses, exceptions, stack frames, and breadcrumbs; ship them to ReplayStack for debugging, replay, and dashboards. First-class Express, Next.js, and NestJS integrations.
Maintainers
Readme
ReplayStack TypeScript SDK
ReplayStack SDK captures backend events from Node.js/TypeScript applications and sends them to the ReplayStack ingestion API.
It is designed for backend debugging, event replay, exception inspection, and real-time dashboard updates.
What ReplayStack Captures
ReplayStack is production-safe and developer-friendly. It does not automatically capture every internal code line or execution step in full detail — no line-by-line debugger, profiler, or automatic function instrumentation.
ReplayStack captures the important debugging context automatically and allows developers to add meaningful breadcrumbs for business-level step tracking. It does not act as a production line-by-line debugger or profiler.
Level 1 — Automatic request/response (framework middleware)
When you use Express, Next.js, or NestJS adapters, the SDK automatically captures:
eventType, HTTP method, endpoint/path, status,statusCode,executionTimeMs- Request and response headers (masked)
- Request and response payloads (masked when enabled)
serviceName,environment,appVersion,commitHash,sourceIp,userAgent
Level 2 — Automatic exception detail
On failures, the SDK captures:
errorName,errorMessage,stackTracestackFrames[]withfunctionName,fileName,lineNumber,columnNumber(when available), andraw
This pinpoints where the error happened — not every line that ran before it.
Level 3 — Developer-defined breadcrumbs
Add meaningful business steps manually (nothing is auto-generated per source line):
replayStack.addBreadcrumb('Order API started');
replayStack.addBreadcrumb('Validated request payload');
replayStack.addBreadcrumb('Checking inventory', { orderId: 'ord_123' });Breadcrumb metadata is masked before storage. Framework HTTP lifecycle breadcrumbs are off by default; set automaticFrameworkBreadcrumbs: true on middleware options if you want them.
Level 4 — Optional logs (disabled by default)
const replayStack = createReplayStackClient({
apiKey: '...',
captureLogs: true, // default; set false to disable
logLevel: 'error', // default; can also be set via dashboard remote SDK config
});
replayStack.captureLog({
level: 'error',
message: 'Payment provider timeout',
metadata: { provider: 'stripe' },
});captureLogsdefaults totrue(setcaptureLogs: falseorREPLAYSTACK_CAPTURE_LOGS=falseto disable)logLeveldefaults toerror(onlyerrorand above are kept unless you lower the threshold)- Express/Nest error middleware auto-records the exception message as an error log when
captureLogsis enabled - Does not intercept
console, Winston, Pino, or other loggers — usecaptureLog()for extra lines or add logger adapters later
Masking
All request/response payloads and headers, breadcrumb metadata, and log metadata pass through shared recursive, case-insensitive masking before ingest. Fields are merged from:
- SDK defaults (
authorization,password,token,accessToken,refreshToken,apiKey,secret,cookie,session,cardNumber,otp, …) config.maskFieldsconfig.remoteMaskingRules.fields(e.g. from dashboard)
What ReplayStack does not capture
- Every executed line of code
- Every function call
- Local variables
- Full internal execution flow
Installation
npm install @replaystack/sdkFor Express apps:
npm install express @replaystack/sdkRequirements
- Node.js >= 18
- TypeScript supported
- A ReplayStack project API key
- ReplayStack backend ingestion endpoint
Package entry points
One npm package, multiple import paths. NestJS and Next.js helpers are not on the root entry (since v1.0.4)—use subpaths so Express apps do not need @nestjs/common.
| Import from | Use for |
| -------------------------- | -------------------------------------------------------------------------- |
| @replaystack/sdk | Client, Express middleware, captureEvent / captureException, utils |
| @replaystack/sdk/express | Express middleware only |
| @replaystack/sdk/nextjs | withReplayStackNext, withReplayStackNextApi |
| @replaystack/sdk/nestjs | createReplayStackNestInterceptor, createReplayStackNestExceptionFilter |
| @replaystack/sdk/client | Core client only |
Upgrading from v1.0.3? If you see Cannot find module '@nestjs/common' when importing from @replaystack/sdk, upgrade to ^1.0.4 and move Nest/Next imports to the subpaths above. Full guide: docs/PACKAGE-ENTRYPOINTS.md. See CHANGELOG.md.
Development
npm run lint
npm run lint:fix
npm run format
npm run format:check
npm test
npm run test:coverage
npm run test:typecheck
npm run release:verify # lint + format check + tests + typecheck + buildPublish to npm
- Ensure you are logged in:
npm login - Verify locally:
npm run release:verify - Dry run:
npm run publish:dry - Bump version (creates a git tag unless you use
--no-git-tag-version):npm run version:patch/version:minor/version:major - Publish:
npm run publish:npm(runsprepublishOnly→npm run build)
prepublishOnly only builds; run release:verify before publishing if you want the full gate.
The package uses Vitest with V8 coverage. Tests cover the core client (including offline queue, flush, and periodic flush), utilities, async context, stack parsing, Express middleware, and NestJS interceptor / exception filter plus Next.js App Router and Pages Router wrappers (with mocked HTTP).
Remaining gaps are mostly branch coverage inside Express body patching (res.send paths) and rarely hit Next helpers (readResponseBodySafely fallbacks, multipart hints).
Basic Client Setup
import { createReplayStackClient } from '@replaystack/sdk';
const replayStack = createReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT || 'https://api.replaystack.co',
serviceName: 'order-service',
environment: process.env.NODE_ENV || 'development',
appVersion: process.env.APP_VERSION,
commitHash: process.env.COMMIT_HASH,
});Express Middleware Usage
Use the normal middleware before routes and the error middleware after routes.
import express from 'express';
import {
createReplayStackClient,
replayStackExpressMiddleware,
replayStackExpressErrorMiddleware,
} from '@replaystack/sdk';
const app = express();
app.use(express.json());
const replayStack = createReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT || 'https://api.replaystack.co',
serviceName: 'main-api',
environment: process.env.NODE_ENV || 'development',
appVersion: process.env.APP_VERSION,
commitHash: process.env.COMMIT_HASH,
});
app.use(
replayStackExpressMiddleware(replayStack, {
captureRequestBody: true,
captureResponseBody: true,
captureHeaders: true,
ignoredPaths: ['/health', '/metrics'],
}),
);
app.post('/orders', async (req, res) => {
replayStack.addBreadcrumb('Validating order payload', {
category: 'order',
metadata: { bodyKeys: Object.keys(req.body || {}) },
});
replayStack.addBreadcrumb('Fetching user before order creation', {
category: 'order',
});
const user: any = undefined;
// This throws TypeError and ReplayStack captures file, line, column, and function.
const userId = user.id;
res.json({ success: true, userId });
});
// Must be after all routes.
app.use(replayStackExpressErrorMiddleware(replayStack));
app.listen(3000);Exception Capture Result
When an exception happens, the SDK sends details like this:
{
"eventType": "api",
"method": "POST",
"endpoint": "/orders",
"status": "failed",
"statusCode": 500,
"errorName": "TypeError",
"errorMessage": "Cannot read properties of undefined (reading 'id')",
"stackTrace": "TypeError: Cannot read properties...",
"stackFrames": [
{
"functionName": "anonymous",
"fileName": "/app/src/server.ts",
"lineNumber": 32,
"columnNumber": 23,
"raw": "at /app/src/server.ts:32:23"
}
],
"breadcrumbs": [
{
"message": "HTTP request started",
"category": "http",
"level": "info",
"timestamp": "2026-05-09T10:00:00.000Z"
},
{
"message": "Validating order payload",
"category": "order",
"level": "info",
"timestamp": "2026-05-09T10:00:01.000Z"
}
]
}This gives the dashboard enough detail to show:
Exception occurred in:
File: /app/src/server.ts
Line: 32
Column: 23
Function: anonymousBreadcrumbs
Breadcrumbs are developer-defined steps that explain what happened before an error.
replayStack.addBreadcrumb('Started payment processing', {
category: 'payment',
level: 'info',
metadata: { orderId: 'ord_123' },
});
replayStack.addBreadcrumb('Calling Stripe charge API', {
category: 'payment',
level: 'info',
});If an exception happens later in the same Express request, breadcrumbs are attached to the failed event.
For Express, breadcrumbs are request-scoped using Node.js AsyncLocalStorage, so concurrent requests do not mix their breadcrumbs.
For non-Express/manual usage, breadcrumbs are kept on the client instance until you call:
replayStack.clearBreadcrumbs();Optional logs
Logs are disabled by default. Enable explicitly when you need them in failed events:
const replayStack = createReplayStackClient({
apiKey: '...',
captureLogs: true,
logLevel: 'error', // or lower via SDK config / dashboard remote settings
});
replayStack.captureLog({
level: 'error',
message: 'Inventory service unavailable',
metadata: { sku: 'SKU-42' },
});Log metadata is masked. The SDK does not hook console or third-party loggers — call captureLog() from your code or a future logger adapter.
Manual Exception Capture
Use this for queues, cron jobs, webhooks, background workers, or custom logic.
import { createTraceId } from '@replaystack/sdk';
async function processPaymentJob(job: any) {
const traceId = createTraceId();
const startedAt = Date.now();
replayStack.addBreadcrumb('Payment job started', {
category: 'queue',
metadata: { jobId: job.id },
});
try {
throw new Error('Stripe timeout');
} catch (error) {
await replayStack.captureException(error, {
traceId,
eventType: 'queue',
endpoint: 'payment.process',
requestPayload: job,
statusCode: 500,
executionTimeMs: Date.now() - startedAt,
logs: [
{
level: 'error',
message: 'Payment job failed',
metadata: { jobId: job.id },
},
],
});
replayStack.clearBreadcrumbs();
throw error;
}
}Manual Event Capture
Use manual capture for successful/failed custom events.
await replayStack.captureEvent({
traceId: createTraceId(),
eventType: 'custom',
endpoint: 'inventory.sync',
requestPayload: { sku: 'ABC-123' },
responsePayload: { synced: true },
status: 'success',
statusCode: 200,
executionTimeMs: 120,
});Stack Trace Parser
You can also parse stack traces directly:
import { parseStackTrace } from '@replaystack/sdk';
try {
throw new Error('Something failed');
} catch (error: any) {
const frames = parseStackTrace(error.stack);
console.log(frames);
}Configuration
createReplayStackClient({
apiKey: 'rs_live_xxxxx',
endpoint: 'https://api.replaystack.co',
serviceName: 'order-service',
environment: 'production',
appVersion: '1.2.0',
commitHash: 'a7f91c',
enabled: true,
timeoutMs: 2500,
retries: 1,
sampleRate: 1,
captureSuccess: true,
maxPayloadSizeBytes: 512 * 1024,
maxBreadcrumbs: 50,
maskFields: ['password', 'token', 'authorization'],
ignoredPaths: ['/health', '/metrics'],
offlineQueueMax: 100,
flushIntervalMs: 0,
});| Option | Description | Default |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------- | ---------------------------- |
| apiKey | Project API key from ReplayStack dashboard | Required |
| endpoint | ReplayStack backend base URL (optional) | https://api.replaystack.co |
| serviceName | Current backend service name | undefined |
| environment | local/development/staging/production | NODE_ENV |
| appVersion | App release version | undefined |
| commitHash | Deployment commit hash | undefined |
| enabled | Enable or disable SDK | true |
| timeoutMs | Request timeout for ingestion | 2500 |
| retries | Retry count if ingestion fails | 1 |
| sampleRate | Capture percentage from 0 to 1 | 1 |
| captureSuccess | Capture successful events | false |
| captureLogs | Attach captureLog() entries to events | true |
| logLevel | Minimum log level when captureLogs is true (debug | info | warning | error) | error |
| maxPayloadSizeBytes | Payload truncation size | 524288 |
| maxBreadcrumbs | Number of breadcrumbs kept per request/client | 50 |
| maxLogs | Number of logs kept per request/client when captureLogs is true | 50 |
| maskFields | Custom fields to mask (merged with SDK defaults) | [] |
| remoteMaskingRules | Dashboard-provided rules, e.g. { fields: ['internalId'] } | undefined |
| ignoredPaths | Paths to ignore | [] |
| offlineQueueMax | Max prepared events held in memory after ingest still fails after retries; oldest dropped when full. 0 disables the queue | 0 |
| flushIntervalMs | If greater than 0, periodically calls flush() to drain the offline queue | 0 (disabled) |
| onQueueDrop | Callback when the queue drops the oldest event because offlineQueueMax was exceeded | none |
Ingest reliability: offline queue, flush, and shutdown
When the ingest HTTP request fails after the configured retries, the SDK can keep a bounded in-memory queue of prepared events (offlineQueueMax, default 100). Set offlineQueueMax to 0 to turn this off (failed sends are dropped immediately after retries).
flush()— Sends queued events in order until the queue is empty or a send fails again.flushIntervalMs— If set to a positive number, the client runsflush()on that interval so events drain automatically when the API recovers.close()— Stops new automatic capture where applicable, cancels periodic flush, then drains the queue once (best effort).onQueueDrop— Optional hook when the queue is full and the oldest event is removed to make room.
In Node.js, you can register process hooks so the client attempts to flush before exit:
import { createReplayStackClient, installReplayStackProcessGuards } from '@replaystack/sdk';
const replayStack = createReplayStackClient({ apiKey: process.env.REPLAYSTACK_API_KEY! });
installReplayStackProcessGuards(replayStack);This wires unhandledRejection, uncaughtException, and beforeExit to call flush() best effort. It does not guarantee delivery on hard crashes (for example SIGKILL or abrupt process death); for that you need an out-of-process buffer or WAL outside this SDK.
Environment Variables
REPLAYSTACK_ENDPOINT is optional. If unset, the client uses https://api.replaystack.co (same as omitting endpoint in config). Set it for staging, self-hosted, or regional gateways.
REPLAYSTACK_API_KEY=rs_live_xxxxxxxxxxxxxxxxx
# Optional — defaults to https://api.replaystack.co
# REPLAYSTACK_ENDPOINT=https://api.replaystack.co
REPLAYSTACK_SERVICE_NAME=order-service
REPLAYSTACK_APP_VERSION=1.2.0
REPLAYSTACK_COMMIT_HASH=a7f91c
REPLAYSTACK_ENABLED=true
REPLAYSTACK_TIMEOUT_MS=2500
REPLAYSTACK_RETRIES=1
REPLAYSTACK_SAMPLE_RATE=1
REPLAYSTACK_CAPTURE_SUCCESS=true
REPLAYSTACK_MAX_PAYLOAD_SIZE_BYTES=524288
REPLAYSTACK_MAX_BREADCRUMBS=50
# Optional — max events to buffer in memory after failed ingest (0 = disable queue)
# REPLAYSTACK_OFFLINE_QUEUE_MAX=100
# Optional — periodic flush interval in ms (0 = disabled)
# REPLAYSTACK_FLUSH_INTERVAL_MS=0Ingestion API Contract
The SDK sends events to:
POST /api/v1/ingest/events
x-replaystack-api-key: rs_live_xxxxxxxxxThe backend should accept this shape:
{
"traceId": "uuid",
"eventType": "api",
"method": "POST",
"endpoint": "/api/orders",
"requestUrl": "https://orders.example.com/api/orders?page=1",
"requestHeaders": {},
"requestPayload": {},
"responseHeaders": {},
"responsePayload": {},
"status": "failed",
"statusCode": 500,
"executionTimeMs": 840,
"errorName": "TypeError",
"errorMessage": "Cannot read properties of undefined",
"stackTrace": "Error stack trace here",
"stackFrames": [],
"breadcrumbs": [],
"serviceName": "order-service",
"environment": "production",
"appVersion": "1.2.0",
"commitHash": "a7f91c",
"sourceIp": "127.0.0.1",
"userAgent": "Mozilla/5.0",
"logs": []
}Sensitive Data Masking
The SDK masks these fields by default:
- authorization
- password
- passwd
- token
- access_token
- refresh_token
- apiKey
- api_key
- cookie
- set-cookie
- cardNumber
- card_number
- cvv
- otp
- secret
- client_secret
Custom fields can be added:
createReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
maskFields: ['patientId', 'nationalId', 'sessionId'],
});Build
npm install
npm run buildPublish
npm publish --access publicLicense
MIT
Framework support
ReplayStack supports four integration styles:
| Framework / Use Case | Import helpers from | Integration method |
| -------------------------- | ------------------------- | --------------------------------------------------------------------------- |
| Express | @replaystack/sdk | replayStackExpressMiddleware + replayStackExpressErrorMiddleware |
| Next.js App Router | @replaystack/sdk/nextjs | withReplayStackNext |
| Next.js Pages Router | @replaystack/sdk/nextjs | withReplayStackNextApi |
| NestJS | @replaystack/sdk/nestjs | createReplayStackNestInterceptor + createReplayStackNestExceptionFilter |
| Any backend / queue / cron | @replaystack/sdk | captureEvent() / captureException() |
| Unsupported language | — | Direct HTTP API: POST /api/v1/ingest/events |
Express
import express from 'express';
import { ReplayStackClient, replayStackExpressMiddleware, replayStackExpressErrorMiddleware } from '@replaystack/sdk';
const app = express();
app.use(express.json());
const replayStack = new ReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT!,
serviceName: 'express-api',
environment: process.env.NODE_ENV || 'development',
appVersion: process.env.APP_VERSION,
commitHash: process.env.COMMIT_HASH,
});
app.use(replayStackExpressMiddleware(replayStack));
app.get('/health', (_req, res) => res.json({ ok: true }));
app.use(replayStackExpressErrorMiddleware(replayStack));Next.js App Router
Use this in files such as app/api/orders/route.ts.
import { NextRequest, NextResponse } from 'next/server';
import { ReplayStackClient } from '@replaystack/sdk';
import { withReplayStackNext } from '@replaystack/sdk/nextjs';
const replayStack = new ReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT!,
serviceName: 'nextjs-api',
environment: process.env.NODE_ENV || 'development',
appVersion: process.env.APP_VERSION,
commitHash: process.env.COMMIT_HASH,
});
export const POST = withReplayStackNext(
async function POST(req: NextRequest) {
const body = await req.json();
return NextResponse.json({
success: true,
order: { id: 'ord_123', ...body },
});
},
{
client: replayStack,
endpoint: '/api/orders',
},
);Next.js Pages Router
Use this in files such as pages/api/orders.ts.
import type { NextApiRequest, NextApiResponse } from 'next';
import { ReplayStackClient } from '@replaystack/sdk';
import { withReplayStackNextApi } from '@replaystack/sdk/nextjs';
const replayStack = new ReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT!,
serviceName: 'nextjs-pages-api',
environment: process.env.NODE_ENV || 'development',
});
async function handler(req: NextApiRequest, res: NextApiResponse) {
res.status(200).json({ success: true });
}
export default withReplayStackNextApi(handler, {
client: replayStack,
});Next.js Server Actions
Server Actions should use manual capture because they do not behave like normal HTTP middleware.
'use server';
import { replayStack } from '@/lib/replaystack';
export async function createOrderAction(formData: FormData) {
const startedAt = Date.now();
try {
replayStack.addBreadcrumb('Server action started');
replayStack.addBreadcrumb('Validating order form data');
const order = { id: 'ord_123' };
await replayStack.captureEvent({
eventType: 'custom',
endpoint: 'createOrderAction',
status: 'success',
statusCode: 200,
executionTimeMs: Date.now() - startedAt,
requestPayload: Object.fromEntries(formData),
responsePayload: order,
});
return order;
} catch (error) {
await replayStack.captureException(error, {
eventType: 'custom',
endpoint: 'createOrderAction',
executionTimeMs: Date.now() - startedAt,
});
throw error;
}
}NestJS
NestJS integration uses an interceptor for normal request/response capture and an exception filter for errors.
// app.module.ts
import { Module } from '@nestjs/common';
import { APP_FILTER, APP_INTERCEPTOR } from '@nestjs/core';
import { ReplayStackClient } from '@replaystack/sdk';
import { createReplayStackNestExceptionFilter, createReplayStackNestInterceptor } from '@replaystack/sdk/nestjs';
const replayStack = new ReplayStackClient({
apiKey: process.env.REPLAYSTACK_API_KEY!,
endpoint: process.env.REPLAYSTACK_ENDPOINT!,
serviceName: 'nestjs-api',
environment: process.env.NODE_ENV || 'development',
appVersion: process.env.APP_VERSION,
commitHash: process.env.COMMIT_HASH,
});
@Module({
providers: [
{
provide: APP_INTERCEPTOR,
useClass: createReplayStackNestInterceptor({ client: replayStack }),
},
{
provide: APP_FILTER,
useClass: createReplayStackNestExceptionFilter({ client: replayStack }),
},
],
})
export class AppModule {}What Next.js/NestJS Adapters Capture
The adapters capture:
- HTTP method
- endpoint/path
- request headers and payload
- response headers and payload
- status code
- execution time
- error name and message
- stack trace
- parsed stack frames with file/function/line/column
- breadcrumbs
- service name
- environment
- app version
- commit hash
Backend Requirement
All adapters send captured data to the same ReplayStack ingestion endpoint:
POST /api/v1/ingest/events
x-replaystack-api-key: rs_live_xxxxxxxxxYour ReplayStack backend should validate the project API key, resolve project_id, check plan limits, store/process the event, then publish real-time dashboard updates using SSE.
