Skip to content

A comprehensive Node.js/Express analytics server for tracking website views with MySQL storage, featuring auto-database creation, advanced tracking, and rich analytics.

License

Notifications You must be signed in to change notification settings

harshankur/viewcounter

View Counter

Documentation License: MIT Tests

A comprehensive Node.js/Express analytics server for tracking website views with MySQL storage, featuring auto-database creation, advanced tracking, and rich analytics.

πŸ“– Documentation

Visit our Interactive Documentation for detailed API specifications, debugging tips, and integration guides.

πŸ›‘οΈ GDPR Compliant & Privacy-First

100% GDPR Compliant By Design. This project is built from the ground up to respect user privacy and adhere to modern ethical standards:

  • Zero Cookies: No cookies, no local storage, and no consent banners required.
  • Data Sovereignty: You own your data. Analytics never leave your private infrastructure.
  • Minimal Collection: Tracks only what is necessary (Country, Browser, OS, Page Path).

πŸ”„ Data Privacy Lifecycle

graph LR
    A[Visitor Request] --> B{Privacy Filter}
    B -->|Transient| C[Geo-lookup]
    B -->|Transient| D[Daily Salting]
    C --> E[Masked IP: 1.2.3.0]
    D --> F[SHA256 Hash]
    E --> G[(MySQL Database)]
    F --> G
    B -.->|Discarded| H[Raw IP Address]
    style H fill:#f96,stroke:#333,stroke-width:2px
Loading

🧬 What happens to the IP?

We believe in total transparency regarding your visitors' data:

  1. Transient Use Only: The raw IP address is used only in memory for the initial Country lookup and for generating the daily unique visitor hash.
  2. Immediate Masking: Before being saved to disk, the IP is masked (IPv4 last octet is zeroed).
  3. No Storage: The raw, identifiable IP address never touches the database disk.
  4. Automated Guards: Our build pipeline includes fail-safe regex tests to ensure no code changes can accidentally start saving raw IPs.

✨ Features

Core Capabilities

  • πŸ”’ Security: Prepared statements, rate limiting, Helmet.js, input validation
  • ⚑ Performance: Connection pooling with mysql2, duplicate prevention
  • πŸ—„οΈ Flexible Database: Connect to existing DB or auto-create schema
  • πŸ› οΈ Easy Setup: Interactive CLI wizard with config detection
  • πŸ₯ Production-Ready: Health checks, graceful shutdown, structured logging

Advanced Tracking

  • πŸ“ Page Tracking: Track specific pages/paths, not just app-level
  • πŸ”— Referrer Analysis: Automatic source categorization (search, social, email, campaign, referral, direct)
  • πŸ–₯️ User Agent Parsing: Browser, OS, and device type detection
  • πŸ‘€ Session Tracking: Group views by user session
  • 🎯 Custom Events: Track button clicks, form submissions, etc.
  • πŸ“Š Time-Based Analytics: Hourly, daily, and weekly trends

Quick Start

1. Install Dependencies

npm install

2. Run Setup Wizard

npm run setup

The wizard will:

  • Detect existing configuration (if any)
  • Guide you through database setup (connect vs. create mode)
  • Configure allowed app IDs and device sizes
  • Optionally create .env file

3. Start Server

npm start

Configuration

Database Modes

Connect Mode (default): Use existing database

// dbInfo.json
{
    "mode": "connect",
    "host": "127.0.0.1",
    "database": "viewcounterdb",
    "user": "root",
    "password": "your_password"
}

Create Mode: Auto-create database and tables

// dbInfo.json
{
    "mode": "create",
    "host": "127.0.0.1",
    "database": "viewcounterdb",
    "user": "root",
    "password": "your_password"
}

Allowed Values

// allowed.json
{
    "appId": ["blog", "portfolio"],
    "deviceSize": ["small", "medium", "large"]
}

Environment Variables (optional)

See .env.example for all options. Key settings:

  • DB_MODE: connect or create
  • PORT: Server port (default: 3030)
  • RATE_LIMIT_MAX: Max requests per minute (default: 100)
  • UNIQUE_VISITOR_WINDOW_HOURS: Duplicate prevention window (default: 24, 0 to disable)

API Endpoints

πŸ“Š Tracking

Register View (Enhanced)

# Basic (backward compatible)
GET /registerView?appId=blog&deviceSize=medium

# Enhanced with page tracking
GET /registerView?appId=blog&deviceSize=medium&page=/blog/my-post&title=My%20Post

# With referrer and session
GET /registerView?appId=blog&deviceSize=medium&page=/blog/my-post&referrer=https://google.com&sessionId=abc123

Automatic tracking:

  • βœ… IP address and geolocation
  • βœ… Browser, OS, device type (from User-Agent)
  • βœ… Referrer domain and source type
  • βœ… Duplicate prevention (configurable window)

