@cherrypeak-org/cherryboard-client
v1.1.0
Published
CherryBoard error tracking client for Node.js — captures and sends application errors to CherryBoard Dashboard
Maintainers
Readme
@cherrypeak-org/cherryboard-client
A lightweight, zero-dependency Node.js/TypeScript client for capturing and sending application errors to the CherryBoard Dashboard — the JavaScript equivalent of the CherryPeak.CherryBoard.Client NuGet package for .NET.
Features
- Automatic error capture via Lambda handler wrapper or Express middleware
- In-memory queue with configurable batch size and interval
- Exponential-backoff retries (skips 4xx client errors)
- Sampling — configurable capture rate from 0% to 100%
- Zero runtime dependencies — uses native
fetch(Node 18+) - TypeScript-first with full type declarations
Requirements
- Node.js 18+ (for native
fetch)
Installation
npm install @cherrypeak-org/cherryboard-clientRequires Node 18 or later — the client uses the built-in fetch, so it has no
runtime dependencies of its own.
The API key
Create one in the CherryBoard dashboard under Project → Environment → API keys, and use a Server key: it can read and manage data, so it belongs in secrets management and never in a commit or a client bundle.
# Development
CHERRYBOARD_API_KEY=cpd_xxxxxxxxxxxxxxxxThe key determines which project and environment your reports land in, so use a separate one per environment — revoking a leaked key then affects only that environment.
Quick Start
1. Create a client instance
import { createCherryBoardClient } from "@cherrypeak-org/cherryboard-client";
const client = createCherryBoardClient({
apiKey: process.env.CHERRYBOARD_API_KEY!,
apiUrl: "https://api.cherryboard.cherrypeak.eu",
environment: process.env.NODE_ENV ?? "production",
});2a. AWS Lambda (SST / Serverless)
import { createCherryBoardClient, withCherryBoard } from "@cherrypeak-org/cherryboard-client";
const client = createCherryBoardClient({
apiKey: process.env.CHERRYBOARD_API_KEY!,
apiUrl: process.env.CHERRYBOARD_API_URL!,
environment: process.env.SST_STAGE ?? "production",
});
// Wrap your handler — errors are captured automatically and the queue is
// flushed after every invocation (critical for Lambda's freeze behavior).
export const handler = withCherryBoard(async (event, context) => {
// ... your handler logic ...
return { statusCode: 200, body: JSON.stringify({ ok: true }) };
}, client);2b. Express
import express from "express";
import {
createCherryBoardClient,
cherryBoardErrorHandler,
} from "@cherrypeak-org/cherryboard-client";
const app = express();
const client = createCherryBoardClient({
apiKey: process.env.CHERRYBOARD_API_KEY!,
apiUrl: process.env.CHERRYBOARD_API_URL!,
});
// Your routes...
app.get("/", (req, res) => {
res.send("Hello");
});
// Register the error handler AFTER all routes
app.use(cherryBoardErrorHandler(client));
app.listen(3000);3. Manual capture
try {
await riskyOperation();
} catch (err) {
await client.captureException(err as Error, (data) => {
data.userId = currentUser.id;
data.metadata = JSON.stringify({ orderId: "123" });
});
}Or send a pre-built error:
import { createErrorData, Severity } from "@cherrypeak-org/cherryboard-client";
await client.captureError(
createErrorData({
message: "Payment failed",
severity: Severity.Critical,
userId: user.id,
metadata: JSON.stringify({ provider: "stripe", code: "card_declined" }),
})
);Configuration
| Option | Type | Default | Valid Range | Description |
| --- | --- | --- | --- | --- |
| apiKey | string | — | Required | API key (X-API-Key header) |
| apiUrl | string | — | Required | Base URL of Dashboard API |
| environment | string | "Production" | Any | Environment label |
| enableAutomaticCapture | boolean | true | — | Enable middleware/wrapper auto-capture |
| maxBatchSize | number | 50 | 1–1000 | Queue threshold before immediate flush |
| batchIntervalSeconds | number | 30 | 1–3600 | Timer interval for queue drain |
| maxRetries | number | 3 | 0–10 | Retry attempts for failed HTTP calls |
| enableOfflineQueue | boolean | true | — | Use in-memory queue + batch sending |
| sampleRate | number | 1.0 | 0.0–1.0 | Probability of capturing each error |
| disableSslValidation | boolean | false | — | Skip SSL cert validation (dev only) |
API Reference
createCherryBoardClient(options, logger?)
Factory function. Returns a CherryBoardClient instance.
CherryBoardClient
| Method | Returns | Description |
| --- | --- | --- |
| captureException(error, configure?) | Promise<string \| null> | Capture a JS Error with auto-populated fields |
| captureError(errorData) | Promise<string \| null> | Send a pre-built ErrorData |
| sendBatch(errors) | Promise<BatchErrorResponse \| null> | Send multiple errors in one request |
| flush() | Promise<void> | Drain the internal queue immediately |
| destroy() | Promise<void> | Stop batch timer + flush (for graceful shutdown) |
withCherryBoard(handler, client, options?)
Wraps an AWS Lambda handler. Auto-captures errors and flushes after every invocation.
cherryBoardErrorHandler(client)
Returns an Express error-handling middleware ((err, req, res, next)).
Severity Levels
import { Severity } from "@cherrypeak-org/cherryboard-client";
Severity.Debug // 0
Severity.Info // 1
Severity.Warning // 2
Severity.Error // 3
Severity.Critical // 4Logging
The client accepts an optional logger matching this interface:
interface Logger {
debug(message: string, ...args: unknown[]): void;
info(message: string, ...args: unknown[]): void;
warn(message: string, ...args: unknown[]): void;
error(message: string, ...args: unknown[]): void;
}Pass console, pino(), winston.createLogger(), or any compatible logger:
import pino from "pino";
const client = createCherryBoardClient(
{ apiKey: "...", apiUrl: "..." },
pino()
);Comparison with .NET Client
| Feature | .NET (NuGet) | Node.js (this package) |
| --- | --- | --- |
| Queue | ConcurrentQueue<T> | Array (single-threaded) |
| Batch timer | System.Threading.Timer | setInterval (unref'd) |
| HTTP | HttpClient + DI | Native fetch |
| Middleware | ASP.NET Core middleware | Lambda wrapper / Express middleware |
| DI | IServiceCollection.AddCherryBoard() | createCherryBoardClient() factory |
| Retry | Exponential backoff, skip 4xx | Same |
| Lambda support | N/A | withCherryBoard() with auto-flush |
Request timing
Times each request and reports per-route rollups to the dashboard, where they appear alongside the browser SDK's numbers for the same route.
Off by default. Two things to enable it:
import express from "express";
import {
createCherryBoardClient,
cherryBoardPerformance,
cherryBoardErrorHandler,
} from "@cherrypeak-org/cherryboard-client";
const client = createCherryBoardClient({
apiKey: process.env.CHERRYBOARD_API_KEY!,
apiUrl: "https://api.cherryboard.cherrypeak.eu",
enablePerformanceTracking: true,
});
const app = express();
// Register early. Anything above this line is time the measurement never sees.
app.use(cherryBoardPerformance(client.metrics!));
app.get("/api/orders/:id", handler);
// Error handling stays last.
app.use(cherryBoardErrorHandler(client));Call client.destroy() on shutdown so the final window is sent rather than lost
with the process.
Why measure both sides
The browser SDK already reports how long API calls take. That figure includes DNS, TLS, the network and transferring the response; this one is server processing alone.
| What you see | What it points at | |---|---| | Both numbers similar | Your code — the time is in the handler | | Frontend much higher | Payload size or the network, not the handler | | Backend high, few calls | A slow dependency on a rarely-hit endpoint |
What it costs
A timestamp per request and an in-memory counter update. Rollups are posted on a timer, so the payload is bounded by how many endpoints the service has rather than how much traffic it serves — a service handling a million requests sends the same small summary as one handling a hundred.
Delivery failures are swallowed by design. A dropped window is a gap in a chart; a thrown exception would be an outage.
What is not recorded
Responses the server holds open are skipped automatically: server-sent events
(text/event-stream) and protocol upgrades (HTTP 101). Their elapsed time is
how long the client stayed subscribed, not how long anything took to compute —
left in, a healthy stream sits permanently at the top of the slowest-routes
table.
For anything else slow by design — long-polling, large downloads — mark the handler:
import { cherryBoardIgnorePerformance } from "@cherrypeak-org/cherryboard-client";
app.get("/download/:id", cherryBoardIgnorePerformance(async (req, res) => {
// ...
}));Use it sparingly: a route that is genuinely slow is what this feature exists to show you.
What gets recorded
Paths are normalized before anything is stored, so /api/orders/12345 becomes
/api/orders/:id. This uses the same rules as the browser SDK and the .NET
client deliberately — the dashboard pairs a route measured in the browser with
the same route measured on the server, and that pairing is a string match, so
the implementations have to agree.
- Query strings are never recorded — they carry tokens and email addresses, and say nothing about how long a request took.
- 404s group under one label, since the set of paths nobody serves is unbounded.
4xxis not counted as an error: a caller's bad request is not your endpoint failing.
License
MIT
