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.