SDK Reference

Node SDK Reference

The Databuddy Node SDK provides a lightweight, type-safe way to track server-side analytics events from Node.js, Bun, Deno, and serverless environments.

TL;DR

The Node SDK is perfect for tracking server-side events like API calls, background jobs, webhooks, and more. It supports batching and explicit flushes for serverless environments.

tsx
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: true
});

await client.track({
name: 'api_call',
properties: { endpoint: '/api/users', method: 'GET' }
});

await client.flush(); // Important in serverless!

Installation

bash
bun add @databuddy/sdk

Quick Start

Basic Usage

tsx
import { Databuddy } from '@databuddy/sdk/node';

// Initialize the client
const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!
});

// Track an event
await client.track({
name: 'user_signup',
properties: {
  plan: 'pro',
  source: 'api'
}
});

Serverless Usage

In serverless environments, always flush before the function exits:

tsx
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: true
});

export async function handler(event) {
await client.track({
  name: 'lambda_invocation',
  properties: { source: event.source }
});

// Ensure events are sent before function exits
const flushResult = await client.flush();
if (!flushResult.success) {
  console.error("Failed to flush analytics:", flushResult.error);
}

return { statusCode: 200 };
}

Configuration

tsx
interface DatabuddyConfig {
  apiKey: string;               // Required: Your API key for authentication
  websiteId?: string;           // Optional: Default Client ID to scope events
  namespace?: string;           // Optional: Default namespace for logical grouping
  source?: string;              // Optional: Default source identifier for events
  apiUrl?: string;              // Default: 'https://basket.databuddy.cc'
  debug?: boolean;              // Enable debug logging
  logger?: Logger;              // Custom logger instance
  enableBatching?: boolean;     // Default: true
  batchSize?: number;           // Default: 10, Max: 100
  batchTimeout?: number;        // Default: 2000ms
  maxQueueSize?: number;        // Default: 1000
  enableDeduplication?: boolean; // Default: true
  maxDeduplicationCacheSize?: number; // Default: 10000
  middleware?: Middleware[];     // Transform or drop events before send
}
tsx
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: true,
batchSize: 20,
batchTimeout: 5000
});

Core Methods

track(event)

Track custom events with optional properties, session IDs, and anonymous IDs:

tsx
await client.track({
name: 'purchase_completed',
eventId: 'purchase-ORD-789',
anonymousId: 'anon_123',
sessionId: 'sess_456',
properties: {
  order_id: 'ORD-789',
  revenue: 299.99,
  currency: 'USD',
  items: 3
}
});

Response:

tsx
interface EventResponse {
success: boolean;
eventId?: string;
error?: string;
}

With enableBatching: true, track() usually confirms that the event was queued. It returns a delivery failure only when the call immediately sends the event, such as enableBatching: false or when the queue reaches batchSize or maxQueueSize. Call flush() when you need the final delivery result.

Use eventId when your code may retry the same logical event. The SDK uses it as a local deduplication key for queued or already delivered events.

batch(events)

Send multiple events in a single batch (max 100 events):

tsx
await client.batch([
{
  type: 'custom',
  name: 'event1',
  properties: { foo: 'bar' }
},
{
  type: 'custom',
  name: 'event2',
  anonymousId: 'anon_123',
  sessionId: 'sess_456',
  properties: { baz: 'qux' }
}
]);

flush()

Manually flush all queued events. Critical for serverless environments:

tsx
await client.track({ name: 'event1' });
await client.track({ name: 'event2' });
await client.track({ name: 'event3' });

// Flush all queued events before function exits
const flushResult = await client.flush();
if (!flushResult.success) {
console.error("Failed to flush analytics:", flushResult.error);
}

Event Tracking Patterns

Per-Event Options

You can override the default websiteId, namespace, and source for individual events:

tsx
// Set defaults in config
const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
websiteId: "default-website",
namespace: "api",
source: "backend"
});

// Override per-event
await client.track({
name: 'payment_processed',
websiteId: 'billing-service',  // Override default websiteId
namespace: 'payments',          // Override default namespace
source: 'stripe-webhook',       // Override default source
properties: {
  amount: 99.99,
  currency: 'USD'
}
});

// Mix defaults and overrides
await client.track({
name: 'user_login',
namespace: 'auth',  // Only override namespace
properties: {
  method: 'oauth',
  provider: 'google'
}
});

Use Cases:

  • websiteId: Scope events to different websites within your organization
  • namespace: Group events logically (e.g., billing, auth, api, webhook)
  • source: Identify where the event originated (e.g., backend, webhook, cli, cron-job)

