Skip to main content

Analytics System Implementation

Overview

A lightweight, privacy-focused analytics system similar to Plausible, integrated with the website module. Tracks essential metrics without cookies or personal data collection.

✅ Completed Implementation

1. Data Models

AnalyticsEvent (src/modules/analytics/models/analytics-event.ts)

Core table for storing all tracking events:

  • website_id: Reference to website
  • event_type: "pageview" or "custom_event"
  • event_name: For custom events
  • pathname, referrer, referrer_source
  • visitor_id, session_id: Privacy-focused identifiers
  • user_agent, browser, os, device_type
  • country: Country-level only (no city tracking)
  • metadata: JSON for custom properties
  • timestamp: Event time

Indexes:

  • (website_id, timestamp)
  • (website_id, pathname, timestamp)
  • (session_id)
  • (visitor_id)
  • (website_id, event_type, timestamp)

AnalyticsSession (src/modules/analytics/models/analytics-session.ts)

Aggregate session-level data:

  • website_id: Reference to website
  • session_id: Unique session identifier
  • visitor_id: Visitor identifier
  • entry_page, exit_page
  • pageviews, duration_seconds, is_bounce
  • referrer, referrer_source
  • country, device_type, browser, os
  • started_at, ended_at, last_activity_at

Indexes:

  • (website_id, started_at)
  • (session_id) - unique
  • (visitor_id)
  • (website_id, is_bounce)

AnalyticsDailyStats (src/modules/analytics/models/analytics-daily-stats.ts)

Pre-aggregated daily statistics for fast reporting:

  • website_id: Reference to website
  • date: Date for stats
  • Traffic metrics: pageviews, unique_visitors, sessions, bounce_rate, avg_session_duration
  • Top content: top_pages, top_referrers, top_countries (JSON)
  • Device breakdown: desktop_visitors, mobile_visitors, tablet_visitors
  • Browser/OS stats: browser_stats, os_stats (JSON)

Indexes:

  • (website_id, date) - unique
  • (date)

2. Module Structure

Analytics Module (src/modules/analytics/)

analytics/
├── index.ts (module definition with ANALYTICS_MODULE constant)
├── service.ts (AnalyticsService with all three models)
└── models/
├── analytics-event.ts
├── analytics-session.ts
└── analytics-daily-stats.ts

Module Constant:

export const ANALYTICS_MODULE = "analytics";

3. Workflows

Generated CRUD Workflows (src/workflows/analytics/)

  • create-analytics-event.ts
  • list-analytics-event.ts
  • update-analytics-event.ts
  • delete-analytics-event.ts

Custom Tracking Workflow (src/workflows/analytics/track-analytics-event.ts)

  • Step 1: createAnalyticsEventStep - Creates event with parsed user agent data
  • Step 2: updateSessionStep - Updates or creates session
  • Features:
    • Automatic user agent parsing (browser, OS, device type)
    • Referrer source extraction (Google, Facebook, direct, etc.)
    • Session management (creates new or updates existing)
    • Bounce rate tracking
    • Proper rollback compensation

4. API Endpoints

Admin API (src/api/admin/analytics-events/)

Generated by script with full CRUD operations:

  • GET /admin/analytics-events - List events
  • POST /admin/analytics-events - Create event
  • GET /admin/analytics-events/:id - Get event
  • PUT /admin/analytics-events/:id - Update event
  • DELETE /admin/analytics-events/:id - Delete event

Includes:

  • route.ts - Route handlers
  • validators.ts - Zod schemas
  • helpers.ts - Helper functions

Public Tracking API (src/api/web/analytics/track/route.ts)

Endpoint: POST /web/analytics/track

Authentication: None required (public endpoint)

Request Body:

{
"website_id": "string",
"event_type": "pageview" | "custom_event",
"event_name": "string (optional)",
"pathname": "string",
"referrer": "string (optional)",
"visitor_id": "string",
"session_id": "string",
"metadata": {} (optional)
}

Response:

{
"success": true,
"message": "Event tracked"
}

Features:

  • Extracts user agent and IP from request headers
  • Calls trackAnalyticsEventWorkflow
  • Always returns 200 (even on errors for security)
  • Minimal response for performance

5. Privacy Features

No Cookies: Uses localStorage/sessionStorage only ✅ No PII: Visitor IDs are hashed fingerprints ✅ Country-level Only: No city/region tracking ✅ No Cross-site Tracking: Each website isolated ✅ GDPR Compliant: No personal data collection

🚧 Pending Implementation

1. Client-Side Tracking Script

Location: public/analytics.js

Features Needed:

  • Visitor ID generation (localStorage)
  • Session ID generation (sessionStorage)
  • Automatic pageview tracking
  • SPA navigation detection
  • Custom event tracking API
  • Beacon API for reliability

Usage:

<script 
src="/analytics.js"
data-website-id="website_123"
defer
></script>

Custom Events:

window.trackEvent('button_click', { button: 'signup' });

2. Admin Reporting APIs

Endpoints Needed:

GET /admin/websites/:id/analytics/overview
GET /admin/websites/:id/analytics/realtime
GET /admin/websites/:id/analytics/pages
GET /admin/websites/:id/analytics/referrers
GET /admin/websites/:id/analytics/locations
GET /admin/websites/:id/analytics/devices
GET /admin/websites/:id/analytics/timeseries

