Hero
Back to all guides

How to Track Events with Node.js

Add server-side analytics to your Node.js application. Track events, identify users, and analyze behavior with OpenPanel's JavaScript SDK.

OpenPanel Team

12/14/2025

Beginner
7 min

Introduction

Server-side analytics gives you complete control over what data you track and ensures events are never blocked by ad blockers. OpenPanel's JavaScript SDK works perfectly in Node.js environments, allowing you to track events from your backend, API routes, and background jobs.

OpenPanel is an open-source alternative to Mixpanel and Amplitude, giving you powerful server-side analytics without compromising user privacy.

Prerequisites

  • Node.js project set up
  • OpenPanel account (sign up free)
  • Your Client ID and Client Secret from the OpenPanel dashboard

Important: Server-side tracking requires a clientSecret for authentication.

Step 1: Install the SDK

Install the OpenPanel JavaScript SDK:

npm install @openpanel/sdk

Or with pnpm:

pnpm install @openpanel/sdk

Step 2: Initialize OpenPanel

Create an OpenPanel instance with your credentials:

lib/op.js
import { OpenPanel } from '@openpanel/sdk';

export const op = new OpenPanel({
  clientId: 'YOUR_CLIENT_ID',
  clientSecret: 'YOUR_CLIENT_SECRET', // Required for server-side
});

Security Note: Never expose your clientSecret in client-side code. Use environment variables in production.

Using environment variables

lib/op.js
import { OpenPanel } from '@openpanel/sdk';

export const op = new OpenPanel({
  clientId: process.env.OPENPANEL_CLIENT_ID,
  clientSecret: process.env.OPENPANEL_CLIENT_SECRET,
});
.env
OPENPANEL_CLIENT_ID=your-client-id
OPENPANEL_CLIENT_SECRET=your-client-secret

Step 3: Track events

Track events throughout your Node.js application:

routes/api/signup.js
import { op } from '@/lib/op';

export async function POST(request) {
  const { email, name } = await request.json();

  // Track signup event
  op.track('user_signed_up', {
    email,
    name,
    source: 'website',
  });

  // Your signup logic
  const user = await createUser({ email, name });

  return Response.json({ success: true, user });
}

Track API requests

middleware/analytics.js
import { op } from '@/lib/op';

export function analyticsMiddleware(req, res, next) {
  // Track API request
  op.track('api_request', {
    method: req.method,
    path: req.path,
    status_code: res.statusCode,
  });

  next();
}

Track background jobs

jobs/send-email.js
import { op } from '@/lib/op';

export async function sendEmailJob(userId, emailData) {
  try {
    await sendEmail(emailData);
    
    op.track('email_sent', {
      user_id: userId,
      email_type: emailData.type,
      success: true,
    });
  } catch (error) {
    op.track('email_failed', {
      user_id: userId,
      email_type: emailData.type,
      error: error.message,
    });
  }
}

Step 4: Identify users

To identify users and track their behavior:

routes/api/login.js
import { op } from '@/lib/op';

export async function POST(request) {
  const { email, password } = await request.json();
  
  const user = await authenticateUser(email, password);

  // Identify the user
  op.identify({
    profileId: user.id,
    firstName: user.firstName,
    lastName: user.lastName,
    email: user.email,
    properties: {
      plan: user.plan,
      signupDate: user.createdAt,
    },
  });

  // Track login event
  op.track('user_logged_in', {
    user_id: user.id,
    method: 'email',
  });

  return Response.json({ success: true, user });
}

Track user actions

routes/api/purchase.js
import { op } from '@/lib/op';

