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 websiteevent_type: "pageview" or "custom_event"event_name: For custom eventspathname,referrer,referrer_sourcevisitor_id,session_id: Privacy-focused identifiersuser_agent,browser,os,device_typecountry: Country-level only (no city tracking)metadata: JSON for custom propertiestimestamp: 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 websitesession_id: Unique session identifiervisitor_id: Visitor identifierentry_page,exit_pagepageviews,duration_seconds,is_bouncereferrer,referrer_sourcecountry,device_type,browser,osstarted_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 websitedate: 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.tslist-analytics-event.tsupdate-analytics-event.tsdelete-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 eventsPOST /admin/analytics-events- Create eventGET /admin/analytics-events/:id- Get eventPUT /admin/analytics-events/:id- Update eventDELETE /admin/analytics-events/:id- Delete event
Includes:
route.ts- Route handlersvalidators.ts- Zod schemashelpers.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 periodslimit: 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
5. Module Links (Optional)
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
- Traffic: Pageviews, Unique Visitors, Sessions
- Engagement: Bounce Rate, Avg Session Duration, Pages per Session
- Sources: Direct, Referrers, Search Engines, Social Media
- Content: Top Pages, Entry Pages, Exit Pages
- Technology: Browsers, OS, Device Types
- 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
- Indexes: All critical queries have proper indexes
- Aggregation: Daily stats pre-computed for fast reporting
- Minimal Response: Tracking endpoint returns minimal data
- Beacon API: Client uses sendBeacon for reliability
- Async Processing: Tracking doesn't block page load
🔐 Security
- No Authentication Required: Public tracking endpoint
- Rate Limiting: Should be added at infrastructure level
- Error Hiding: Errors not exposed to clients
- Input Validation: Zod schemas validate all inputs
- 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
- ✅ Fix TypeScript errors in tracking workflow
- ✅ Create integration tests
- ⬜ Create client-side tracking script (
public/analytics.js) - ⬜ Implement admin reporting APIs
- ⬜ Create background aggregation jobs
- ⬜ Build admin UI dashboard
- ⬜ Add GeoIP integration (optional)
- ⬜ Implement data retention policies
- ⬜ Add rate limiting
- ⬜ 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