Skip to main content

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')
caution

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'],
})
})
tip

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

Help us improve Swetrix

Was this page helpful to you?