Response:

{"message": "Success!", "duplicate": false}

Track Custom Event

POST /event
Content-Type: application/json

{
  "appId": "blog",
  "eventType": "button_click",
  "eventData": {"button": "subscribe", "location": "header"},
  "sessionId": "abc123",
  "page": "/blog/my-post"
}

πŸ“ˆ Analytics

Get Statistics

GET /stats/:appId

Response:

{
  "appId": "blog",
  "stats": {
    "total": 1523,
    "uniqueVisitors": 892,
    "last24Hours": 47,
    "byCountry": [{"country": "US", "count": 423}],
    "byDevice": [{"devicesize": "medium", "count": 789}]
  }
}

Get Trends

# Daily trends for last 30 days
GET /trends/:appId?period=daily&days=30

# Hourly trends for last 7 days
GET /trends/:appId?period=hourly&days=7

# Weekly trends for last 12 weeks
GET /trends/:appId?period=weekly&days=84

Response:

{
  "appId": "blog",
  "period": "daily",
  "days": 30,
  "trends": [
    {"period": "2026-01-01", "count": 45},
    {"period": "2026-01-02", "count": 52}
  ]
}

Get Referrer Statistics

GET /referrers/:appId?limit=20

Response:

{
  "appId": "blog",
  "bySource": [
    {"source_type": "search", "count": 450},
    {"source_type": "social", "count": 230},
    {"source_type": "direct", "count": 180}
  ],
  "byDomain": [
    {"referrer_domain": "google.com", "count": 320},
    {"referrer_domain": "twitter.com", "count": 150}
  ]
}

Get Browser/OS Statistics

GET /browsers/:appId

Response:

{
  "appId": "blog",
  "byBrowser": [
    {"browser": "Chrome", "count": 650},
    {"browser": "Safari", "count": 320}
  ],
  "byOS": [
    {"os": "Windows", "count": 550},
    {"os": "Mac OS", "count": 380}
  ],
  "byDeviceType": [
    {"device_type": "desktop", "count": 890},
    {"device_type": "mobile", "count": 450}
  ]
}

Get Page Statistics

GET /pages/:appId?limit=20

Response:

{
  "appId": "blog",
  "pages": [
    {"page_path": "/blog/post-1", "page_title": "My First Post", "views": 234},
    {"page_path": "/blog/post-2", "page_title": "Second Post", "views": 189}
  ]
}

Get Session Details

GET /sessions/:appId/:sessionId

Response:

{
  "appId": "blog",
  "sessionId": "abc123",
  "events": [
    {
      "id": 1,
      "event_type": "pageview",
      "page_path": "/blog/post-1",
      "timestamp": "2026-01-09T21:30:00.000Z"
    },
    {
      "id": 2,
      "event_type": "button_click",
      "event_data": {"button": "subscribe"},
      "timestamp": "2026-01-09T21:31:15.000Z"
    }
  ],
  "count": 2
}

Get Recent Views

GET /views/:appId?limit=10&offset=0

List Apps

GET /apps

Health Check

GET /health

Test IP Detection

GET /ip

Deployment

Production with nohup

nohup node index.js > stdout.log &
# Kill with: kill <pid>

Environment Variables

Set NODE_ENV=production to hide error details in API responses.

What Gets Tracked?

For each view/event, the system automatically captures:

Field Source Description
IP Address Request Visitor IP
Country GeoIP lookup 2-letter country code
Timestamp Server When the event occurred
Device Size Query param small, medium, large
Page Path Query param (optional) e.g., /blog/my-post
Page Title Query param (optional) e.g., "My Blog Post"
Referrer Header/query (optional) Full referrer URL
Referrer Domain Parsed e.g., google.com
Source Type Parsed search, social, email, campaign, referral, direct
Browser User-Agent e.g., Chrome, Safari, Firefox
Browser Version User-Agent e.g., 120.0
OS User-Agent e.g., Windows, Mac OS, Linux
OS Version User-Agent e.g., 10, 14.2
Device Type User-Agent desktop, mobile, tablet, tv, console
Session ID Query param (optional) Group events by session
Event Type Query param/body pageview, click, submit, etc.
Event Data Body (optional) Custom JSON data

Understanding UNIQUE_VISITOR_WINDOW_HOURS

This setting prevents counting the same visitor multiple times within a time window.

