Swetrix
Integrations

Fastify

Integrate Swetrix with your Fastify application using the @swetrix/node server-side SDK to track page views, monitor errors, and capture custom events — all while staying privacy-friendly and GDPR-compliant.

Unlike client-side integrations that inject a tracking script into the browser, @swetrix/node sends analytics data directly from your server. This means tracking works even when visitors have JavaScript disabled or use ad blockers, and no third-party scripts are loaded in the browser.

Installation

npm install @swetrix/node

Setup

Initialise the Swetrix client with your Project ID:

import Fastify from "fastify";
import { Swetrix } from "@swetrix/node";

const fastify = Fastify({ logger: true });
const swetrix = new Swetrix("YOUR_PROJECT_ID");

Replace YOUR_PROJECT_ID with your actual Project ID from the Swetrix dashboard, otherwise tracking won't work.

The Swetrix constructor accepts an optional second argument — an options object:

const swetrix = new Swetrix("YOUR_PROJECT_ID", {
  devMode: false,
  disabled: process.env.NODE_ENV !== "production",
  apiURL: undefined, // only needed for self-hosted Swetrix
});
OptionTypeDescription
devModebooleanWhen true, all tracking logs are printed to the console
disabledbooleanWhen true, no data is sent to the server — useful for development
apiURLstringCustom API server URL (for self-hosted Swetrix)
uniquebooleanWhen true, only unique events are saved
profileIdstringOptional profile ID for long-term user tracking

Tracking pageviews

To track pageviews you need to pass the visitor's IP address and User-Agent header. Without these, unique visitor counting and live visitor tracking won't work. See the Events API docs for details.

Basic example

fastify.get("/", async (request, reply) => {
  const ip = request.ip;
  const userAgent = request.headers["user-agent"];

  await swetrix.trackPageView(ip, userAgent, {
    pg: "/",
    lc: request.headers["accept-language"]?.split(",")[0],
    ref: request.headers["referer"],
  });

  return { message: "Hello World" };
});

Fastify's lifecycle hooks let you track pageviews automatically on every request without repeating code in each route handler. The onRequest hook runs before the route handler:

fastify.addHook("onRequest", async (request, reply) => {
  const ip = request.ip;
  const userAgent = request.headers["user-agent"];

  // Fire-and-forget — don't block the response
  swetrix.trackPageView(ip, userAgent, {
    pg: request.url,
    lc: request.headers["accept-language"]?.split(",")[0],
    ref: request.headers["referer"],
  });
});

By not awaiting trackPageView, analytics calls happen in the background without adding latency to your responses. This is the recommended pattern for production.

If your Fastify app runs behind a reverse proxy (e.g. Nginx, Cloudflare, or a load balancer), set the trustProxy option so request.ip returns the real client IP:

const fastify = Fastify({
  trustProxy: true,
});

Plugin approach

For a more Fastify-idiomatic solution, you can wrap the tracking logic in a plugin:

import fp from "fastify-plugin";
import { Swetrix } from "@swetrix/node";

export default fp(async function swetrixPlugin(fastify, opts) {
  const swetrix = new Swetrix(opts.projectId, {
    disabled: opts.disabled,
    devMode: opts.devMode,
  });

  fastify.decorate("swetrix", swetrix);

  fastify.addHook("onRequest", async (request) => {
    swetrix.trackPageView(request.ip, request.headers["user-agent"], {
      pg: request.url,
      lc: request.headers["accept-language"]?.split(",")[0],
      ref: request.headers["referer"],
    });
  });
});

Register the plugin in your app:

import swetrixPlugin from "./plugins/swetrix.js";

fastify.register(swetrixPlugin, {
  projectId: process.env.SWETRIX_PROJECT_ID,
  disabled: process.env.NODE_ENV !== "production",
});

After registering, you can access the Swetrix instance from any route via fastify.swetrix or request.server.swetrix.

Pageview options

The third argument to trackPageView is an options object:

OptionTypeDescription
pgstringPage path (e.g. /home)
lcstringVisitor locale (e.g. en-US)
refstringReferrer URL
sostringTraffic source (e.g. utm_source)
mestringTraffic medium (e.g. utm_medium)
castringCampaign (e.g. utm_campaign)
uniquebooleanOnly save unique visits
metaobjectCustom metadata key-value pairs

Tracking custom events

Track specific actions — API calls, form submissions, purchases, etc.:

fastify.post("/api/subscribe", async (request, reply) => {
  swetrix.track(request.ip, request.headers["user-agent"], {
    ev: "NEWSLETTER_SUBSCRIBE",
    page: "/subscribe",
    meta: {
      plan: request.body.plan,
    },
  });

  return { success: true };
});

Event naming rules

Event names must:

  • Contain any characters (including spaces, unicode, etc.)
  • Be no longer than 256 characters

We recommend UPPER_SNAKE_CASE for consistency (e.g. NEWSLETTER_SUBSCRIBE, CHECKOUT_COMPLETED).

Error tracking

Use Fastify's setErrorHandler to report errors to Swetrix automatically:

fastify.setErrorHandler((error, request, reply) => {
  swetrix.trackError(request.ip, request.headers["user-agent"], {
    name: error.name || "Error",
    message: error.message,
    stackTrace: error.stack,
    pg: request.url,
  });

  reply.status(error.statusCode || 500).send({
    error: "Internal Server Error",
  });
});

You can also track errors from specific routes:

