api-route-creator

API Route Creation Skill

Safety Notice

This listing is imported from skills.sh public index metadata. Review upstream SKILL.md and repository scripts before running.

Copy this and send it to your AI assistant to learn

Install skill "api-route-creator" with this command: npx skills add omerakben/omer-akben/omerakben-omer-akben-api-route-creator

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

Source Transparency

This detail page is rendered from real SKILL.md content. Trust labels are metadata-based hints, not a safety guarantee.

Related Skills

Related by shared tags or category signals.

General

bundle-optimization

No summary provided by upstream source.

Repository SourceNeeds Review
General

doc-coauthoring

No summary provided by upstream source.

Repository SourceNeeds Review
General

data-architecture

No summary provided by upstream source.

Repository SourceNeeds Review
General

mcp-builder

No summary provided by upstream source.

Repository SourceNeeds Review