Scrawn LogoScrawn Docs
Scrawn LogoScrawn Docs
Scrawn.js

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 null to skip tracking.
  • Returns: EventPayload object with userId (string) and debitAmount (number), or null

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/users but 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.ts
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 });
});

Next Steps