fastify.get("/api/data", async (request, reply) => {
  try {
    const data = await fetchExternalData();
    return data;
  } catch (err) {
    swetrix.trackError(request.ip, request.headers["user-agent"], {
      name: err.name,
      message: err.message,
      stackTrace: err.stack,
      pg: request.url,
    });

    reply.status(500).send({ error: "Failed to fetch data" });
  }
});

Heartbeat events

Heartbeat events let Swetrix know a visitor's session is still active, powering the Live Visitors counter in your dashboard. They're most useful in long-lived connections like WebSocket sessions:

import websocket from "@fastify/websocket";

fastify.register(websocket);

const HEARTBEAT_INTERVAL = 30_000;

fastify.get("/ws", { websocket: true }, (socket, request) => {
  const ip = request.ip;
  const userAgent = request.headers["user-agent"];

  const interval = setInterval(() => {
    swetrix.heartbeat(ip, userAgent);
  }, HEARTBEAT_INTERVAL);

  socket.on("close", () => clearInterval(interval));
});

Feature flags

Evaluate feature flags server-side based on visitor context:

fastify.get("/dashboard", async (request, reply) => {
  const showNewDashboard = await swetrix.getFeatureFlag(
    "new-dashboard",
    request.ip,
    request.headers["user-agent"],
    { profileId: request.user?.id },
    false, // default value
  );

  if (showNewDashboard) {
    return reply.view("dashboard-v2");
  }

  return reply.view("dashboard");
});

You can also fetch all flags at once:

const flags = await swetrix.getFeatureFlags(request.ip, request.headers["user-agent"]);

if (flags["beta-feature"]) {
  // Enable beta feature
}

Feature flags are cached for 5 minutes. To force a refresh:

swetrix.clearFeatureFlagsCache();

A/B testing (experiments)

Run server-side A/B tests and get the assigned variant for each visitor:

fastify.get("/pricing", async (request, reply) => {
  const variant = await swetrix.getExperiment(
    "pricing-experiment-id",
    request.ip,
    request.headers["user-agent"],
    { profileId: request.user?.id },
    null, // default variant
  );

  return {
    showAnnualFirst: variant === "annual-first",
  };
});

Revenue attribution

Use profile and session IDs to connect analytics with your payment provider (e.g. Paddle, Stripe):

fastify.get("/checkout", async (request, reply) => {
  const [profileId, sessionId] = await Promise.all([
    swetrix.getProfileId(request.ip, request.headers["user-agent"]),
    swetrix.getSessionId(request.ip, request.headers["user-agent"]),
  ]);

  return {
    checkoutConfig: {
      customData: {
        swetrix_profile_id: profileId,
        swetrix_session_id: sessionId,
      },
    },
  };
});

Disable tracking in development

Use the disabled option to prevent tracking during development:

const swetrix = new Swetrix("YOUR_PROJECT_ID", {
  disabled: process.env.NODE_ENV !== "production",
});

Or use devMode to log tracking calls to the console without disabling them:

const swetrix = new Swetrix("YOUR_PROJECT_ID", {
  devMode: process.env.NODE_ENV === "development",
});

Using environment variables for your Project ID

Rather than hardcoding the Project ID, store it in an environment variable:

SWETRIX_PROJECT_ID=YOUR_PROJECT_ID
const swetrix = new Swetrix(process.env.SWETRIX_PROJECT_ID);

With Fastify's @fastify/env plugin:

import fastifyEnv from "@fastify/env";

const schema = {
  type: "object",
  required: ["SWETRIX_PROJECT_ID"],
  properties: {
    SWETRIX_PROJECT_ID: { type: "string" },
  },
};

fastify.register(fastifyEnv, { schema, dotenv: true });

Complete example

Here's a full Fastify application with Swetrix analytics:

import Fastify from "fastify";
import { Swetrix } from "@swetrix/node";

const fastify = Fastify({
  logger: true,
  trustProxy: true,
});

const swetrix = new Swetrix(process.env.SWETRIX_PROJECT_ID, {
  disabled: process.env.NODE_ENV !== "production",
});

// Track pageviews on all requests
fastify.addHook("onRequest", async (request) => {
  swetrix.trackPageView(request.ip, request.headers["user-agent"], {
    pg: request.url,
    lc: request.headers["accept-language"]?.split(",")[0],
    ref: request.headers["referer"],
  });
});

fastify.get("/", async () => {
  return { message: "Hello World" };
});

fastify.post("/api/contact", async (request) => {
  swetrix.track(request.ip, request.headers["user-agent"], {
    ev: "CONTACT_FORM_SUBMITTED",
    page: "/contact",
  });

  return { success: true };
});

// Error tracking
fastify.setErrorHandler((error, request, reply) => {
  swetrix.trackError(request.ip, request.headers["user-agent"], {
    name: error.name || "Error",
    message: error.message,
    stackTrace: error.stack,
    pg: request.url,
  });

  reply.status(500).send({ error: "Internal Server Error" });
});

fastify.listen({ port: 3000 }, (err) => {
  if (err) {
    fastify.log.error(err);
    process.exit(1);
  }
});

Check your installation

Deploy your application (or temporarily enable devMode) and make a few requests. Within a minute you should see new pageviews appearing in your Swetrix dashboard.

Self-hosted Swetrix

If you're self-hosting the Swetrix API, point the apiURL option to your instance:

const swetrix = new Swetrix("YOUR_PROJECT_ID", {
  apiURL: "https://your-swetrix-instance.com/log",
});

Further reading

On this page