Track Middleware Calls
Automatically track API requests with middleware
Overview
The middlewareEventConsumer method creates Express-compatible middleware that automatically tracks usage for API requests. This is perfect for REST APIs where you want to charge per request without manually calling sdkCallEventConsumer.
Framework Compatibility: The middleware is designed for Express-like frameworks with standard (req, res, next) signatures. For frameworks like Fastify, Next.js, or Hono with different middleware patterns, you may need to manually call sdkCallEventConsumer in your route handlers or adapt the middleware with wrapper functions.
How It Works
The middleware intercepts HTTP requests, extracts userId and debitAmount via your custom extractor function, and automatically tracks the usage event. It supports whitelist/blacklist patterns with wildcards for fine-grained control.
import { Scrawn, type EventPayload } from '@scrawn/core';
const scrawn = new Scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: process.env.SCRAWN_BASE_URL || 'http://localhost:8069',
});
// Middleware automatically tracks each request
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.headers['x-user-id'] as string,
debitAmount: req.body?.cost || 1,
}),
}));In simple terms: When a request comes in, grab the userId from the request headers and the debitAmount from the request body, then charge that user that amount.
Configuration
extractor (required)
- Type:
(request) => EventPayload | Promise<EventPayload> | null | Promise<null> - Description: Function to extract userId and debitAmount from the request. Return
nullto skip tracking. - Returns: EventPayload object with
userId(string) anddebitAmount(number), ornull
Basic extractor:
extractor: (req) => ({
userId: req.user.id,
debitAmount: 10,
})Async extractor:
extractor: async (req) => {
const user = await getUser(req);
return { userId: user.id, debitAmount: calculateCost(req) };
}Skip tracking conditionally:
extractor: (req) => {
if (!req.user) return null;
return { userId: req.user.id, debitAmount: 1 };
}whitelist (optional)
- Type:
string[] - Description: Array of endpoint patterns to track. Takes precedence over blacklist.
- Wildcards:
*matches a single path segment (e.g.,/api/*matches/api/usersbut not/api/users/123)**matches multiple segments (e.g.,/api/**matches any path starting with/api/)
whitelist: ['/api/generate', '/api/analyze', '/api/v1/*']blacklist (optional)
- Type:
string[] - Description: Array of endpoint patterns to exclude. Only applies to endpoints not in whitelist.
- Wildcards: Same as whitelist
blacklist: ['/health', '/api/collect-payment', '/internal/**', '**.tmp']Express.js Example
import express from 'express';
import { Scrawn, type EventPayload } from '@scrawn/core';
const app = express();
app.use(express.json());
const scrawn = new Scrawn({
apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
baseURL: process.env.SCRAWN_BASE_URL || 'http://localhost:8069',
});
// Apply middleware to all routes
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.headers['x-user-id'] as string,
debitAmount: req.body?.cost || 1,
}),
blacklist: ['/health', '/api/collect-payment'],
}));Advanced Patterns
Dynamic Pricing Based on Endpoint
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => {
const endpoint = req.path || req.url || '';
let debitAmount = 1;
if (endpoint.startsWith('/api/ai')) debitAmount = 100;
else if (endpoint.startsWith('/api/premium')) debitAmount = 50;
else if (endpoint.startsWith('/api/standard')) debitAmount = 10;
return {
userId: req.headers['x-user-id'] as string,
debitAmount,
};
},
}));Conditional Tracking with Extractor
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload | null => {
// Skip tracking for non-authenticated requests
if (!req.user) {
return null;
}
// Skip tracking for admin users
if (req.user.role === 'admin') {
return null;
}
return {
userId: req.user.id,
debitAmount: 10,
};
},
}));Whitelist/Blacklist Patterns
// Only track specific endpoints
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
whitelist: [
'/api/generate',
'/api/analyze',
'/api/v1/*', // Single segment wildcard
'/api/premium/**', // Multi-segment wildcard
],
}));
// Exclude specific endpoints
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
blacklist: [
'/health',
'/api/collect-payment',
'/internal/**', // Exclude all internal routes
'**.tmp', // Exclude temp file requests
],
}));Request Size-Based Pricing
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => {
const sizeInKB = parseInt(req.headers['content-length'] || '0', 10) / 1024;
const basePrice = 10;
const sizePrice = Math.ceil(sizeInKB) * 5;
return {
userId: req.user.id,
debitAmount: basePrice + sizePrice,
};
},
}));Error Handling
Automatic Error Handling
The middleware automatically catches and logs errors without blocking requests:
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
}));Explicit Error Handling
To handle errors explicitly and potentially block requests, use sdkCallEventConsumer directly:
app.use(async (req, res, next) => {
try {
await scrawn.sdkCallEventConsumer({
userId: req.user.id,
debitAmount: 10,
});
next();
} catch (error) {
console.error('Failed to track usage:', error);
// Decide whether to block the request
res.status(500).json({ error: 'Failed to track usage' });
}
});Best Practices
1. Place Middleware After Authentication
// Good - User info is available
app.use(authMiddleware);
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
}));
// ❌ Bad - Won't have user info
app.use(scrawn.middlewareEventConsumer({ /* ... */ }));
app.use(authMiddleware);2. Use Blacklist for Internal Routes
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
blacklist: ['/internal/**', '/health', '/_next/**'],
}));3. Return Null for Invalid Requests
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload | null => {
if (!req.user) {
// Skip tracking for unauthenticated requests
return null;
}
return {
userId: req.user.id,
debitAmount: 10,
};
},
}));4. Don't Await Events Inline
The middleware automatically handles async tracking without blocking. Event tracking runs in the background while your handler executes.
app.use(scrawn.middlewareEventConsumer({
extractor: (req): EventPayload => ({
userId: req.user.id,
debitAmount: 10,
}),
}));
app.post('/api/generate', async (req, res) => {
// Your handler runs immediately - tracking happens in parallel
const result = await generateContent(req.body);
res.json({ result });
});