Tutorial

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.

MI

Tutorial Expert

• 7 min read

CounterAPI Node.js Backend Analytics Express API Monitoring

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:

  1. Tracks every API request by endpoint path and HTTP method
  2. Uses middleware to ensure all requests are monitored
  3. Provides an admin endpoint to retrieve analytics data
  4. 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:

  1. Creates a job tracking wrapper that monitors starts, completions, failures, and execution time
  2. Uses cron to schedule recurring tasks
  3. 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:

  1. Creates a middleware that checks and enforces rate limits
  2. Updates response headers with rate limit information
  3. Automatically resets counters on a schedule
  4. Handles errors gracefully

Best Practices for Node.js Implementation

When using CounterAPI in production Node.js applications, consider these best practices:

  1. 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
    });
    
  2. 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'));
    
  3. 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');
    
  4. 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!