E-commerce Events

tsx
// Product viewed
await client.track({
name: 'product_viewed',
anonymousId: userId,
sessionId: sessionId,
properties: {
  product_id: 'P12345',
  category: 'Electronics',
  price: 99.99,
  currency: 'USD'
}
});

// Purchase completed
await client.track({
name: 'purchase_completed',
anonymousId: userId,
sessionId: sessionId,
properties: {
  order_id: 'ORD-789',
  revenue: 299.99,
  currency: 'USD',
  items: 3,
  payment_method: 'credit_card'
}
});

API & Backend Events

tsx
// API call tracking
await client.track({
name: 'api_call',
properties: {
  endpoint: '/api/users',
  method: 'GET',
  status_code: 200,
  duration_ms: 45
}
});

// Background job tracking
await client.track({
name: 'job_completed',
properties: {
  job_name: 'email_digest',
  duration_ms: 5400,
  emails_sent: 1250,
  status: 'success'
}
});

Webhook Events

tsx
// Stripe webhook
await client.track({
name: 'stripe_webhook_received',
properties: {
  event_type: 'payment_intent.succeeded',
  amount: 2999,
  currency: 'usd'
}
});

// Generic webhook
await client.track({
name: 'webhook_received',
properties: {
  source: 'github',
  event_type: 'push',
  repository: 'my-repo'
}
});

Authentication Events

tsx
// User signup
await client.track({
name: 'user_signup',
anonymousId: userId,
properties: {
  method: 'email',
  plan: 'free',
  source: 'landing_page'
}
});

// User login
await client.track({
name: 'user_login',
anonymousId: userId,
sessionId: sessionId,
properties: {
  method: 'oauth',
  provider: 'google',
  success: true
}
});

Session & Anonymous IDs

The Node SDK supports optional anonymousId and sessionId fields to track user sessions across requests. These IDs should match the IDs generated by the web SDK to enable unified tracking across client and server.

Getting IDs from the Web SDK

The web SDK automatically generates and manages these IDs. You need to pass them to your backend to use in server-side tracking.

Client-Side (React/Next.js):

app/components/checkout-button.tsxtsx
import { getTrackingIds } from '@databuddy/sdk';

export function CheckoutButton() {
const handleCheckout = async () => {
  const { anonId: anonymousId, sessionId } = getTrackingIds();
  
  if (anonymousId || sessionId) {
    await fetch('/api/checkout', {
      method: 'POST',
      body: JSON.stringify({ anonymousId, sessionId, cart: [...] })
    });
  }
};

return <button onClick={handleCheckout}>Checkout</button>;
}

Server-Side (API Route):

app/api/checkout/route.tstsx
import { Databuddy } from '@databuddy/sdk/node';
import { NextResponse } from 'next/server';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!
});

export async function POST(request: Request) {
const { anonymousId, sessionId, cart } = await request.json();

await client.track({
  name: 'checkout_started',
  anonymousId,
  sessionId,
  properties: {
    cart_value: calculateTotal(cart),
    items_count: cart.length
  }
});

return NextResponse.json({ success: true });
}

Web SDK ID Access Methods

tsx
import { getAnonymousId, getSessionId, getTrackingIds } from '@databuddy/sdk';

// Method 1: Via SDK helpers (React/Next.js)
const { anonId: anonymousId, sessionId } = getTrackingIds();

// Method 2: Individual helpers
const currentAnonymousId = getAnonymousId();
const currentSessionId = getSessionId();

Environment-Specific Usage

Next.js API Routes

app/api/track/route.tstsx
import { Databuddy } from '@databuddy/sdk/node';
import { NextResponse } from 'next/server';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: false
});

export async function POST(request: Request) {
const body = await request.json();

await client.track({
  name: 'api_endpoint_called',
  properties: {
    endpoint: '/api/track',
    payload_size: JSON.stringify(body).length
  }
});

return NextResponse.json({ success: true });
}

Express.js Middleware

tsx
import express from 'express';
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!
});

const app = express();

app.use(async (req, res, next) => {
const start = Date.now();

res.on('finish', async () => {
  await client.track({
    name: 'http_request',
    properties: {
      method: req.method,
      path: req.path,
      status_code: res.statusCode,
      duration_ms: Date.now() - start
    }
  });
});

next();
});

process.on('SIGTERM', async () => {
await client.flush();
process.exit(0);
});

AWS Lambda

tsx
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: true
});

