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-middleware

Quick 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=true

API 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