Express Middleware
Express.js middleware for automatic analytics tracking
Express Middleware
The @entrolytics/express-middleware package provides Express.js middleware for automatic analytics tracking, request monitoring, and user identification.
Installation
pnpm add @entrolytics/express-middlewareQuick Start
Basic Setup
import express from 'express'
import { entrolyticsMiddleware } from '@entrolytics/express-middleware'
const app = express()
// Add Entrolytics middleware
app.use(
entrolyticsMiddleware({
websiteId: 'your-website-id',
apiKey: 'your-api-key'
})
)
// Your routes
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(3000)With Environment Variables
import express from 'express'
import { entrolyticsMiddleware } from '@entrolytics/express-middleware'
import dotenv from 'dotenv'
dotenv.config()
const app = express()
app.use(
entrolyticsMiddleware({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY!,
host: process.env.ENTROLYTICS_HOST || 'https://entrolytics.dev'
})
)Configuration
Middleware Options
interface ExpressMiddlewareConfig {
/** Website ID (required) */
websiteId: string
/** API key for authentication */
apiKey?: string
/** Custom API host */
host?: string
/** Auto-track requests (default: true) */
autoTrack?: boolean
/** Track errors and exceptions (default: true) */
trackErrors?: boolean
/** Track response times (default: true) */
trackPerformance?: boolean
/** Exclude specific paths from tracking */
excludePaths?: string[]
/** Include specific headers in events */
includeHeaders?: string[]
/** Enable debug logging */
debug?: boolean
/** Async tracking (default: true) */
async?: boolean
/** Custom request ID header */
requestIdHeader?: string
}Environment Variables
# .env
ENTROLYTICS_WEBSITE_ID=your-website-id
ENTROLYTICS_API_KEY=your-api-key
ENTROLYTICS_HOST=https://entrolytics.dev
ENTROLYTICS_DEBUG=false
ENTROLYTICS_ASYNC=trueAPI Reference
Middleware Request API
The middleware adds an entrolytics object to the Express request:
interface EntrolyticsRequest {
/** Track a custom event */
track(event: string, properties?: Record<string, any>): Promise<void>
/** Track multiple events in batch */
trackBatch(
events: Array<{ event: string; properties?: Record<string, any> }>
): Promise<void>
/** Identify a user */
identify(userId: string, traits?: Record<string, any>): Promise<void>
/** Set user properties */
setUserProperties(properties: Record<string, any>): Promise<void>
/** Track a page view */
page(url?: string, properties?: Record<string, any>): Promise<void>
/** Get current configuration */
getConfig(): ExpressMiddlewareConfig
/** Get session ID */
getSessionId(): string
/** Get user ID */
getUserId(): string | undefined
}Usage Examples
app.get('/api/users/:id', async (req, res) => {
// Track custom event
await req.entrolytics.track('user_profile_view', {
userId: req.params.id,
source: 'api'
})
// Identify user
await req.entrolytics.identify(req.params.id, {
email: 'user@example.com',
role: 'user'
})
// Track batch events
await req.entrolytics.trackBatch([
{ event: 'api_request', properties: { endpoint: '/api/users' } },
{ event: 'database_query', properties: { table: 'users' } }
])
const user = await getUserById(req.params.id)
res.json(user)
})Advanced Usage
Custom Middleware Logic
import express from 'express'
import { entrolyticsMiddleware } from '@entrolytics/express-middleware'
const app = express()
// Base middleware
app.use(
entrolyticsMiddleware({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY!,
excludePaths: ['/health', '/metrics'],
includeHeaders: ['user-agent', 'x-forwarded-for']
})
)
// Custom tracking middleware
app.use((req, res, next) => {
// Track authenticated users
if (req.user) {
req.entrolytics.identify(req.user.id, {
email: req.user.email,
role: req.user.role,
plan: req.user.plan
})
}
// Track API version
const apiVersion = req.headers['api-version']
if (apiVersion) {
req.entrolytics.track('api_request', {
version: apiVersion,
method: req.method,
path: req.path
})
}
next()
})Error Tracking
app.use(
(
error: Error,
req: express.Request,
res: express.Response,
next: express.NextFunction
) => {
// Track error details
req.entrolytics.track('error', {
message: error.message,
stack: error.stack,
url: req.url,
method: req.method,
userAgent: req.headers['user-agent'],
ip: req.ip
})
// Track error type
req.entrolytics.track('server_error', {
errorType: error.constructor.name,
statusCode: res.statusCode || 500
})
next(error)
}
)Performance Monitoring
// Performance tracking middleware
app.use((req, res, next) => {
const start = Date.now()
res.on('finish', async () => {
const duration = Date.now() - start
await req.entrolytics.track('request_performance', {
method: req.method,
path: req.path,
statusCode: res.statusCode,
duration,
contentLength: res.get('content-length'),
cacheHit: res.get('x-cache-status') === 'hit'
})
})
next()
})Session Management
import session from 'express-session'
app.use(
session({
secret: 'your-secret-key',
resave: false,
saveUninitialized: false
})
)
app.use((req, res, next) => {
// Track session events
if (req.session && !req.session.analyticsTracked) {
req.entrolytics.track('session_start', {
sessionId: req.sessionID,
isNewSession: !req.session.userId
})
req.session.analyticsTracked = true
}
// Track session duration
if (req.session && req.session.createdAt) {
const sessionDuration = Date.now() - req.session.createdAt
req.entrolytics.track('session_duration', {
duration: sessionDuration,
sessionId: req.sessionID
})
}
next()
})Conditional Tracking
app.use((req, res, next) => {
// Only track production traffic
if (process.env.NODE_ENV === 'production') {
req.entrolytics.track('production_request', {
path: req.path,
method: req.method
})
}
// Exclude bot traffic
const userAgent = req.headers['user-agent'] || ''
if (userAgent.includes('bot') || userAgent.includes('crawler')) {
req.entrolytics.track('bot_request', {
userAgent,
path: req.path
})
return next()
}
// Track authenticated vs anonymous users
if (req.user) {
req.entrolytics.track('authenticated_request')
} else {
req.entrolytics.track('anonymous_request')
}
next()
})Authentication Integration
JWT Authentication
import jwt from 'jsonwebtoken'
app.use((req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (token) {
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET!) as any
req.user = decoded
// Identify user in analytics
req.entrolytics.identify(decoded.sub, {
email: decoded.email,
role: decoded.role,
permissions: decoded.permissions
})
req.entrolytics.track('jwt_authenticated', {
userId: decoded.sub,
issuer: decoded.iss
})
} catch (error) {
req.entrolytics.track('jwt_auth_failed', {
error: error.message,
token: token.substring(0, 10) + '...'
})
}
}
next()
})Passport.js Integration
import passport from 'passport'
app.use(passport.initialize())
app.use(passport.session())
app.post('/login', passport.authenticate('local'), (req, res) => {
// Track successful login
req.entrolytics.track('login_success', {
userId: req.user.id,
provider: 'local'
})
// Identify user
req.entrolytics.identify(req.user.id, {
email: req.user.email,
loginCount: req.user.loginCount + 1
})
res.json({ success: true })
})
app.post('/logout', (req, res) => {
const userId = req.user?.id
req.logout((err) => {
if (!err) {
// Track logout
req.entrolytics.track('logout', {
userId,
sessionDuration: Date.now() - req.session.createdAt
})
}
res.json({ success: true })
})
})Testing
Unit Tests
import request from 'supertest'
import express from 'express'
import { entrolyticsMiddleware } from '@entrolytics/express-middleware'
// Mock the analytics client
jest.mock('@entrolytics/express-middleware', () => ({
entrolyticsMiddleware: jest.fn(() => (req: any, res: any, next: any) => {
req.entrolytics = {
track: jest.fn(),
identify: jest.fn(),
page: jest.fn()
}
next()
})
}))
describe('Analytics Middleware', () => {
let app: express.Application
beforeEach(() => {
app = express()
app.use(
entrolyticsMiddleware({
websiteId: 'test-website-id',
apiKey: 'test-api-key'
})
)
app.get('/test', (req, res) => {
req.entrolytics.track('test_event', { property: 'value' })
res.json({ success: true })
})
})
it('should add entrolytics to request', async () => {
const response = await request(app).get('/test')
expect(response.status).toBe(200)
expect(response.body.success).toBe(true)
})
it('should track events', async () => {
await request(app).get('/test')
// Verify track was called
expect(req.entrolytics.track).toHaveBeenCalledWith('test_event', {
property: 'value'
})
})
})Integration Tests
import request from 'supertest'
import express from 'express'
import { entrolyticsMiddleware } from '@entrolytics/express-middleware'
describe('Analytics Integration', () => {
let app: express.Application
beforeAll(() => {
app = express()
app.use(
entrolyticsMiddleware({
websiteId: process.env.TEST_WEBSITE_ID!,
apiKey: process.env.TEST_API_KEY!,
debug: true
})
)
app.get('/users/:id', async (req, res) => {
await req.entrolytics.track('user_view', {
userId: req.params.id
})
res.json({ userId: req.params.id })
})
})
it('should track user views', async () => {
const response = await request(app).get('/users/123')
expect(response.status).toBe(200)
expect(response.body.userId).toBe('123')
// Check analytics were sent (would need to verify with actual API)
})
})Performance Optimization
Async Tracking
app.use(
entrolyticsMiddleware({
websiteId: process.env.ENTROLYTICS_WEBSITE_ID!,
apiKey: process.env.ENTROLYTICS_API_KEY!,
async: true // Don't block request processing
})
)Batching
// Batch events every 10 seconds
const eventBatch: Array<{ event: string; properties?: Record<string, any> }> =
[]
setInterval(async () => {
if (eventBatch.length > 0) {
await req.entrolytics.trackBatch([...eventBatch])
eventBatch.length = 0
}
}, 10000)
app.use((req, res, next) => {
// Add to batch instead of immediate tracking
eventBatch.push({
event: 'request',
properties: { path: req.path, method: req.method }
})
next()
})Conditional Tracking
app.use((req, res, next) => {
// Only sample 10% of requests in high traffic
if (Math.random() > 0.9) {
req.entrolytics.track('sampled_request', {
path: req.path,
method: req.method
})
}
next()
})Troubleshooting
Best Practices
Migration Guide
From Manual Tracking
// Before
app.post('/signup', (req, res) => {
// Manual analytics call
analytics.track('signup', req.body)
// Handle signup
handleSignup(req.body)
})
// After with middleware
app.post('/signup', async (req, res) => {
// Automatic request tracking + custom event
await req.entrolytics.track('signup', req.body)
// Handle signup
await handleSignup(req.body)
})From Other Analytics
// Google Analytics
app.get('/page', (req, res) => {
ga('send', 'pageview', '/page')
res.render('page')
})
// Entrolytics with middleware
app.get('/page', async (req, res) => {
await req.entrolytics.page('/page', {
title: 'Page Title',
category: 'marketing'
})
res.render('page')
})Express.js middleware for Entrolytics - First-party growth analytics for the edge