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/nodeSetup
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
});| Option | Type | Description |
|---|---|---|
devMode | boolean | When true, all tracking logs are printed to the console |
disabled | boolean | When true, no data is sent to the server — useful for development |
apiURL | string | Custom API server URL (for self-hosted Swetrix) |
unique | boolean | When true, only unique events are saved |
profileId | string | Optional 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" };
});Hook approach (recommended)
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:
| Option | Type | Description |
|---|---|---|
pg | string | Page path (e.g. /home) |
lc | string | Visitor locale (e.g. en-US) |
ref | string | Referrer URL |
so | string | Traffic source (e.g. utm_source) |
me | string | Traffic medium (e.g. utm_medium) |
ca | string | Campaign (e.g. utm_campaign) |
unique | boolean | Only save unique visits |
meta | object | Custom 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_IDconst 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
@swetrix/nodesource code — full source and documentation for the server-side SDK.- Events API — API documentation for direct event submission.
- Fastify documentation — official Fastify docs.
Help us improve Swetrix
Was this page helpful to you?