How it works:

  • When a view is registered, the system checks if the same IP has visited within the last X hours
  • If yes: Returns {duplicate: true} (doesn't count again)
  • If no: Inserts new view

Examples:

  • 24 (default): Same IP counts as 1 view per day
  • 0: Disable duplicate prevention (count every request)
  • 168: Same IP counts as 1 view per week

Note: Only applies to pageview events, not custom events.

πŸ›‘οΈ Privacy Guardrails (Fail-Safe)

To guarantee that raw IPs never leak into the database, we've implemented an automated Privacy Guard suite (privacyFailSafe.test.js):

  • Query Interception: Every single SQL INSERT is intercepted during tests.
  • Regex Scanning: We scan all query parameters against raw IP patterns (IPv4 and IPv6).
  • Hard Enforcement: If the system ever attempts to save an unmasked IP, the test suite immediately fails, preventing accidental privacy regressions.

This makes ViewCounter not just "Privacy-First" by design, but Privacy-Guaranteed by automation.

  • Implement IP masking utility
  • Implement transient hashing for uniqueness
  • Update DatabaseManager to use hashes/masked IPs
  • Update db/schema.sql (column renaming/clarification)
  • Remove "IP Address" references from docs/README
  • Update documentation with "How it works" privacy section
  • Update and verify tests

Security Features

  • βœ… SQL injection prevention (prepared statements)
  • βœ… Rate limiting (100 req/min default)
  • βœ… Security headers (Helmet.js)
  • βœ… Input validation (express-validator)
  • βœ… IP validation
  • βœ… Duplicate view prevention

Client-Side Integration

Basic Tracking

<script>
  // Track page view
  fetch('https://your-server.com/registerView?appId=blog&deviceSize=medium');
</script>

Enhanced Tracking

// Generate session ID (store in sessionStorage)
const sessionId = sessionStorage.getItem('sessionId') || 
  Math.random().toString(36).substring(2);
sessionStorage.setItem('sessionId', sessionId);

// Track page view with full context
fetch(`https://your-server.com/registerView?` + new URLSearchParams({
  appId: 'blog',
  deviceSize: window.innerWidth < 768 ? 'small' : 
               window.innerWidth < 1200 ? 'medium' : 'large',
  page: window.location.pathname,
  title: document.title,
  referrer: document.referrer,
  sessionId: sessionId
}));

Track Custom Events

async function trackEvent(eventType, eventData) {
  await fetch('https://your-server.com/event', {
    method: 'POST',
    headers: {'Content-Type': 'application/json'},
    body: JSON.stringify({
      appId: 'blog',
      eventType,
      eventData,
      sessionId: sessionStorage.getItem('sessionId'),
      page: window.location.pathname
    })
  });
}

// Track button click
document.querySelector('#subscribe-btn').addEventListener('click', () => {
  trackEvent('button_click', {button: 'subscribe', location: 'header'});
});

Testing

Running Tests

# Run all tests with coverage (auto-generates TEST_REPORT.md)
npm test

# Run tests in watch mode (for development)
npm run test:watch

# Run tests and persist database for inspection
npm run test:persist

# Run tests for CI/CD (no report generation)
npm run test:ci

Test Database

Automatic Management:

  • βœ… Creates fresh viewcounterdb_test database before each test run
  • βœ… Populates with realistic test data
  • βœ… Automatically cleaned up after tests complete

Persist Database for Debugging:

# Keep test database after tests
npm run test:persist

# Or set environment variable
PERSIST_TEST_DB=true npm test

When persisted, you can inspect the database:

USE viewcounterdb_test;
SHOW TABLES;
SELECT * FROM test_app_1;

To manually remove:

DROP DATABASE viewcounterdb_test;

Test Reports

Automatically generated after every test run:

  • βœ… Terminal output: Immediate test results and coverage
  • βœ… TEST_REPORT.md: Comprehensive markdown summary (auto-generated)
  • βœ… test-report.html: Visual test results with dark theme
  • βœ… coverage/index.html: Interactive code coverage report

All reports are created in the project root directory.

Test Coverage

The test suite includes:

Unit Tests

  • βœ… UserAgentParser: Browser, OS, and device detection
  • βœ… ReferrerParser: Traffic source categorization

Integration Tests

  • βœ… Health Check: Server status monitoring
  • βœ… View Registration: Basic and enhanced tracking
  • βœ… Custom Events: Event tracking with metadata
  • βœ… Statistics: Aggregated analytics
  • βœ… Trends: Time-based analytics
  • βœ… Referrers: Traffic source analysis
  • βœ… Browsers: Browser/OS/device breakdown
  • βœ… Pages: Page view statistics
  • βœ… Sessions: Session journey tracking
  • βœ… Rate Limiting: Request throttling

Test Scenarios

All endpoints are tested with:

  • βœ“ Valid inputs
  • βœ“ Invalid inputs
  • βœ“ Missing parameters
  • βœ“ Edge cases
  • βœ“ Security validation

License

MIT - Do whatever you want with this, just don't sue us.

About

A comprehensive Node.js/Express analytics server for tracking website views with MySQL storage, featuring auto-database creation, advanced tracking, and rich analytics.

Resources

License

Code of conduct

Contributing

Security policy

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published