8 min de lectura
Server-Side Analytics Integration
This guide covers building robust server-side integrations with the Zenovay API. You will learn how to create reusable API wrappers in Node.js and Python, implement caching, batch-fetch data, and set up scheduled pipelines.
Node.js Wrapper Class
A reusable client that handles authentication, error handling, and rate-limit retries:
lib/zenovay-client.tsTypeScript
interface ZenovayConfig {
apiKey: string;
baseUrl?: string;
maxRetries?: number;
}
interface ZenovayResponse<T> {
success: boolean;
data: T;
timestamp: string;
}
export class ZenovayClient {
private apiKey: string;
private baseUrl: string;
private maxRetries: number;
constructor(config: ZenovayConfig) {
this.apiKey = config.apiKey;
this.baseUrl = config.baseUrl || 'https://api.zenovay.com/api/external/v1';
this.maxRetries = config.maxRetries ?? 3;
}
private async request<T>(endpoint: string, params?: Record<string, string>): Promise<T> {
const url = new URL(`${this.baseUrl}${endpoint}`);
if (params) {
Object.entries(params).forEach(([k, v]) => url.searchParams.set(k, v));
}
for (let attempt = 0; attempt <= this.maxRetries; attempt++) {
const response = await fetch(url.toString(), {
headers: { 'X-API-Key': this.apiKey },
});
if (response.status === 429) {
const retryAfter = parseInt(response.headers.get('Retry-After') || '60', 10);
await new Promise(r => setTimeout(r, retryAfter * 1000));
continue;
}
const json: ZenovayResponse<T> = await response.json();
if (!json.success) {
throw new Error(`Zenovay API error (${response.status}): ${JSON.stringify(json)}`);
}
return json.data;
}
throw new Error('Max retries exceeded');
}
async getUsage() {
return this.request<any>('/usage');
}
async listWebsites() {
return this.request<{ websites: any[]; total: number }>('/websites');
}
async getAnalytics(websiteId: string, range = '7d') {
return this.request<any>(`/analytics/${websiteId}`, { range });
}
async getVisitors(websiteId: string, options?: { range?: string; limit?: number; offset?: number }) {
const params: Record<string, string> = { range: options?.range || '7d' };
if (options?.limit) params.limit = String(options.limit);
if (options?.offset) params.offset = String(options.offset);
return this.request<any>(`/analytics/${websiteId}/visitors`, params);
}
async getPages(websiteId: string, range = '7d', limit = 20) {
return this.request<any>(`/analytics/${websiteId}/pages`, { range, limit: String(limit) });
}
async getCountries(websiteId: string, range = '7d') {
return this.request<any>(`/analytics/${websiteId}/countries`, { range });
}
async getTechnology(websiteId: string, range = '7d') {
return this.request<any>(`/analytics/${websiteId}/technology`, { range });
}
async getErrorGroups(websiteId: string, status?: string) {
const params: Record<string, string> = {};
if (status) params.status = status;
return this.request<any>(`/errors/${websiteId}/groups`, params);
}
}Usage:
Usage exampleTypeScript
const zenovay = new ZenovayClient({ apiKey: process.env.ZENOVAY_API_KEY! });
const analytics = await zenovay.getAnalytics('ws_abc123', '30d');
console.log('Total visitors:', analytics.summary.total_visitors);
const countries = await zenovay.getCountries('ws_abc123', '30d');
console.log('Top country:', countries.countries[0]?.country_name);Python Wrapper Class
The same pattern in Python using the requests library:
zenovay_client.pyPython
import time
import requests
from typing import Optional
class ZenovayClient:
def __init__(self, api_key: str, base_url: str = "https://api.zenovay.com/api/external/v1", max_retries: int = 3):
self.api_key = api_key
self.base_url = base_url
self.max_retries = max_retries
def _request(self, endpoint: str, params: Optional[dict] = None):
url = f"{self.base_url}{endpoint}"
headers = {"X-API-Key": self.api_key}
for attempt in range(self.max_retries + 1):
response = requests.get(url, headers=headers, params=params)
if response.status_code == 429:
retry_after = int(response.headers.get("Retry-After", 60))
time.sleep(retry_after)
continue
data = response.json()
if not data.get("success"):
raise Exception(f"Zenovay API error ({response.status_code}): {data}")
return data["data"]
raise Exception("Max retries exceeded")
def get_usage(self):
return self._request("/usage")
def list_websites(self):
return self._request("/websites")
def get_analytics(self, website_id: str, range: str = "7d"):
return self._request(f"/analytics/{website_id}", {"range": range})
def get_visitors(self, website_id: str, range: str = "7d", limit: int = 100, offset: int = 0):
return self._request(f"/analytics/{website_id}/visitors", {
"range": range, "limit": limit, "offset": offset
})
def get_pages(self, website_id: str, range: str = "7d", limit: int = 20):
return self._request(f"/analytics/{website_id}/pages", {"range": range, "limit": limit})
def get_countries(self, website_id: str, range: str = "7d"):
return self._request(f"/analytics/{website_id}/countries", {"range": range})
def get_technology(self, website_id: str, range: str = "7d"):
return self._request(f"/analytics/{website_id}/technology", {"range": range})
def get_error_groups(self, website_id: str, status: Optional[str] = None):
params = {}
if status:
params["status"] = status
return self._request(f"/errors/{website_id}/groups", params)Usage:
Usage examplePython
import os
from zenovay_client import ZenovayClient
client = ZenovayClient(api_key=os.environ["ZENOVAY_API_KEY"])
analytics = client.get_analytics("ws_abc123", range="30d")
print(f"Total visitors: {analytics['summary']['total_visitors']}")
countries = client.get_countries("ws_abc123", range="30d")
print(f"Top country: {countries['countries'][0]['country_name']}")Caching Strategies
Analytics data does not change every second. Caching reduces API calls and improves response times.
In-Memory Cache (Node.js)
lib/cache.tsTypeScript
interface CacheEntry<T> {
data: T;
expiresAt: number;
}
const cache = new Map<string, CacheEntry<any>>();
export function getCached<T>(key: string): T | null {
const entry = cache.get(key);
if (!entry) return null;
if (Date.now() > entry.expiresAt) {
cache.delete(key);
return null;
}
return entry.data;
}
export function setCache<T>(key: string, data: T, ttlSeconds: number): void {
cache.set(key, { data, expiresAt: Date.now() + ttlSeconds * 1000 });
}
// Usage with Zenovay client
export async function getCachedAnalytics(client: ZenovayClient, websiteId: string, range: string) {
const cacheKey = `analytics:${websiteId}:${range}`;
const cached = getCached(cacheKey);
if (cached) return cached;
const data = await client.getAnalytics(websiteId, range);
setCache(cacheKey, data, 300); // Cache for 5 minutes
return data;
}Redis Cache
lib/redis-cache.tsTypeScript
import { createClient } from 'redis';
const redis = createClient({ url: process.env.REDIS_URL });
redis.connect();
export async function getCachedOrFetch<T>(
key: string,
ttlSeconds: number,
fetcher: () => Promise<T>
): Promise<T> {
const cached = await redis.get(key);
if (cached) return JSON.parse(cached);
const data = await fetcher();
await redis.setEx(key, ttlSeconds, JSON.stringify(data));
return data;
}
// Usage
const analytics = await getCachedOrFetch(
`zenovay:analytics:ws_abc123:30d`,
300,
() => client.getAnalytics('ws_abc123', '30d')
);Recommended Cache TTLs
| Data Type | TTL | Reason |
|---|---|---|
| Analytics overview | 5 minutes | Aggregated data, updates infrequently |
| Visitor list | 2 minutes | New visitors arrive regularly |
| Countries | 10 minutes | Geographic distribution is stable |
| Technology | 10 minutes | Device/browser mix changes slowly |
| Live visitors | No cache | Real-time data, always fetch fresh |
Batch Data Fetching
When building dashboards that need multiple data points, fetch in parallel:
Parallel fetchingTypeScript
async function fetchDashboardData(client: ZenovayClient, websiteId: string, range: string) {
const [analytics, countries, technology, pages] = await Promise.all([
client.getAnalytics(websiteId, range),
client.getCountries(websiteId, range),
client.getTechnology(websiteId, range),
client.getPages(websiteId, range),
]);
return { analytics, countries, technology, pages };
}For fetching data across multiple websites:
Multi-website fetchingTypeScript
async function fetchAllWebsiteStats(client: ZenovayClient, range: string) {
const { websites } = await client.listWebsites();
const results = await Promise.all(
websites.map(async (website: any) => {
const analytics = await client.getAnalytics(website.id, range);
return {
website: { id: website.id, domain: website.domain, name: website.name },
summary: analytics.summary,
};
})
);
return results;
}Scheduled Data Pipelines
Use cron jobs to collect analytics data on a regular schedule.
Node.js with node-cron
scripts/daily-export.tsTypeScript
import cron from 'node-cron';
import { ZenovayClient } from '../lib/zenovay-client';
import fs from 'fs/promises';
const client = new ZenovayClient({ apiKey: process.env.ZENOVAY_API_KEY! });
// Run daily at 2:00 AM
cron.schedule('0 2 * * *', async () => {
console.log('Starting daily analytics export...');
try {
const { websites } = await client.listWebsites();
for (const website of websites) {
const analytics = await client.getAnalytics(website.id, '24h');
const countries = await client.getCountries(website.id, '24h');
const date = new Date().toISOString().split('T')[0];
const filename = `exports/${website.domain}_${date}.json`;
await fs.mkdir('exports', { recursive: true });
await fs.writeFile(filename, JSON.stringify({
website: website.domain,
date,
analytics: analytics.summary,
countries: countries.countries,
}, null, 2));
console.log(`Exported: ${filename}`);
}
} catch (error) {
console.error('Export failed:', error);
}
});
console.log('Daily export cron job scheduled.');Python with Schedule
scripts/daily_export.pyPython
import os
import json
import schedule
import time
from datetime import date
from zenovay_client import ZenovayClient
client = ZenovayClient(api_key=os.environ["ZENOVAY_API_KEY"])
def daily_export():
print("Starting daily analytics export...")
try:
websites = client.list_websites()["websites"]
for website in websites:
analytics = client.get_analytics(website["id"], range="24h")
countries = client.get_countries(website["id"], range="24h")
today = date.today().isoformat()
filename = f"exports/{website['domain']}_{today}.json"
os.makedirs("exports", exist_ok=True)
with open(filename, "w") as f:
json.dump({
"website": website["domain"],
"date": today,
"analytics": analytics["summary"],
"countries": countries["countries"],
}, f, indent=2)
print(f"Exported: {filename}")
except Exception as e:
print(f"Export failed: {e}")
schedule.every().day.at("02:00").do(daily_export)
print("Daily export cron job scheduled.")
while True:
schedule.run_pending()
time.sleep(60)Monitoring and Alerting
Build alerts that fire when analytics metrics cross thresholds:
scripts/traffic-alert.tsTypeScript
import { ZenovayClient } from '../lib/zenovay-client';
const client = new ZenovayClient({ apiKey: process.env.ZENOVAY_API_KEY! });
interface AlertRule {
websiteId: string;
metric: 'total_visitors' | 'total_page_views';
threshold: number;
direction: 'above' | 'below';
}
const rules: AlertRule[] = [
{ websiteId: 'ws_abc123', metric: 'total_visitors', threshold: 100, direction: 'below' },
{ websiteId: 'ws_abc123', metric: 'total_page_views', threshold: 50000, direction: 'above' },
];
async function checkAlerts() {
for (const rule of rules) {
const analytics = await client.getAnalytics(rule.websiteId, '24h');
const value = analytics.summary[rule.metric];
const triggered =
rule.direction === 'above' ? value > rule.threshold : value < rule.threshold;
if (triggered) {
console.log(
`ALERT: ${rule.metric} is ${value} (${rule.direction} threshold of ${rule.threshold})`
);
// Send notification via email, Slack, PagerDuty, etc.
}
}
}
// Run every hour
setInterval(checkAlerts, 60 * 60 * 1000);
checkAlerts();Next Steps
- Getting Started with the API -- API key setup and first call
- Building a Custom Dashboard -- React frontend with charts
- Webhook and Real-Time Data -- Live data patterns and alerting
- External API Reference -- Full endpoint documentation
¿Fue útil esta página?