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?
Core API Reference See all available methods and options
Authentication Learn about server-side authentication
Query API Query your analytics data programmatically
Configuration Guide Advanced configuration options
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