# Backend

Server-side code: API routes, business logic, third-party integrations,
webhooks. With Next.js App Router, the line between frontend and backend
has blurred — this skill covers everything that runs on the server.

## When to use this
- The user is writing API endpoints or route handlers.
- The user is integrating a third-party service (Stripe, Resend, etc.).
- The user is handling webhooks from an external provider.
- The user needs background jobs, queues, or scheduled tasks.

## Stack-aware

This skill works for two common deployment paths:
- **Stack A (serverless on Vercel):** Server Components, Server Actions, Route Handlers. 10s function timeout on Hobby, 60s on Pro. Background work goes to Trigger.dev/Inngest.
- **Stack B (long-running on Render):** Same Next.js code, but functions can run for minutes (no serverless timeout). Background work can run in-process. Cron jobs via `render.yaml`.

Both work. Pick A for traffic spikes and edge globalness. Pick B for AI features that need long timeouts, websockets, or anything where serverless cold starts hurt.

## The mental model (Next.js App Router)

In modern Next.js, server code lives in three places:

1. **Server Components** — render on the server, can fetch data directly with `async/await`. Default for any `page.tsx` or `layout.tsx` not marked `"use client"`.
2. **Server Actions** — async functions marked `"use server"` that you call from client components like a regular function. They run on the server.
3. **Route Handlers** — `app/api/<route>/route.ts` files that export HTTP handlers (`GET`, `POST`, etc.). Use these for webhooks, public APIs, and anything called by external services.

**When to use which:**
- Reading data on a page → Server Component
- Form submissions, mutations from your own UI → Server Action
- Webhooks, public APIs, file uploads from third parties → Route Handler

## Server Components (data fetching)

```tsx
// app/dashboard/page.tsx — runs on the server
import { db } from "@/lib/db";
import { recipes } from "@/lib/schema";
import { eq } from "drizzle-orm";

export default async function DashboardPage() {
  const userRecipes = await db
    .select()
    .from(recipes)
    .where(eq(recipes.userId, "current-user-id"));

  return (
    <div>
      {userRecipes.map((recipe) => (
        <div key={recipe.id}>{recipe.title}</div>
      ))}
    </div>
  );
}
```

No useEffect, no loading state, no client fetching. Just `await` the data.

## Server Actions (mutations)

```ts
// app/actions/create-recipe.ts
"use server";
import { db } from "@/lib/db";
import { recipes } from "@/lib/schema";
import { revalidatePath } from "next/cache";
import { auth } from "@/lib/auth";

export async function createRecipe(formData: FormData) {
  const session = await auth();
  if (!session) throw new Error("Unauthorized");

  const title = formData.get("title") as string;
  if (!title || title.length < 2) {
    return { error: "Title is required" };
  }

  await db.insert(recipes).values({ title, userId: session.user.id });
  revalidatePath("/dashboard");
  return { ok: true };
}
```

Then call it from a client component:
```tsx
"use client";
import { createRecipe } from "@/app/actions/create-recipe";

<form action={createRecipe}>
  <input name="title" />
  <button type="submit">Create</button>
</form>
```

## Route Handlers (webhooks, public APIs)

```ts
// app/api/contact/route.ts
import { contactSchema } from "@/lib/schemas";
import { resend } from "@/lib/resend";

export async function POST(req: Request) {
  const body = await req.json();
  const parsed = contactSchema.safeParse(body);
  if (!parsed.success) {
    return Response.json(
      { error: parsed.error.flatten() },
      { status: 400 }
    );
  }

  await resend.emails.send({
    from: "noreply@yoursite.com",
    to: "you@yoursite.com",
    subject: `Contact: ${parsed.data.subject}`,
    text: parsed.data.message,
  });

  return Response.json({ ok: true });
}
```

## Third-party integrations

The pattern is always the same:

1. **Install the SDK**: `npm install @company/sdk`
2. **Get your API key** from the provider's dashboard
3. **Add to .env.local** as `PROVIDER_API_KEY=...`
4. **Create a server-only client** in `lib/<provider>.ts`
5. **Import and call** from server components, server actions, or route handlers

```ts
// lib/stripe.ts
import Stripe from "stripe";

export const stripe = new Stripe(process.env.STRIPE_SECRET_KEY!, {
  apiVersion: "2024-11-20.acacia",
});
```