Query Parameters:

  • period: "7d" | "30d" | "90d" | "custom"
  • start_date, end_date: For custom periods
  • limit: Number of results

3. Background Jobs

Daily Aggregation Job

  • Runs at midnight
  • Aggregates previous day's data into analytics_daily_stats
  • Calculates: pageviews, unique visitors, sessions, bounce rate, avg duration
  • Generates: top pages, top referrers, top countries, device/browser stats

Session Cleanup Job

  • Runs hourly
  • Marks sessions as ended if no activity for 30+ minutes
  • Calculates session duration

4. Admin UI Components

Dashboard Location: src/admin/routes/websites/[id]/analytics/page.tsx

Components Needed:

  • Overview cards (visitors, pageviews, bounce rate, duration)
  • Time series chart (visitors/pageviews over time)
  • Top pages table
  • Referrer sources table
  • Geographic map/list
  • Device breakdown pie chart
  • Realtime visitor count

If you want to query analytics with website data in a single query, create a module link:

Link File: src/links/website-analytics-link.ts

import { defineLink } from "@medusajs/framework/utils";
import WebsiteModule from "../modules/website";
import AnalyticsModule from "../modules/analytics";

export default defineLink(
WebsiteModule.linkable.website,
AnalyticsModule.linkable.analyticsEvent
);

📊 Key Metrics Tracked

  1. Traffic: Pageviews, Unique Visitors, Sessions
  2. Engagement: Bounce Rate, Avg Session Duration, Pages per Session
  3. Sources: Direct, Referrers, Search Engines, Social Media
  4. Content: Top Pages, Entry Pages, Exit Pages
  5. Technology: Browsers, OS, Device Types
  6. Geography: Countries (privacy-focused)

🔧 Configuration

Module Registration

Already added to medusa-config.ts and medusa-config.prod.ts:

{
resolve: "./src/modules/analytics",
}

Environment Variables (Optional)

# GeoIP Service (if you want country detection)
GEOIP_API_KEY=your_key_here

# Data Retention (days)
ANALYTICS_RETENTION_DAYS=90

🚀 Usage Examples

Track Pageview (Client-side)

// Automatic with script
// Or manual:
fetch('/web/analytics/track', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
website_id: 'website_123',
event_type: 'pageview',
pathname: window.location.pathname,
referrer: document.referrer,
visitor_id: getVisitorId(),
session_id: getSessionId()
})
});

Track Custom Event

window.trackEvent('signup_completed', {
plan: 'premium',
source: 'homepage'
});

Query Analytics (Admin API)

// Get overview stats
const response = await fetch(
'/admin/websites/website_123/analytics/overview?period=7d'
);

// Get top pages
const pages = await fetch(
'/admin/websites/website_123/analytics/pages?period=30d&limit=10'
);

📈 Performance Considerations

  1. Indexes: All critical queries have proper indexes
  2. Aggregation: Daily stats pre-computed for fast reporting
  3. Minimal Response: Tracking endpoint returns minimal data
  4. Beacon API: Client uses sendBeacon for reliability
  5. Async Processing: Tracking doesn't block page load

🔐 Security

  1. No Authentication Required: Public tracking endpoint
  2. Rate Limiting: Should be added at infrastructure level
  3. Error Hiding: Errors not exposed to clients
  4. Input Validation: Zod schemas validate all inputs
  5. No Sensitive Data: Only aggregate, anonymous data stored

🧪 Testing

Integration Tests

Comprehensive end-to-end tests are available:

# Run all analytics tests
npm run test:integration -- analytics

# Run specific test file
npm run test:integration -- track-analytics-event.spec.ts

# Run with watch mode
npm run test:integration:watch -- analytics

Test Coverage:

  • ✅ Basic pageview tracking
  • ✅ Event creation in database
  • ✅ Session creation and updates
  • ✅ Custom event tracking
  • ✅ User agent parsing (browser, OS, device)
  • ✅ Referrer source extraction
  • ✅ Concurrent request handling
  • ✅ Admin API filtering

Location: integration-tests/http/analytics/

See Analytics Test README for details.

📝 Next Steps

  1. ✅ Fix TypeScript errors in tracking workflow
  2. ✅ Create integration tests
  3. ⬜ Create client-side tracking script (public/analytics.js)
  4. ⬜ Implement admin reporting APIs
  5. ⬜ Create background aggregation jobs
  6. ⬜ Build admin UI dashboard
  7. ⬜ Add GeoIP integration (optional)
  8. ⬜ Implement data retention policies
  9. ⬜ Add rate limiting
  10. ⬜ Performance testing

🎯 Benefits Over Third-Party Analytics

  • Privacy-First: No cookies, no PII, GDPR compliant
  • Self-Hosted: Full data ownership
  • Fast: No external requests, minimal overhead
  • Integrated: Works seamlessly with website module
  • Customizable: Full control over metrics and reporting
  • Cost-Effective: No per-event pricing

📚 References

  • MedusaJS v2 Documentation
  • Plausible Analytics (inspiration)
  • GDPR Compliance Guidelines
  • Web Analytics Best Practices