Node servers
The SDK works on Node 18.18 and later, in any HTTP framework. It records the webhooks your server receives and the outgoing calls made with:
fetchand undici, and libraries built on undici (discord.js, for example);http/https, and everything built on them: Axios,@nestjs/axios, got, node-fetch and most API SDKs.
Calls your cloud provider's SDK makes (AWS, Google Cloud, Azure) are recorded too, grouped as infrastructure: monitored, with alerts off by default. Calls are recorded from the moment init() runs, including through HTTP clients created before it; calls made before it are not.
Where init() goes
ES modules (import)
In ES modules every import runs before the code of the file that imports it, so an init() call below your imports misses any call made while those modules load. Put it in a file of its own and import that file first:
// instrumentation.ts
import { init } from '@theirside/sdk';
init({
apiKey: process.env.THEIRSIDE_API_KEY,
endpoint: process.env.THEIRSIDE_COLLECTOR_URL,
service: 'orders-api',
environment: process.env.NODE_ENV ?? 'production',
});
// server.ts
import './instrumentation.js'; // first line, before anything else
import express from 'express';
const app = express();
app.get('/health', (_req, res) => {
res.send('ok');
});
app.listen(3000);
You can also load it from the command line, with no change to your entry file: node --import ./dist/instrumentation.js dist/server.js.
CommonJS (require)
require runs in order, so init() at the top of the entry file is enough:
// server.js
const { init } = require('@theirside/sdk');
init({
apiKey: process.env.THEIRSIDE_API_KEY,
endpoint: process.env.THEIRSIDE_COLLECTOR_URL,
service: 'orders-api',
environment: process.env.NODE_ENV || 'production',
});
const express = require('express');
const app = express();
app.get('/health', (_req, res) => {
res.send('ok');
});
app.listen(3000);
TypeScript compiled to CommonJS ("module": "commonjs") keeps your import order, so import { init } …; init(…); at the top of the entry file works there too. When in doubt, use the separate file: it works in both.
Config loaded at runtime
init() uses the values it gets when it runs: in the examples above, process.env.THEIRSIDE_API_KEY is read at that moment. If your app loads its settings later, from SSM, Secrets Manager, Vault or a config service, those variables are still empty then and the SDK stays off. Call init() with explicit values right after the config is loaded, before your app creates its HTTP clients and starts making calls:
// server.ts
import { init } from '@theirside/sdk';
import express from 'express';
const config = await loadConfig(); // your loader: SSM, Secrets Manager, Vault, a config service…
init({
apiKey: config.theirsideApiKey,
endpoint: config.theirsideCollectorUrl,
service: 'orders-api',
environment: config.environment,
});
const app = express(); // created after init(), like any HTTP clients
app.get('/health', (_req, res) => {
res.send('ok');
});
app.listen(3000);
Calls made before init() (the config loader's own, for example) aren't recorded; everything after is. On AWS Lambda, do the same at cold start: Serverless.
Express
As above: import ./instrumentation.js first in the file that creates the app. Nothing else to add. Webhooks your routes receive are picked up automatically (Webhooks).
Fastify
// server.ts
import './instrumentation.js';
import Fastify from 'fastify';
const app = Fastify();
app.get('/health', async () => 'ok');
await app.listen({ port: 3000, host: '0.0.0.0' });
NestJS
Import the instrumentation file at the top of main.ts, before NestFactory:
// main.ts
import './instrumentation.js';
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module.js';
const app = await NestFactory.create(AppModule);
await app.listen(3000);
// app.module.ts: your module as it is; nothing to add for Theirside
import { Module } from '@nestjs/common';
@Module({})
export class AppModule {}
No shutdown code is needed: the SDK sends what is queued on SIGTERM by itself (at most 2 s), also when the app calls app.enableShutdownHooks(). With SDK 0.4.1 or older and enableShutdownHooks(), add await shutdown() in an onApplicationShutdown() hook: Nest ends the process before those versions have sent.
A Nest project that compiles to CommonJS (the default nest new setup) can call init() at the very top of main.ts instead of using a separate file.
Service and environment names
servicenames the app in the dashboard, alerts and SDK health: one per deployable (orders-api,billing-worker). Several instances of the same app share it.environmentkeeps staging traffic from mixing with production: each environment learns its own normal and has its own incidents. Default:production.- Use the same names everywhere a service runs; renaming one starts a new series.
Shutting down
On SIGTERM and when the event loop empties, the SDK sends what is queued (at most 2 s) and lets your app exit as before. If another SIGTERM handler ends the process itself (re-raising the signal or calling process.exit(), as NestJS does with enableShutdownHooks()), the SDK holds that until it has sent, then exits the same way. Nothing else is needed: the SDK never keeps a process alive and never blocks a request.
Check it works
import { getStats } from '@theirside/sdk';
// undefined: the SDK is off (no apiKey or endpoint). Otherwise `sent` grows and
// lastFlushError stays undefined once the app has made a few calls.
console.log(getStats());
The service then shows under Settings → SDK health in the dashboard, and its providers on the Providers page within a minute. Anything else: Troubleshooting.