Server-Side Analytics with CounterAPI and Node.js
Learn how to implement powerful server-side analytics and tracking in your Node.js applications using CounterAPI — from basic usage to advanced patterns.
Tutorial Expert
• 7 min read
The Case for Server-Side Analytics
While client-side tracking provides insights into user interactions, server-side analytics offers a more reliable and comprehensive view of your application's usage. Client-side tracking can be blocked by ad blockers or disabled JavaScript, but server-side tracking happens invisibly on your infrastructure.
Server-side analytics are particularly valuable for:
- Tracking API usage and endpoint performance
- Monitoring background jobs and scheduled tasks
- Implementing rate limiting
- Recording business events with guaranteed delivery
CounterAPI provides an elegant solution for these needs without requiring complex infrastructure setup or database management.
Getting Started with CounterAPI in Node.js
Installation
First, add CounterAPI to your project:
npm install counterapi
# or
yarn add counterapi
Basic Setup
Import the library and create a counter client:
// ES Modules
import { Counter } from 'counterapi';
// Or CommonJS
// const { Counter } = require('counterapi');
const counter = new Counter({
workspace: 'my-nodejs-service', // Your workspace name
debug: process.env.NODE_ENV !== 'production', // Enable logging in development
timeout: 5000 // Request timeout in ms
});
That's all you need to start tracking metrics in your Node.js applications!
Practical Use Cases
Example 1: API Request Tracking with Express
One of the most common use cases for server-side analytics is monitoring API usage. Here's how to track requests to your Express.js endpoints:
import express from 'express';
import { Counter } from 'counterapi';
const app = express();
const counter = new Counter({ workspace: 'api-analytics' });
// Middleware to track all API requests
app.use(async (req, res, next) => {
const endpoint = req.path.replace(/\//g, '-') || 'home';
const method = req.method.toLowerCase();
try {
// Track by endpoint and method
await Promise.all([
counter.up(`endpoint${endpoint}`),
counter.up(`method-${method}`)
]);
} catch (error) {
// Log but don't block the request
console.error('Analytics error:', error);
}
// Continue with request handling
next();
});
// Routes
app.get('/users', (req, res) => {
res.json([{ id: 1, name: 'John' }, { id: 2, name: 'Jane' }]);
});
app.post('/orders', (req, res) => {
// Order creation logic
res.status(201).json({ success: true });
});
// Stats endpoint for internal monitoring
app.get('/admin/stats', async (req, res) => {
try {
const [usersEndpoint, ordersEndpoint, getRequests, postRequests] = await Promise.all([
counter.get('endpoint-users'),
counter.get('endpoint-orders'),
counter.get('method-get'),
counter.get('method-post')
]);
res.json({
endpoints: {
users: usersEndpoint.value,
orders: ordersEndpoint.value
},
methods: {
get: getRequests.value,
post: postRequests.value
}
});
} catch (error) {
res.status(500).json({ error: error.message });
}
});
app.listen(3000, () => console.log('Server running on port 3000'));
This implementation:
- Tracks every API request by endpoint path and HTTP method
- Uses middleware to ensure all requests are monitored
- Provides an admin endpoint to retrieve analytics data
- Handles errors properly to ensure the main application continues functioning if analytics fail
Example 2: Monitoring Scheduled Jobs
For backend services that run scheduled tasks, tracking execution is critical:
import { Counter } from 'counterapi';
import cron from 'node-cron';
const counter = new Counter({ workspace: 'jobs-monitoring' });
// Wrapper function to track job execution
async function trackJob(jobName, jobFunction) {
try {
console.log(`Starting job: ${jobName}`);
await counter.up(`job-${jobName}-started`);
const startTime = Date.now();
await jobFunction();
const duration = Date.now() - startTime;
// Track successful completion and duration
await Promise.all([
counter.up(`job-${jobName}-completed`),
counter.up(`job-${jobName}-duration-${Math.floor(duration / 1000)}s`)
]);
console.log(`Job ${jobName} completed in ${duration}ms`);
} catch (error) {
// Track failure
await counter.up(`job-${jobName}-failed`);
console.error(`Job ${jobName} failed:`, error);
// Re-throw if needed for further handling
throw error;
}
}
// Database backup job
async function runDatabaseBackup() {
// Simulated backup logic
await new Promise(resolve => setTimeout(resolve, 2000));
console.log('Backup completed');
}
// Daily cleanup job
async function cleanupOldData() {
// Simulated cleanup logic
await new Promise(resolve => setTimeout(resolve, 1000));
console.log('Cleanup completed');
}
// Schedule jobs with tracking
cron.schedule('0 0 * * *', () => trackJob('database-backup', runDatabaseBackup)); // Midnight
cron.schedule('0 3 * * *', () => trackJob('data-cleanup', cleanupOldData)); // 3 AM
console.log('Scheduled jobs monitoring initialized');
This example:
- Creates a job tracking wrapper that monitors starts, completions, failures, and execution time
- Uses cron to schedule recurring tasks
- Implements proper error handling to ensure failures are recorded
Example 3: API Rate Limiting Implementation
Rate limiting is a common requirement for public APIs. Here's how to implement it with CounterAPI:
import express from 'express';
import { Counter } from 'counterapi';
const app = express();
app.use(express.json());
const counter = new Counter({ workspace: 'api-rate-limits' });
// Rate limiting middleware
const rateLimit = async (req, res, next) => {
// Get API key from request (header, query param, etc)
const apiKey = req.header('X-API-Key') || 'anonymous';
try {
// Get current usage for this API key
const usage = await counter.get(`rate-limit-${apiKey}`);
const currentCount = usage.value || 0;
// Check against limit (100 requests per day)
if (currentCount >= 100) {
return res.status(429).json({
error: 'Rate limit exceeded',
limit: 100,
current: currentCount,
reset: 'at midnight UTC'
});
}
// Increment usage counter
await counter.up(`rate-limit-${apiKey}`);
// Add rate limit info to response headers
res.set({
'X-RateLimit-Limit': 100,
'X-RateLimit-Remaining': 100 - (currentCount + 1),
'X-RateLimit-Reset': 'at midnight UTC'
});
next();
} catch (error) {
console.error('Rate limit error:', error);
// In case of error, allow the request but log the issue
next();
}
};
// Apply rate limiting to all API routes
app.use('/api', rateLimit);
// Example API routes
app.get('/api/data', (req, res) => {
res.json({ data: 'This is rate-limited data' });
});
// Schedule a daily reset of rate limits (in a real app, use a proper scheduler)
const resetRateLimits = async () => {
// In a real app, you'd get this list from a database
const apiKeys = ['key1', 'key2', 'anonymous'];
for (const key of apiKeys) {
try {
// For v2 API, use reset method
await counter.reset(`rate-limit-${key}`);
console.log(`Reset rate limit for ${key}`);
} catch (error) {
console.error(`Failed to reset rate limit for ${key}:`, error);
}
}
};
// Start server
app.listen(3000, () => {
console.log('API server running on port 3000');
});
// For demo purposes - reset rate limits every 6 hours
// In production use a proper scheduler that runs at midnight
setInterval(resetRateLimits, 6 * 60 * 60 * 1000);
This implementation:
- Creates a middleware that checks and enforces rate limits
- Updates response headers with rate limit information
- Automatically resets counters on a schedule
- Handles errors gracefully
Best Practices for Node.js Implementation
When using CounterAPI in production Node.js applications, consider these best practices:
-
Use environment variables for configuration:
const counter = new Counter({ workspace: process.env.COUNTER_WORKSPACE, debug: process.env.NODE_ENV !== 'production', accessToken: process.env.COUNTER_API_TOKEN });
-
Implement circuit breakers for resilience:
// Simple circuit breaker implementation let circuitBroken = false; let failureCount = 0; const FAILURE_THRESHOLD = 5; const CIRCUIT_RESET_TIMEOUT = 60000; // 1 minute async function safeCounterOperation(operation) { if (circuitBroken) { console.log('Circuit broken, skipping counter operation'); return null; } try { const result = await operation(); // Reset failure count on success failureCount = 0; return result; } catch (error) { failureCount++; console.error('Counter operation failed:', error); // Break circuit if threshold reached if (failureCount >= FAILURE_THRESHOLD) { circuitBroken = true; console.warn('Circuit breaker triggered - disabling counter operations'); // Reset circuit after timeout setTimeout(() => { circuitBroken = false; failureCount = 0; console.log('Circuit breaker reset'); }, CIRCUIT_RESET_TIMEOUT); } return null; } } // Usage safeCounterOperation(() => counter.up('some-metric'));
-
Group related counters with consistent naming:
// User-related events counter.up('user:signup'); counter.up('user:login'); counter.up('user:profile-update'); // Order-related events counter.up('order:created'); counter.up('order:fulfilled'); counter.up('order:canceled');
-
Batch operations when possible:
// Process multiple events in parallel async function trackEvents(events) { try { await Promise.all( events.map(event => counter.up(`event-${event.type}`)) ); console.log(`Tracked ${events.length} events successfully`); } catch (error) { console.error('Failed to track batch events:', error); } }
Conclusion
CounterAPI provides a powerful yet simple solution for server-side analytics in Node.js applications. Whether you're tracking API usage, monitoring background tasks, or implementing rate limiting, CounterAPI offers a lightweight alternative to complex analytics infrastructure.
By following the patterns in this guide, you can quickly implement robust tracking in your own applications without managing databases or complex analytics pipelines.
For more advanced usage, including authentication options and detailed API documentation, visit the official CounterAPI Node.js documentation.
Ready to add server-side analytics to your Node.js application? Get started with CounterAPI today!