Agent skill
nextjs-api-development
REST API development with Next.js 15 App Router. Dual authentication (API Key + Session), dynamic entities, metadata system. Use this skill to create endpoints, validate APIs, or understand API patterns.
Install this agent skill to your Project
npx add-skill https://github.com/majiayu000/claude-skill-registry/tree/main/skills/other/other/nextjs-api-development
SKILL.md
Next.js API Development Skill
Patterns and tools for REST API development with Next.js 15 App Router.
Architecture Overview
app/api/v1/
├── [entity]/ # Dynamic CRUD (auto-generated from registry)
│ ├── route.ts # GET (list) / POST (create)
│ └── [id]/route.ts # GET (read) / PATCH (update) / DELETE
├── (contents)/ # Custom overrides (parentheses = not in URL)
│ └── tasks/ # Example: custom implementation
├── users/ # Core endpoints (not dynamic)
├── api-keys/ # API key management
├── auth/ # Authentication endpoints
├── billing/ # Billing & subscriptions
├── teams/ # Team management
└── theme/ # Theme-specific endpoints
📍 Context-Aware Paths: Core API routes (
app/api/v1/) are read-only in consumer projects. Create custom endpoints incontents/themes/{theme}/app/api/or override via(contents)/pattern. Seecore-theme-responsibilitiesskill for complete rules.
When to Use This Skill
- Creating new API endpoints
- Implementing dual authentication
- Adding custom business logic to entities
- Debugging API issues
- Testing API endpoints
- Understanding API patterns
Core Patterns
Route Handler Pattern (MANDATORY)
All routes MUST use withApiLogging wrapper and addCorsHeaders:
import { NextRequest, NextResponse } from 'next/server'
import { queryWithRLS, mutateWithRLS } from '@/core/lib/db'
import {
createApiResponse,
createApiError,
createPaginationMeta,
parsePaginationParams,
withApiLogging,
handleCorsPreflightRequest,
addCorsHeaders,
} from '@/core/lib/api/helpers'
import { authenticateRequest } from '@/core/lib/api/auth/dual-auth'
// Handle CORS preflight
export async function OPTIONS() {
return handleCorsPreflightRequest()
}
// GET /api/v1/[endpoint] - List resources
export const GET = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
try {
// 1. Authenticate (API Key OR Session)
const authResult = await authenticateRequest(req)
if (!authResult.success) {
return NextResponse.json(
{ success: false, error: 'Authentication required', code: 'AUTHENTICATION_FAILED' },
{ status: 401 }
)
}
if (authResult.rateLimitResponse) {
return authResult.rateLimitResponse as NextResponse
}
// 2. Parse pagination
const { page, limit, offset } = parsePaginationParams(req)
// 3. Fetch data with RLS
const data = await queryWithRLS(
`SELECT * FROM "table" WHERE 1=1 ORDER BY "createdAt" DESC LIMIT $1 OFFSET $2`,
[limit, offset],
authResult.user!.id
)
// 4. Create response with pagination
const paginationMeta = createPaginationMeta(page, limit, total)
const response = createApiResponse(data, paginationMeta)
return addCorsHeaders(response)
} catch (error) {
console.error('[ENDPOINT] Error:', error)
const response = createApiError('Failed to fetch data', 500)
return addCorsHeaders(response)
}
})
Response Helpers (MANDATORY)
import { createApiResponse, createApiError } from '@/core/lib/api/helpers'
// Success responses - always wrap with addCorsHeaders
const response = createApiResponse(data)
return addCorsHeaders(response)
const response = createApiResponse(data, { created: true }, 201)
return addCorsHeaders(response)
const response = createApiResponse(data, paginationMeta)
return addCorsHeaders(response)
// Error responses - always wrap with addCorsHeaders
const response = createApiError('Not found', 404)
return addCorsHeaders(response)
const response = createApiError('Validation error', 400, zodError.issues, 'VALIDATION_ERROR')
return addCorsHeaders(response)
Response Format
// Success
{
"success": true,
"data": { /* entity data */ },
"info": {
"timestamp": "2024-01-17T10:30:00Z",
"pagination": { /* if applicable */ }
}
}
// Error
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"details": { /* optional */ },
"info": {
"timestamp": "2024-01-17T10:30:00Z"
}
}
Route Types
1. Dynamic Entity Routes
For standard CRUD operations. Automatically generated from EntityRegistry.
GET /api/v1/products → List products
POST /api/v1/products → Create product
GET /api/v1/products/123 → Read product
PATCH /api/v1/products/123 → Update product
DELETE /api/v1/products/123 → Delete product
When to use: Standard CRUD, basic validation, standard pagination.
2. Custom Override Routes
For special business logic. Use (contents)/ folder.
// app/api/v1/(contents)/tasks/route.ts
export const POST = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
const authResult = await authenticateRequest(req)
// Custom logic: Admin can create for other users
if (authResult.user?.role === 'admin' && body.userId) {
task.userId = body.userId
} else {
task.userId = authResult.user!.id
}
// ... rest of implementation
const response = createApiResponse(data, { created: true }, 201)
return addCorsHeaders(response)
})
When to use: Complex validation, admin-level operations, external integrations.
3. Core Endpoints
System endpoints that cannot be overridden.
/api/v1/users- User management/api/v1/api-keys- API key management/api/v1/auth- Authentication
Input Validation
import { z } from 'zod'
const CreateProductSchema = z.object({
title: z.string().min(1).max(255),
price: z.number().positive(),
status: z.enum(['active', 'inactive']).default('active'),
})
export const POST = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
try {
const body = CreateProductSchema.parse(await req.json())
// Validated body ready to use
} catch (error) {
if (error instanceof z.ZodError) {
const response = createApiError('Validation error', 400, error.issues, 'VALIDATION_ERROR')
return addCorsHeaders(response)
}
throw error
}
})
Scripts
Scaffold New Endpoint
# Basic endpoint
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--methods GET,POST \
--auth required
# With [id] route for single resource operations
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--methods GET,POST \
--auth required \
--with-id
# Preview without creating files
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--dry-run
Generate CRUD Tests
# Generate test file only
python .claude/skills/nextjs-api-development/scripts/generate-crud-tests.py \
--entity products
# Also generate API Controller
python .claude/skills/nextjs-api-development/scripts/generate-crud-tests.py \
--entity products \
--with-controller
# Preview
python .claude/skills/nextjs-api-development/scripts/generate-crud-tests.py \
--entity products \
--dry-run
Validate API Structure
python .claude/skills/nextjs-api-development/scripts/validate-api.py \
--path app/api/v1/
# Strict mode (exit with error if violations found)
python .claude/skills/nextjs-api-development/scripts/validate-api.py \
--path app/api/v1/ \
--strict
Testing Pattern
Tests use API Controllers that extend BaseAPIController:
// In test file
const ProductsAPIController = require('../../../src/controllers/ProductsAPIController.js')
describe('Products API - CRUD Operations', {
tags: ['@api', '@feat-products', '@crud', '@regression']
}, () => {
let productsAPI: any
const SUPERADMIN_API_KEY = Cypress.env('SUPERADMIN_API_KEY')
const TEAM_ID = Cypress.env('TEAM_ID')
before(() => {
productsAPI = new ProductsAPIController(BASE_URL, SUPERADMIN_API_KEY, TEAM_ID)
})
it('should list products', () => {
productsAPI.getProducts().then((response: any) => {
productsAPI.validateSuccessResponse(response, 200)
expect(response.body.data).to.be.an('array')
})
})
})
Security Best Practices
SQL Injection Prevention
// ✅ CORRECT - Parameterized queries with queryWithRLS
const result = await queryWithRLS(
`SELECT * FROM products WHERE "userId" = $1 AND status = $2 LIMIT $3`,
[userId, status, limit],
authResult.user!.id
)
// ❌ NEVER - String concatenation
const query = `SELECT * FROM products WHERE userId = '${userId}'`
Anti-Patterns
// ❌ NEVER: Use export async function instead of withApiLogging
export async function GET(request: NextRequest) { }
// ❌ NEVER: Return response without addCorsHeaders
return createApiResponse(data)
// ❌ NEVER: Use raw NextResponse.json
return NextResponse.json({ data })
// ❌ NEVER: Skip authentication
export async function GET(request: NextRequest) {
const data = await db.query('SELECT * FROM products')
return createApiResponse(data)
}
// ✅ CORRECT: Full pattern
export const GET = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
const authResult = await authenticateRequest(req)
// ...
const response = createApiResponse(data)
return addCorsHeaders(response)
})
Checklist for New Endpoint
- Uses
withApiLoggingwrapper - Has
OPTIONShandler usinghandleCorsPreflightRequest() - All responses wrapped with
addCorsHeaders() - Dual authentication implemented
- Input validation with Zod schema
- Response helpers used (
createApiResponse,createApiError) - Proper error handling with try/catch
- Pagination for list endpoints using
parsePaginationParams - API scopes added to
core/lib/api/keys.ts - Cypress tests created with API Controller pattern
Recommended Agent Skills
Expand your agent's capabilities with these related and highly-rated skills.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
agent-ops-spec
Manage specification documents in .agent/specs/. Use when user provides requirements, acceptance criteria, or feature descriptions that need to be tracked and validated against implementation.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-testing
Test strategy, execution, and coverage analysis. Use when designing tests, running test suites, or analyzing test results beyond baseline checks.
agent-ops-state
Maintain .agent state files. Use at session start, after meaningful steps, and before concluding: read/update constitution/memory/focus/issues/baseline consistently.
Didn't find tool you were looking for?