| name | nextjs-api-development |
| description | 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.
|
| allowed-tools | Read, Glob, Grep, Bash(python:*) |
| version | 1.1.0 |
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 in contents/themes/{theme}/app/api/ or override via (contents)/ pattern.
See core-theme-responsibilities skill 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'
export async function OPTIONS() {
return handleCorsPreflightRequest()
}
export const GET = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
try {
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
}
const { page, limit, offset } = parsePaginationParams(req)
const data = await queryWithRLS(
`SELECT * FROM "table" WHERE 1=1 ORDER BY "createdAt" DESC LIMIT $1 OFFSET $2`,
[limit, offset],
authResult.user!.id
)
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'
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)
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": true,
"data": { },
"info": {
"timestamp": "2024-01-17T10:30:00Z",
"pagination": { }
}
}
{
"success": false,
"error": "Error message",
"code": "ERROR_CODE",
"details": { },
"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.
export const POST = withApiLogging(async (req: NextRequest): Promise<NextResponse> => {
const authResult = await authenticateRequest(req)
if (authResult.user?.role === 'admin' && body.userId) {
task.userId = body.userId
} else {
task.userId = authResult.user!.id
}
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())
} 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
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--methods GET,POST \
--auth required
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--methods GET,POST \
--auth required \
--with-id
python .claude/skills/nextjs-api-development/scripts/scaffold-endpoint.py \
--name products \
--dry-run
Generate CRUD Tests
python .claude/skills/nextjs-api-development/scripts/generate-crud-tests.py \
--entity products
python .claude/skills/nextjs-api-development/scripts/generate-crud-tests.py \
--entity products \
--with-controller
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/
python .claude/skills/nextjs-api-development/scripts/validate-api.py \
--path app/api/v1/ \
--strict
Testing Pattern
Tests use API Controllers that extend BaseAPIController:
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
const result = await queryWithRLS(
`SELECT * FROM products WHERE "userId" = $1 AND status = $2 LIMIT $3`,
[userId, status, limit],
authResult.user!.id
)
const query = `SELECT * FROM products WHERE userId = '${userId}'`
Anti-Patterns
export async function GET(request: NextRequest) { }
return createApiResponse(data)
return NextResponse.json({ data })
export async function GET(request: NextRequest) {
const data = await db.query('SELECT * FROM products')
return createApiResponse(data)
}
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