Claude Code Plugins

Community-maintained marketplace

Feedback

API route implementation patterns with RLS, Zod validation, and error handling. Use when creating API routes, implementing endpoints, or adding server-side validation.

Install Skill

1Download skill
2Enable skills in Claude

Open claude.ai/settings/capabilities and find the "Skills" section

3Upload to Claude

Click "Upload skill" and select the downloaded ZIP file

Note: Please verify skill by going through its instructions before using it.

SKILL.md

name api-patterns
description API route implementation patterns with RLS, Zod validation, and error handling. Use when creating API routes, implementing endpoints, or adding server-side validation.

API Patterns Skill

Purpose

Route to existing API patterns and provide checklists for safe, validated API route implementation. All API routes MUST use RLS context helpers—see rls-patterns skill.

When This Skill Applies

Invoke this skill when:

  • Creating new API routes
  • Implementing CRUD endpoints
  • Adding request/response validation
  • Handling webhooks
  • Implementing error handling patterns

Authoritative References (MUST READ)

Pattern Location Purpose
User Context API docs/patterns/api/user-context-api.md User-scoped operations
Admin Context API docs/patterns/api/admin-context-api.md Admin-scoped operations
Zod Validation docs/patterns/api/zod-validation-api.md Request/response validation
Webhook Handler docs/patterns/api/webhook-handler.md Webhook processing
Bonus Content docs/patterns/api/bonus-content-delivery.md Protected content delivery

Stop-the-Line Conditions

FORBIDDEN Patterns

// FORBIDDEN: Direct Prisma calls (bypass RLS)
const users = await prisma.user.findMany();
// Must use: withUserContext, withAdminContext, or withSystemContext

// FORBIDDEN: Missing authentication check
export async function GET(req: Request) {
  return getUserData(); // No auth check!
}

// FORBIDDEN: Unvalidated user input
const { userId } = await req.json();
// Must validate with Zod schema

// FORBIDDEN: Generic error responses
return new Response("Error", { status: 500 });
// Must use structured error response

CORRECT Patterns

// CORRECT: RLS context + auth check
export async function GET(req: Request) {
  const { userId } = await auth();
  if (!userId) {
    return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
  }

  const data = await withUserContext(prisma, userId, async (client) => {
    return client.user.findUnique({ where: { user_id: userId } });
  });

  return NextResponse.json(data);
}

// CORRECT: Zod validation
const schema = z.object({
  email: z.string().email(),
  name: z.string().min(1),
});

const result = schema.safeParse(body);
if (!result.success) {
  return NextResponse.json(
    { error: "Validation failed", details: result.error.flatten() },
    { status: 400 },
  );
}

API Route Checklist

Before ANY API route:

  • Authentication check with await auth() from Clerk
  • Proper 401 response for unauthenticated
  • Request validation with Zod schema
  • RLS context wrapper (withUserContext/withAdminContext/withSystemContext)
  • Structured error responses with appropriate status codes
  • TypeScript types for request/response

Standard Response Patterns

Success Response

return NextResponse.json({ data, success: true }, { status: 200 });

Error Response

return NextResponse.json(
  {
    error: "Human-readable error message",
    code: "ERROR_CODE",
    details: optional_details,
  },
  { status: 400 | 401 | 403 | 404 | 500 },
);

Status Codes

Code When to Use
200 Success
201 Created (POST)
400 Bad request / validation error
401 Not authenticated
403 Forbidden (authenticated but not authorized)
404 Resource not found
500 Server error

API Route Template

import { auth } from "@clerk/nextjs/server";
import { NextResponse } from "next/server";
import { z } from "zod";
import { withUserContext } from "@/lib/rls-helpers";
import { prisma } from "@/lib/prisma";

// Request validation schema
const RequestSchema = z.object({
  // Define expected fields
});

export async function POST(req: Request) {
  try {
    // 1. Authenticate
    const { userId } = await auth();
    if (!userId) {
      return NextResponse.json({ error: "Unauthorized" }, { status: 401 });
    }

    // 2. Parse and validate request
    const body = await req.json();
    const result = RequestSchema.safeParse(body);
    if (!result.success) {
      return NextResponse.json(
        { error: "Validation failed", details: result.error.flatten() },
        { status: 400 },
      );
    }

    // 3. Execute with RLS context
    const data = await withUserContext(prisma, userId, async (client) => {
      return client.resource.create({ data: result.data });
    });

    // 4. Return success response
    return NextResponse.json({ data, success: true }, { status: 201 });
  } catch (error) {
    console.error("API error:", error);
    return NextResponse.json(
      { error: "Internal server error" },
      { status: 500 },
    );
  }
}

API Documentation Template

For documenting new endpoints:

## Endpoint: POST /api/resource

### Description

Creates a new resource for the authenticated user.

### Authentication

Required: Clerk session

### Request Body

| Field | Type   | Required | Description   |
| ----- | ------ | -------- | ------------- |
| name  | string | Yes      | Resource name |
| type  | string | No       | Resource type |

### Response

**Success (201)**:
\`\`\`json
{ "data": { "id": 1, "name": "..." }, "success": true }
\`\`\`

**Error (400)**:
\`\`\`json
{ "error": "Validation failed", "details": {...} }
\`\`\`

### RLS Context

Uses `withUserContext` - user can only access own resources.

Related Skills

  • rls-patterns: RLS context helper usage (REQUIRED for all DB operations)
  • security-audit: API security validation
  • testing-patterns: API endpoint testing