export const handler = async (event: any) => {
try {
  await client.track({
    name: 'lambda_invoked',
    properties: {
      function_name: process.env.AWS_LAMBDA_FUNCTION_NAME,
      event_source: event.source
    }
  });
  
  const result = await processEvent(event);
  
  await client.track({
    name: 'lambda_success',
    properties: { result_count: result.length }
  });
  
  return { statusCode: 200, body: JSON.stringify(result) };
} catch (error) {
  await client.track({
    name: 'lambda_error',
    properties: { error: error.message, stack: error.stack }
  });
  throw error;
} finally {
  await client.flush();
}
};

Cloudflare Workers

tsx
import { Databuddy } from '@databuddy/sdk/node';

export default {
async fetch(request: Request, env: Env) {
  const client = new Databuddy({
    apiKey: env.DATABUDDY_API_KEY,
    enableBatching: false
  });
  
  await client.track({
    name: 'worker_request',
    properties: { url: request.url, method: request.method }
  });
  
  return new Response('Hello World');
}
};

Bun Server

tsx
import { Databuddy } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!
});

Bun.serve({
port: 3000,
async fetch(req) {
  await client.track({
    name: 'bun_request',
    properties: { url: req.url, method: req.method }
  });
  
  return new Response('Hello from Bun!');
}
});

Batching

Events are automatically batched when batch size or timeout is reached:

tsx
const client = new Databuddy({
  apiKey: process.env.DATABUDDY_API_KEY!,
  websiteId: process.env.DATABUDDY_WEBSITE_ID,
  namespace: "api",
  source: "backend",
  enableBatching: true,
  batchSize: 20,
  batchTimeout: 5000
});

// These will be batched automatically
await client.track({ name: 'event1' });
await client.track({ name: 'event2' });
await client.track({ name: 'event3' });

const flushResult = await client.flush();
if (!flushResult.success) {
console.error("Failed to deliver analytics:", flushResult.error);
}

Deduplication

When enableDeduplication is enabled, events with the same eventId are dropped if they are already queued or were delivered successfully. Failed sends do not poison the deduplication cache, so you can retry the same eventId after a temporary network or API failure.

tsx
const response = await client.track({
name: "invoice_paid",
eventId: `stripe-${stripeEvent.id}`,
properties: { invoice_id: invoice.id }
});

if (!response.success) {
// Safe to retry later with the same eventId
await retryAnalyticsEvent();
}

Debugging

Enable debug logging:

tsx
const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
debug: true
});

Error Handling

The SDK returns success/failure status for delivery errors instead of throwing:

tsx
const response = await client.track({
name: 'test_event',
properties: { test: true }
});

if (!response.success) {
console.error('Failed to track event:', response.error);
}

The constructor still throws for invalid configuration, such as a missing or blank apiKey.

TypeScript Support

The SDK is fully typed:

tsx
import { Databuddy, type CustomEventInput } from '@databuddy/sdk/node';

const client = new Databuddy({
apiKey: process.env.DATABUDDY_API_KEY!,
enableBatching: true
});

const event: CustomEventInput = {
name: 'user_signup',
anonymousId: 'anon_123',
sessionId: 'sess_456',
properties: { plan: 'pro', source: 'api' }
};

await client.track(event);

Best Practices

Always Flush in Serverless

tsx
// ✅ Good
export async function handler(event: any) {
await client.track({ name: 'event' });
await client.flush();
return { statusCode: 200 };
}

// ❌ Bad - events may be lost
export async function handler(event: any) {
await client.track({ name: 'event' });
return { statusCode: 200 };
}

Use Consistent Property Names

tsx
// ✅ Good - consistent naming
await client.track({
name: 'api_call',
properties: {
  endpoint: '/api/users',
  method: 'GET',
  status_code: 200
}
});

// ❌ Bad - inconsistent naming
await client.track({
name: 'API_CALL',
properties: {
  EndPoint: '/api/users',
  METHOD: 'GET',
  statusCode: 200
}
});

Troubleshooting

IssueSolution
Events not appearingVerify apiKey, check network in logs with debug: true
Authentication errorsEnsure apiKey is valid and has proper permissions
TypeScript errorsEnsure TypeScript 4.5+, check @databuddy/sdk version
Serverless events missingAlways call await client.flush() before function exits
High latencyEnable batching: enableBatching: true, batchSize: 50
Temporary network failuresCheck response.success and retry from your application when the event is safe to send again
Memory issuesReduce maxQueueSize, flush more frequently
Wrong website/namespaceCheck per-event websiteId, namespace, and source values

How is this guide?