**Common integrations and their packages:**
- Stripe → `stripe`
- Email → `resend`
- AI → `@anthropic-ai/sdk`, `openai`, `@ai-sdk/openai`
- File upload → `@aws-sdk/client-s3`, Supabase Storage SDK, `uploadthing`
- Auth → `@clerk/nextjs`, `@supabase/supabase-js`
- Analytics → `@vercel/analytics`, `posthog-node`
- Error tracking → `@sentry/nextjs`

## Webhooks

Webhooks are how third parties tell you something happened (a payment
succeeded, an email bounced, a user updated their profile).

**The five rules of webhooks:**
1. **Always verify the signature.** Every reputable provider signs webhooks. Verify before trusting any data.
2. **Return 200 fast.** Acknowledge receipt within a few seconds. Process slow work in a background job.
3. **Be idempotent.** The same webhook may arrive twice. Use the event ID to dedupe.
4. **Log everything.** When a webhook breaks, you need the raw payload to debug.
5. **Use a separate endpoint per provider.** Don't mix Stripe and Resend in the same route.

```ts
// app/api/webhooks/stripe/route.ts
import { stripe } from "@/lib/stripe";
import { headers } from "next/headers";

export async function POST(req: Request) {
  const body = await req.text();
  const sig = (await headers()).get("stripe-signature")!;

  let event;
  try {
    event = stripe.webhooks.constructEvent(
      body,
      sig,
      process.env.STRIPE_WEBHOOK_SECRET!
    );
  } catch {
    return new Response("Invalid signature", { status: 400 });
  }

  // Handle the event
  switch (event.type) {
    case "checkout.session.completed":
      // ... fulfill the order
      break;
  }

  return Response.json({ received: true });
}
```

## Rate limiting

Public endpoints need rate limiting. Use Upstash Redis with their rate limit
package — it's serverless-friendly and free at small scale:

```bash
npm install @upstash/ratelimit @upstash/redis
```

```ts
import { Ratelimit } from "@upstash/ratelimit";
import { Redis } from "@upstash/redis";

const ratelimit = new Ratelimit({
  redis: Redis.fromEnv(),
  limiter: Ratelimit.slidingWindow(10, "10 s"), // 10 requests per 10 seconds
});

export async function POST(req: Request) {
  const ip = req.headers.get("x-forwarded-for") ?? "anonymous";
  const { success } = await ratelimit.limit(ip);
  if (!success) {
    return new Response("Too many requests", { status: 429 });
  }
  // ... handle the request
}
```

## Caching

Next.js has aggressive caching. Two key patterns:

**`unstable_cache`** for expensive queries:
```ts
import { unstable_cache } from "next/cache";

const getPopularRecipes = unstable_cache(
  async () => db.select().from(recipes).limit(10),
  ["popular-recipes"],
  { revalidate: 60, tags: ["recipes"] }
);
```

**`revalidatePath` / `revalidateTag`** to invalidate after mutations:
```ts
import { revalidatePath, revalidateTag } from "next/cache";

// After creating a recipe
revalidatePath("/recipes");
revalidateTag("recipes");
```

## Background jobs

For anything that takes more than a few seconds (sending bulk emails,
generating reports, scraping), use a queue. Modern picks:

- **Trigger.dev** — easy DX, built for Next.js
- **Inngest** — durable workflows, good for complex flows
- **Cloudflare Queues** — if you're on Workers
- **Vercel Cron + a route handler** — for simple scheduled tasks

## Common mistakes to avoid

- **Putting secrets in client code.** Server-only env vars must NOT have `NEXT_PUBLIC_` prefix.
- **Calling external APIs from client components.** Proxy through your own route handler instead — keeps the API key safe and avoids CORS.
- **Forgetting to verify webhook signatures.** Without this, anyone can hit your endpoint.
- **Using fetch instead of the SDK.** SDKs handle retries, errors, and types for you.
- **Long-running route handlers.** Vercel functions time out at 10s (Hobby) / 60s (Pro). Use a background job for slow work.
- **Hard-coding URLs.** Use env vars so dev/staging/prod work the same.
- **No error handling.** Every external call can fail — wrap in try/catch.

## Going deeper
- Backend: https://www.codebooks.ai/backend
- API Integration: https://www.codebooks.ai/api-integration
- Environment Variables: https://www.codebooks.ai/env-variables
- Real-Time Features: https://www.codebooks.ai/realtime


---

This skill is part of the **CodeBooks Vibe Coding Skills Library**.
Browse all skills, install guides, and the source chapters at
https://www.codebooks.ai/skills
