Use Grain Analytics on your backend to track API calls, background jobs, and server-side events.
Install the Package
npm install @grainql/analytics-web
yarn add @grainql/analytics-web
pnpm add @grainql/analytics-web
The package name includes “web” but it works perfectly in Node.js. We’ll release a dedicated server package soon, but this one works great for now.
Initialize the SDK
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({
tenantId: 'your-tenant-id',
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY // Get this from your dashboard
});
Keep your secret key safe! Never commit it to version control or expose it in client-side code. Use environment variables.
Track Server-Side Events
// Track an API request
app.post('/api/users', async (req, res) => {
// Your logic...
await grain.track('api_request', {
endpoint: '/api/users',
method: 'POST',
user_id: req.user?.id,
status: 200
}, { flush: true }); // Flush immediately for serverless
res.json({ success: true });
});
Serverless environments: Use { flush: true } to send events immediately before the function terminates. Otherwise, events might not get sent.
Track Background Jobs
// Track a cron job or background task
async function processOrders() {
const orders = await getOrders();
for (const order of orders) {
await processOrder(order);
await grain.track('order_processed', {
order_id: order.id,
total: order.total,
items_count: order.items.length
});
}
// Flush at the end of the batch
await grain.flush();
}
Use Remote Configuration
Control server-side behavior without redeploying:
// Check if a feature is enabled
const isNewAlgorithmEnabled = grain.getConfig('use_new_algorithm');
if (isNewAlgorithmEnabled === 'true') {
result = await newAlgorithm(data);
} else {
result = await oldAlgorithm(data);
}
// Get configuration values
const maxRetries = parseInt(grain.getConfig('max_retries') || '3');
const timeout = parseInt(grain.getConfig('api_timeout') || '5000');
Express.js Middleware
Track all requests automatically:
import express from 'express';
import { createGrainAnalytics } from '@grainql/analytics-web';
const app = express();
const grain = createGrainAnalytics({
tenantId: process.env.GRAIN_TENANT_ID!,
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY!
});
// Middleware to track all requests
app.use((req, res, next) => {
const startTime = Date.now();
res.on('finish', () => {
const duration = Date.now() - startTime;
grain.track('api_request', {
path: req.path,
method: req.method,
status: res.statusCode,
duration,
user_id: req.user?.id
});
});
next();
});
// Your routes...
app.get('/api/users', (req, res) => {
res.json({ users: [] });
});
// Flush events before shutdown
process.on('SIGTERM', async () => {
await grain.flush();
process.exit(0);
});
app.listen(3000);
Fastify Plugin
import Fastify from 'fastify';
import { createGrainAnalytics } from '@grainql/analytics-web';
const fastify = Fastify();
const grain = createGrainAnalytics({
tenantId: process.env.GRAIN_TENANT_ID!,
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY!
});
// Track requests
fastify.addHook('onResponse', async (request, reply) => {
await grain.track('api_request', {
path: request.url,
method: request.method,
status: reply.statusCode,
duration: reply.getResponseTime()
});
});
// Flush on shutdown
fastify.addHook('onClose', async () => {
await grain.flush();
});
fastify.listen({ port: 3000 });
AWS Lambda Example
import { Handler } from 'aws-lambda';
import { createGrainAnalytics } from '@grainql/analytics-web';
// Initialize outside handler for connection reuse
const grain = createGrainAnalytics({
tenantId: process.env.GRAIN_TENANT_ID!,
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY!
});
export const handler: Handler = async (event, context) => {
try {
// Your logic...
const result = await processEvent(event);
// Track with immediate flush for Lambda
await grain.track('lambda_invoked', {
function_name: context.functionName,
event_type: event.type,
success: true
}, { flush: true });
return { statusCode: 200, body: JSON.stringify(result) };
} catch (error) {
await grain.track('lambda_error', {
function_name: context.functionName,
error: error.message
}, { flush: true });
throw error;
}
};
Lambda optimization: Initialize the Grain client outside your handler function so it’s reused across invocations. This improves performance and reduces cold starts.
Vercel Serverless Functions
// api/users.ts
import type { VercelRequest, VercelResponse } from '@vercel/node';
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({
tenantId: process.env.GRAIN_TENANT_ID!,
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY!
});
export default async function handler(req: VercelRequest, res: VercelResponse) {
// Your logic...
await grain.track('api_request', {
path: req.url,
method: req.method
}, { flush: true });
res.json({ success: true });
}
Track User Events Server-Side
Associate events with specific users:
// When a user signs up
app.post('/api/signup', async (req, res) => {
const user = await createUser(req.body);
// Identify the user
grain.identify(user.id, {
email: user.email,
name: user.name,
plan: 'free',
signup_date: new Date().toISOString()
});
// Track signup event
await grain.track('user_signed_up', {
method: 'email',
source: req.query.utm_source
}, { flush: true });
res.json({ user });
});
Error Tracking
Track backend errors to understand failure patterns:
app.use((err, req, res, next) => {
// Log to Grain
grain.track('server_error', {
error: err.message,
stack: err.stack,
path: req.path,
method: req.method,
user_id: req.user?.id
});
res.status(500).json({ error: 'Internal server error' });
});
Testing Your Integration
// test.ts
import { createGrainAnalytics } from '@grainql/analytics-web';
const grain = createGrainAnalytics({
tenantId: process.env.GRAIN_TENANT_ID!,
authStrategy: 'SERVER_SIDE',
secretKey: process.env.GRAIN_SECRET_KEY!,
debug: true // Enable debug logging
});
async function test() {
await grain.track('test_event', {
timestamp: new Date().toISOString()
}, { flush: true });
console.log('Event sent! Check your dashboard.');
}
test();
Run it:
Check your dashboard to see the event come in.
What’s Next?
Production checklist:
- Store secret key in environment variables
- Use
{ flush: true } for serverless functions
- Set up error handling and retry logic
- Monitor batch queue size in long-running processes