A comprehensive Node.js/Express analytics server for tracking website views with MySQL storage, featuring auto-database creation, advanced tracking, and rich analytics.
Visit our Interactive Documentation for detailed API specifications, debugging tips, and integration guides.
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).
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 We believe in total transparency regarding your visitors' data:
- 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.
- Immediate Masking: Before being saved to disk, the IP is masked (IPv4 last octet is zeroed).
- No Storage: The raw, identifiable IP address never touches the database disk.
- Automated Guards: Our build pipeline includes fail-safe regex tests to ensure no code changes can accidentally start saving raw IPs.
- π 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
- π 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
npm installnpm run setupThe 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
.envfile
npm startConnect 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.json { "appId": ["blog", "portfolio"], "deviceSize": ["small", "medium", "large"] }See .env.example for all options. Key settings:
DB_MODE:connectorcreatePORT: 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)
# 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=abc123Automatic 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}POST /event Content-Type: application/json { "appId": "blog", "eventType": "button_click", "eventData": {"button": "subscribe", "location": "header"}, "sessionId": "abc123", "page": "/blog/my-post" }GET /stats/:appIdResponse:
{ "appId": "blog", "stats": { "total": 1523, "uniqueVisitors": 892, "last24Hours": 47, "byCountry": [{"country": "US", "count": 423}], "byDevice": [{"devicesize": "medium", "count": 789}] } }# 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=84Response:
{ "appId": "blog", "period": "daily", "days": 30, "trends": [ {"period": "2026-01-01", "count": 45}, {"period": "2026-01-02", "count": 52} ] }GET /referrers/:appId?limit=20Response:
{ "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 /browsers/:appIdResponse:
{ "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 /pages/:appId?limit=20Response:
{ "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 /sessions/:appId/:sessionIdResponse:
{ "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 /views/:appId?limit=10&offset=0GET /appsGET /healthGET /ipnohup node index.js > stdout.log & # Kill with: kill <pid>Set NODE_ENV=production to hide error details in API responses.
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 |
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 day0: Disable duplicate prevention (count every request)168: Same IP counts as 1 view per week
Note: Only applies to pageview events, not custom events.
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
INSERTis 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
DatabaseManagerto 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
- β SQL injection prevention (prepared statements)
- β Rate limiting (100 req/min default)
- β Security headers (Helmet.js)
- β Input validation (express-validator)
- β IP validation
- β Duplicate view prevention
<script> // Track page view fetch('https://your-server.com/registerView?appId=blog&deviceSize=medium'); </script>// 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 }));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'}); });# 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:ciAutomatic Management:
- β
Creates fresh
viewcounterdb_testdatabase 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 testWhen persisted, you can inspect the database:
USE viewcounterdb_test; SHOW TABLES; SELECT * FROM test_app_1;To manually remove:
DROP DATABASE viewcounterdb_test;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.
The test suite includes:
- β UserAgentParser: Browser, OS, and device detection
- β ReferrerParser: Traffic source categorization
- β 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
All endpoints are tested with:
- β Valid inputs
- β Invalid inputs
- β Missing parameters
- β Edge cases
- β Security validation
MIT - Do whatever you want with this, just don't sue us.