Claude-skill-registry api-route-creator
Creates Next.js 16 API routes with auth, validation, and tenant scoping. Use when creating API endpoints.
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/api-route-creator" ~/.claude/skills/majiayu000-claude-skill-registry-api-route-creator && rm -rf "$T"
manifest:
skills/data/api-route-creator/SKILL.mdsource content
API Route Creation Skill
When to Use
Use this skill when creating:
- New API endpoints
- Route handlers
- Server actions
Security Requirements (NEVER VIOLATE)
- Always authenticate - Check session
- Always scope by tenant - Use session.user.tenantId
- Always validate input - Use Zod schemas
- Never trust user input - Especially tenant_id
- Log sensitive ops - Audit trail
Template: API Route Handler
import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/lib/auth/config'; import { z } from 'zod'; import { db } from '@/lib/db'; import { eq, and } from 'drizzle-orm'; import { tableName } from '@/lib/db/schema'; import { auditLogger } from '@/lib/audit/logger'; // Input validation schema const inputSchema = z.object({ name: z.string().min(1).max(100).trim(), description: z.string().max(500).optional(), }); // GET - Read (with tenant scoping) export async function GET(request: NextRequest) { try { const session = await auth(); // Authentication check if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Role check (if needed) if (!['teacher', 'tenant_admin'].includes(session.user.role)) { return NextResponse.json( { error: 'Forbidden' }, { status: 403 } ); } // ✅ CRITICAL: Always scope by tenant const data = await db.query.tableName.findMany({ where: eq(tableName.tenantId, session.user.tenantId), }); return NextResponse.json({ data }); } catch (error) { console.error('[API] Error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } } // POST - Create export async function POST(request: NextRequest) { try { const session = await auth(); if (!session?.user) { return NextResponse.json( { error: 'Unauthorized' }, { status: 401 } ); } // Parse and validate input const body = await request.json(); const validatedInput = inputSchema.parse(body); // ✅ CRITICAL: Use session tenant, NEVER request body const [created] = await db.insert(tableName).values({ ...validatedInput, tenantId: session.user.tenantId, // FROM SESSION ONLY createdBy: session.user.id, }).returning(); // Audit log sensitive operations await auditLogger.log({ action: 'CREATE', resourceType: 'RESOURCE_NAME', resourceId: created.id, userId: session.user.id, tenantId: session.user.tenantId, }); return NextResponse.json({ data: created }, { status: 201 }); } catch (error) { if (error instanceof z.ZodError) { return NextResponse.json( { error: 'Validation error', details: error.errors }, { status: 400 } ); } console.error('[API] Error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }
Template: Dynamic Route ([id])
import { NextRequest, NextResponse } from 'next/server'; import { auth } from '@/lib/auth/config'; import { db } from '@/lib/db'; import { eq, and } from 'drizzle-orm'; import { tableName } from '@/lib/db/schema'; interface RouteContext { params: Promise<{ id: string }>; } export async function GET( request: NextRequest, context: RouteContext ) { try { const session = await auth(); if (!session?.user) { return NextResponse.json({ error: 'Unauthorized' }, { status: 401 }); } // ✅ Await params in Next.js 16 const { id } = await context.params; // ✅ CRITICAL: Scope by BOTH id AND tenant const item = await db.query.tableName.findFirst({ where: and( eq(tableName.id, id), eq(tableName.tenantId, session.user.tenantId) ), }); if (!item) { return NextResponse.json({ error: 'Not found' }, { status: 404 }); } return NextResponse.json({ data: item }); } catch (error) { console.error('[API] Error:', error); return NextResponse.json( { error: 'Internal server error' }, { status: 500 } ); } }
Error Response Format
// Standard error responses (FERPA-safe - no data leakage) { error: 'Unauthorized' } // 401 - Not authenticated { error: 'Forbidden' } // 403 - Wrong role { error: 'Not found' } // 404 - Doesn't exist or wrong tenant { error: 'Validation error', details: [...] } // 400 - Bad input { error: 'Internal server error' } // 500 - Something broke // ❌ NEVER expose internal details { error: `Item ${id} not found in tenant ${tenantId}` } // WRONG!
Role Hierarchy
| Role | Can Access |
|---|---|
| student | Own data, joined assistants |
| teacher | Own assistants, class students |
| tenant_admin | All tenant data, user management |
| platform_admin | Everything (cross-tenant) |
Checklist
- Session authentication
- Role-based authorization
- Tenant scoping on ALL queries
- Input validation with Zod
- Params awaited (Next.js 16)
- FERPA-safe error messages
- Audit logging for sensitive ops
- TypeScript types complete