export async function POST(request) {
  const { userId, productId, amount } = await request.json();

  // Track purchase event
  op.track('purchase_completed', {
    user_id: userId,
    product_id: productId,
    amount,
    currency: 'USD',
  }, {
    profileId: userId, // Associate event with user
  });

  // Your purchase logic
  await processPurchase(userId, productId, amount);

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

Verify your setup

  1. Make requests to your API endpoints that trigger events
  2. Run background jobs that track events
  3. Open your OpenPanel dashboard
  4. Check the Real-time view to see events coming in

Not seeing events?

  • Check server logs for errors
  • Verify your Client ID and Client Secret are correct
  • Ensure clientSecret is provided (required for server-side)
  • Check network requests in your server logs

Common Patterns

Track webhook events

routes/api/webhook.js
import { op } from '@/lib/op';

export async function POST(request) {
  const payload = await request.json();

  op.track('webhook_received', {
    source: payload.source,
    event_type: payload.type,
    timestamp: new Date().toISOString(),
  });

  // Process webhook
  await processWebhook(payload);

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

Set global properties

lib/op.js
import { OpenPanel } from '@openpanel/sdk';

export const op = new OpenPanel({
  clientId: process.env.OPENPANEL_CLIENT_ID,
  clientSecret: process.env.OPENPANEL_CLIENT_SECRET,
  globalProperties: {
    app_version: process.env.APP_VERSION,
    environment: process.env.NODE_ENV,
    server_region: process.env.SERVER_REGION,
  },
});

Track errors

middleware/error-handler.js
import { op } from '@/lib/op';

export function errorHandler(err, req, res, next) {
  // Track error event
  op.track('error_occurred', {
    error_message: err.message,
    error_stack: err.stack,
    path: req.path,
    method: req.method,
  });

  // Your error handling logic
  res.status(500).json({ error: 'Internal server error' });
}

Increment user properties

import { op } from '@/lib/op';

// Increment login count
op.increment({
  profileId: user.id,
  property: 'login_count',
  value: 1,
});

Serverless & Vercel

If you're using serverless functions (like Vercel), use waitUntil to ensure events are tracked before the function completes:

api/track.js
import { waitUntil } from '@vercel/functions';
import { op } from '@/lib/op';

export default async function handler(req, res) {
  // Returns response immediately while keeping function alive
  waitUntil(op.track('important_event', { foo: 'bar' }));
  
  return res.status(200).json({ success: true });
}

Next Steps

FAQ

Why do I need a clientSecret for server-side tracking?

Server-side tracking requires authentication since we can't use CORS headers. The clientSecret ensures your events are properly authenticated and prevents unauthorized tracking.

Can I track events asynchronously?

Yes! The OpenPanel SDK tracks events asynchronously by default. Events are queued and sent in the background, so they won't block your application.

Is OpenPanel GDPR compliant?

Yes! OpenPanel is designed with privacy in mind. Server-side tracking gives you complete control over what data you collect. Check out our cookieless analytics guide for more information.

Loved by builders everywhere

From indie hackers to global teams, OpenPanel helps people understand their users effortlessly.

  • Steven Tey
    Steven Tey
    @steventey

    Open-source Mixpanel alternative just dropped → http://git.new/openpanel

    It combines the power of Mixpanel + the ease of use of @PlausibleHQ into a fully open-source product.

    Built by @CarlLindesvard and it’s already tracking 750K+ events 🤩

  • Pontus Abrahamsson - oss/acc
    Pontus Abrahamsson - oss/acc
    @pontusab

    Thanks, OpenPanel is a beast, love it!

  • Piotr Kulpinski
    Piotr Kulpinski
    @piotrkulpinski

    The Overview tab in OpenPanel is great. It has everything I need from my analytics: the stats, the graph, traffic sources, locations, devices, etc.

    The UI is beautiful ✨ Clean, modern look, very pleasing to the eye.

  • greg hodson 🍜
    greg hodson 🍜
    @h0dson

    i second this, openpanel is killing it

  • Jacob 🍀 Build in Public
    Jacob 🍀 Build in Public
    @javayhuwx

    🤯 wow, it's amazing! Just integrate @OpenPanelDev into http://indiehackers.site last night, and now I can see visitors coming from all round the world.

    OpenPanel has a more beautiful UI and much more powerful features when compared to Umami.

    #buildinpublic #indiehackers

  • Lee
    Lee
    @DutchEngIishman

    Day two of marketing.

    I like this upward trend..

    P.S. website went live on Sunday

    P.P.S. Openpanel by @CarlLindesvard is awesome.

Ready to understand your users better?
Start tracking in minutes

Join thousands of companies using OpenPanel. Free 30-day trial, no credit card required. Self-host for free or use our cloud.

Get started now