Scrawn LogoScrawn Docs
SDK ReferenceReference & Guides

Framework Recipes

Quick integration patterns for popular JavaScript frameworks

Overview

This guide provides detailed integration examples for popular JavaScript frameworks and runtimes.

Framework Compatibility Note: Most examples use basicUsageEventConsumer directly in route handlers rather than the middlewareEventConsumer due to framework-specific middleware patterns. The middlewareEventConsumer is designed for Express-like frameworks with (req, res, next) signatures.

Next.js

app/api/generate/route.ts
import { scrawn } from "@scrawn/core";
import { NextRequest, NextResponse } from "next/server";
import { auth } from "@/lib/auth";

const biller = scrawn({
  apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
  baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
});

export async function POST(req: NextRequest) {
  const session = await auth();

  if (!session?.user?.id) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  // Start tracking (don't await yet)
  const trackingPromise = biller.basicUsageEventConsumer({
    userId: session.user.id,
    debit: 100,
  });

  // Process request
  const result = await generateContent(await req.json());

  // Wait for tracking to complete
  await trackingPromise;

  return NextResponse.json(result);
}

Express.js

server.ts
import express from "express";
import { scrawn } from "@scrawn/core";

const app = express();
app.use(express.json());

const biller = scrawn({
  apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
  baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
});

// Use middleware to automatically track all API calls
app.use(biller.middlewareEventConsumer({
  extractor: (req) => {
    if (!req.user) return null;
    return {
      userId: req.user.id,
      debit: req.body?.cost || 10,
    };
  },
  blacklist: ["/health", "/api/collect-payment"],
}));

app.post("/api/generate", async (req, res) => {
  const result = await generateContent(req.body);
  res.json(result);
});

app.post("/api/collect-payment", async (req, res) => {
  const checkoutLink = await biller.collectPayment(req.body.userId);
  res.redirect(checkoutLink);
});

app.listen(3000);

NestJS

scrawn.interceptor.ts
import {
  Injectable,
  NestInterceptor,
  ExecutionContext,
  CallHandler,
} from "@nestjs/common";
import { Observable } from "rxjs";
import { scrawn } from "@scrawn/core";

@Injectable()
export class ScrawnInterceptor implements NestInterceptor {
  private biller = scrawn({
    apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
    baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
  });

  async intercept(
    context: ExecutionContext,
    next: CallHandler,
  ): Promise<Observable<any>> {
    const request = context.switchToHttp().getRequest();
    const user = request.user;

    if (user) {
      this.biller.basicUsageEventConsumer({
        userId: user.id,
        debit: 10,
      }).catch(err => {
        console.error("Failed to track event:", err);
      });
    }

    return next.handle();
  }
}
app.controller.ts
import { Controller, Post, UseInterceptors } from "@nestjs/common";
import { ScrawnInterceptor } from "./scrawn.interceptor";

@Controller("api")
@UseInterceptors(ScrawnInterceptor)
export class AppController {
  @Post("generate")
  async generate() {
    return { message: "Content generated" };
  }
}

tRPC

trpc/middleware.ts
import { TRPCError } from "@trpc/server";
import { scrawn } from "@scrawn/core";
import { t } from "./trpc";

const biller = scrawn({
  apiKey: process.env.SCRAWN_KEY as `scrn_${string}`,
  baseURL: process.env.SCRAWN_BASE_URL || "http://localhost:8069",
});

export const trackUsage = t.middleware(async ({ ctx, next }) => {
  if (!ctx.session?.user?.id) {
    throw new TRPCError({ code: "UNAUTHORIZED" });
  }

  // Start tracking (don't await)
  const trackingPromise = biller.basicUsageEventConsumer({
    userId: ctx.session.user.id,
    debit: 10,
  });

  // Execute procedure
  const result = await next();

  // Wait for tracking
  await trackingPromise.catch(err => {
    console.error("Failed to track event:", err);
  });

  return result;
});

// Use in procedures
export const protectedProcedure = t.procedure.use(trackUsage);