Next.js Route Handler
Wire a scan into an App Router API route before forwarding to your model.
A minimal pattern for an App Router Route Handler that screens the request body before doing anything else with it.
import { NextRequest, NextResponse } from "next/server";
import { TawaGuard } from "@tawaguard/sdk";
import OpenAI from "openai";
// Construct once per server instance, not per request.
const tawa = await TawaGuard.create(process.env.TAWA_API_KEY!);
const openai = new OpenAI({ apiKey: process.env.OPENAI_API_KEY });
const BLOCKED_ACTIONS = new Set(["block", "hard_block", "soft_block"]);
export async function POST(req: NextRequest) {
const { message } = await req.json();
const scan = await tawa.analyze(message, {
// Attribute the scan to the signed-in user for per-user tracking.
cuid: req.headers.get("x-user-id") ?? undefined,
});
if (BLOCKED_ACTIONS.has(scan.action)) {
return NextResponse.json(
{ error: "Message blocked by content policy.", rationale: scan.rationale },
{ status: 403 },
);
}
const completion = await openai.chat.completions.create({
model: "gpt-4o",
messages: [{ role: "user", content: message }],
});
return NextResponse.json({ reply: completion.choices[0].message.content });
}Streaming responses
If you're streaming the model's reply with the Vercel AI SDK's
streamText, use the Vercel AI SDK integration
instead of wiring analyze() in by hand — it wraps the model once and
handles the pre-generation scan for every call automatically, including the
caveat that a blocked stream surfaces as an error part rather than an
HTTP-level rejection.
Constructing the client once
TawaGuard.create does a network round trip (authentication), so build the
client at module scope — as above — rather than inside the handler. Route
Handlers in a serverless environment may still cold-start per invocation,
but this avoids paying the authentication round trip on every warm request.