api-development
Skill1 starsChanged 10 months agoMITView on GitHub
Complete API development guide for Next.js 15 App Router with REST endpoints, error handling, validation, authentication, security best practices, and performance optimization. Use when creating API routes, handling requests, building CRUD operations, or implementing API features.
10 other projects use a version of this.
Adopted by
Most recently taken up by
- lubusIN/frappe-skillstheir own version, first seen Nov 4, 2026
- weininghui/skillstheir own version, first seen Oct 30, 2026
- Mindrally/skillstheir own version, first seen Oct 8, 2026
- bkywksj/knowledge-basetheir own version, first seen Oct 8, 2026
- hmislk/hmistheir own version, first seen Oct 8, 2026
- jokob-sk/NetAlertXtheir own version, first seen Oct 8, 2026
Used with
Projects with it also use git-workflow.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
Microsoft AzureSkillgit-workflow
Squad branching model: dev-first workflow with insiders preview channel
Also in 4 projects that use this
Other versions10 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 10
- jokob-sk/NetAlertX★ 7.4ksame name
- netalertx/NetAlertX★ 7.4ksame name
- bkywksj/knowledge-base★ 340same name
- Mindrally/skills★ 275same name
- hmislk/hmis★ 244same name
- lubusIN/frappe-skills★ 68same name
- yusupsupriyadi/claude-code-mastering★ 17same name
- wpank/ai★ 11same name
- weininghui/skills★ 1same name
- hallucinaut/skills★ 0same name
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository13 other files it gives its agents.
- docxSkill
- frontend-designSkill
- maplibre-gl-jsSkill
- nextjs-fullstackSkill
- pdfSkill
- performance-optimizationSkill
- pptxSkill
- prisma-databaseSkill
- shadcnSkill
- tanstackSkill
- testing-qaSkill
- typescriptSkill
- xlsxSkill
The file
Show its rules and text
We split this skill into the rules it gives an agent
14 rules. 0 are common, 11 are distinctive, and 3 are very specific to this project.
0
Common
Most of the 9 edited versions we read keep these.
11
Distinctive
This author's own choices. Worth a look.
3
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 97
❌ INEFFICIENT: Write full try-catch every time
- Distinctive: line 99
✅ EFFICIENT: Create utility functions
- Distinctive: line 669
✅ Solution: Always wrap in try-catch with centralized handler
- Distinctive: line 673
✅ Solution: Always use Zod schemas for validation
- Very specific: line 677
✅ Solution: Use select to fetch only needed fields
- Very specific: line 681
✅ Solution: Use success() and error() utility functions
- Distinctive: line 747
Use API routes: - Prefer over server actions
- Distinctive: line 748
Validate everything: - Use Zod for all inputs
- Very specific: line 749
Standard responses: - Use success() and error() helpers
- Distinctive: line 750
Handle errors: - Always use try-catch
- Distinctive: line 751
Select only needed fields: - Avoid over-fetching
- Distinctive: line 752
Add caching: - Use Cache-Control headers
- Distinctive: line 753
Secure endpoints: - Add auth where needed
- Distinctive: line 754
Consistent naming: - Follow REST conventions
“Common” is measured: kept by at least 6 in 10 of the 9 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
API Development Skill
Skill Location: {project_path}/skills/api-development/
Comprehensive guide for building production-ready APIs in Next.js 15 App Router, optimized for minimal token usage while maintaining security and performance.
When to Use This Skill (Trigger Patterns)
MUST apply this skill when:
- Creating API routes in Next.js App Router
- Implementing REST endpoints (GET, POST, PUT, DELETE)
- Handling error responses
- Validating request data
- Adding authentication/authorization
- Securing API routes
- Optimizing API performance
- Implementing pagination, filtering, sorting
Trigger phrases:
- "create an API endpoint"
- "add API route"
- "handle POST/GET/PUT/DELETE"
- "validate API requests"
- "add authentication to API"
- "secure API route"
Core Principles
1. RESTful Design
GET /api/resource - List all
GET /api/resource/:id - Get by ID
POST /api/resource - Create new
PUT /api/resource/:id - Update by ID
DELETE /api/resource/:id - Delete by ID2. Standard Response Format
// Success
{
"success": true,
"data": { ... }
}
// Error
{
"success": false,
"error": {
"message": "Error message",
"code": "ERROR_CODE"
}
}3. HTTP Status Codes
200- OK (successful GET, PUT, PATCH)201- Created (successful POST)400- Bad Request (validation errors)401- Unauthorized (not logged in)403- Forbidden (no permission)404- Not Found500- Internal Server Error
Token-Saving Strategies
1. Use Standard Patterns Without Explanation
❌ INEFFICIENT:
"This function handles the GET request and returns JSON..."✅ EFFICIENT:
// Standard GET handler2. Minimize Error Handling Boilerplate
❌ INEFFICIENT: Write full try-catch every time
✅ EFFICIENT: Create utility functions
// src/lib/api-response.ts
export const success = (data: any, status = 200) =>
NextResponse.json({ success: true, data }, { status })
export const error = (message: string, status = 500, code?: string) =>
NextResponse.json(
{ success: false, error: { message, code } },
{ status }
)3. Assume Knowledge of Basics
Don't explain:
- What
NextRequest,NextResponseare - How HTTP methods work
- Basic error handling concepts
Focus on:
- Implementation patterns
- Security considerations
- Performance optimization
API Route Templates
1. Basic CRUD Routes
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { success, error } from '@/lib/api-response'
// GET /api/users
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const [users, total] = await Promise.all([
db.user.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
}),
db.user.count()
])
return success({
users,
pagination: { page, limit, total }
})
} catch (e) {
return error('Internal error', 500)
}
}
// POST /api/users
export async function POST(req: NextRequest) {
try {
const body = await req.json()
const user = await db.user.create({ data: body })
return success(user, 201)
} catch (e) {
return error('Failed to create user', 500)
}
}2. Single Resource Routes
// src/app/api/users/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { success, error } from '@/lib/api-response'
// GET /api/users/[id]
export async function GET(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await db.user.findUnique({
where: { id: params.id }
})
if (!user) {
return error('User not found', 404, 'NOT_FOUND')
}
return success(user)
} catch (e) {
return error('Internal error', 500)
}
}
// PUT /api/users/[id]
export async function PUT(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const body = await req.json()
const user = await db.user.update({
where: { id: params.id },
data: body
})
return success(user)
} catch (e) {
return error('Failed to update user', 500)
}
}
// DELETE /api/users/[id]
export async function DELETE(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
await db.user.delete({
where: { id: params.id }
})
return success({ success: true })
} catch (e) {
return error('Failed to delete user', 500)
}
}Validation Patterns
1. Zod Validation
import { z } from 'zod'
// src/lib/validations.ts
export const userSchema = z.object({
name: z.string().min(2, 'Name must be at least 2 characters'),
email: z.string().email('Invalid email address'),
role: z.enum(['USER', 'ADMIN']).default('USER')
})
// Usage in API
export async function POST(req: NextRequest) {
try {
const body = await req.json()
const validated = userSchema.parse(body)
const user = await db.user.create({ data: validated })
return success(user, 201)
} catch (e) {
if (e instanceof z.ZodError) {
return error(e.errors[0].message, 400, 'VALIDATION_ERROR')
}
return error('Internal error', 500)
}
}2. Query Parameter Validation
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const page = Math.max(1, parseInt(searchParams.get('page') || '1'))
const limit = Math.min(100, Math.max(1, parseInt(searchParams.get('limit') || '10')))
const sort = searchParams.get('sort') || 'createdAt'
const order = searchParams.get('order') === 'asc' ? 'asc' : 'desc'
// ... use validated params
}Error Handling
1. Centralized Error Handler
// src/lib/api-error.ts
export class ApiError extends Error {
constructor(
public message: string,
public status: number,
public code?: string
) {
super(message)
this.name = 'ApiError'
}
}
export function handleApiError(e: unknown): NextResponse {
if (e instanceof ApiError) {
return error(e.message, e.status, e.code)
}
if (e instanceof z.ZodError) {
return error(e.errors[0].message, 400, 'VALIDATION_ERROR')
}
console.error('Unhandled error:', e)
return error('Internal server error', 500)
}
// Usage
export async function POST(req: NextRequest) {
try {
// ... logic
} catch (e) {
return handleApiError(e)
}
}2. Custom Errors
// Not found
throw new ApiError('User not found', 404, 'NOT_FOUND')
// Validation failed
throw new ApiError('Invalid email format', 400, 'VALIDATION_ERROR')
// Unauthorized
throw new ApiError('Authentication required', 401, 'UNAUTHORIZED')
// Forbidden
throw new ApiError('Insufficient permissions', 403, 'FORBIDDEN')Authentication & Authorization
1. Basic Auth Check
// src/lib/auth.ts
import { cookies } from 'next/headers'
export async function getCurrentUser() {
const cookieStore = await cookies()
const token = cookieStore.get('auth-token')
if (!token) {
throw new ApiError('Unauthorized', 401, 'UNAUTHORIZED')
}
// Verify token and return user
const user = await verifyToken(token.value)
return user
}
// Usage in API
export async function GET(req: NextRequest) {
try {
const user = await getCurrentUser()
// ... use user data
} catch (e) {
return handleApiError(e)
}
}2. Role-Based Access Control
// src/lib/auth.ts
export function requireRole(user: User, ...roles: string[]) {
if (!roles.includes(user.role)) {
throw new ApiError(
'Insufficient permissions',
403,
'FORBIDDEN'
)
}
}
// Usage
export async function DELETE(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser()
requireRole(user, 'ADMIN')
await db.user.delete({ where: { id: params.id } })
return success({ success: true })
} catch (e) {
return handleApiError(e)
}
}3. Resource Ownership Check
export async function checkOwnership(
user: User,
resource: any,
ownerField: string = 'userId'
) {
if (resource[ownerField] !== user.id && user.role !== 'ADMIN') {
throw new ApiError('Access denied', 403, 'FORBIDDEN')
}
}
// Usage
export async function PUT(
req: NextRequest,
{ params }: { params: { id: string } }
) {
try {
const user = await getCurrentUser()
const post = await db.post.findUnique({
where: { id: params.id }
})
if (!post) {
throw new ApiError('Post not found', 404, 'NOT_FOUND')
}
checkOwnership(user, post)
// ... update
} catch (e) {
return handleApiError(e)
}
}Advanced Patterns
1. Pagination
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const page = parseInt(searchParams.get('page') || '1')
const limit = parseInt(searchParams.get('limit') || '10')
const [items, total] = await Promise.all([
db.item.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
}),
db.item.count()
])
return success({
items,
pagination: {
page,
limit,
total,
pages: Math.ceil(total / limit),
hasNext: page * limit < total,
hasPrev: page > 1
}
})
}2. Filtering
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const filters: any = {}
// Text search
const search = searchParams.get('search')
if (search) {
filters.OR = [
{ name: { contains: search } },
{ email: { contains: search } }
]
}
// Exact matches
const status = searchParams.get('status')
if (status) {
filters.status = status
}
// Date range
const from = searchParams.get('from')
const to = searchParams.get('to')
if (from || to) {
filters.createdAt = {}
if (from) filters.createdAt.gte = new Date(from)
if (to) filters.createdAt.lte = new Date(to)
}
const items = await db.item.findMany({
where: filters
})
return success(items)
}3. Sorting
export async function GET(req: NextRequest) {
const { searchParams } = new URL(req.url)
const sortBy = searchParams.get('sortBy') || 'createdAt'
const sortOrder = searchParams.get('order') || 'desc'
const items = await db.item.findMany({
orderBy: {
[sortBy]: sortOrder
}
})
return success(items)
}4. File Upload
export async function POST(req: NextRequest) {
try {
const formData = await req.formData()
const file = formData.get('file') as File
if (!file) {
throw new ApiError('No file uploaded', 400, 'NO_FILE')
}
// Validate file
const maxSize = 5 * 1024 * 1024 // 5MB
if (file.size > maxSize) {
throw new ApiError('File too large', 400, 'FILE_TOO_LARGE')
}
const bytes = await file.arrayBuffer()
const buffer = Buffer.from(bytes)
// Save file or upload to cloud storage
const url = await saveFile(buffer, file.name)
return success({ url })
} catch (e) {
return handleApiError(e)
}
}Performance Optimization
1. Caching
// Cache-Control headers
export async function GET(req: NextRequest) {
const data = await db.item.findMany()
return NextResponse.json(
{ success: true, data },
{
headers: {
'Cache-Control': 'public, s-maxage=60, stale-while-revalidate=300'
}
}
)
}2. Select Only Needed Fields
// Bad: Fetches all fields
const users = await db.user.findMany()
// Good: Selects only needed fields
const users = await db.user.findMany({
select: {
id: true,
name: true,
email: true
}
})3. Use Prisma Transactions
await db.$transaction([
db.order.create({ data: orderData }),
db.product.updateMany({
where: { id: { in: productIds } },
data: { stock: { decrement: 1 } }
})
])Security Best Practices
1. Input Sanitization
const schema = z.object({
email: z.string().email().max(255),
name: z.string().min(2).max(100).trim()
})2. Rate Limiting
// Simple rate limiting with localStorage
// For production, use Redis or dedicated rate limiting3. CORS
export const OPTIONS = async (req: NextRequest) => {
return new NextResponse(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization'
}
})
}4. SQL Injection Prevention
Prisma automatically handles SQL injection through parameterized queries:
// Safe - Prisma handles escaping
const users = await db.user.findMany({
where: {
name: { contains: userInput } // Automatically escaped
}
})Common Pitfalls & Solutions
❌ Problem: Missing Error Handling
✅ Solution: Always wrap in try-catch with centralized handler
❌ Problem: Not Validating Input
✅ Solution: Always use Zod schemas for validation
❌ Problem: Over-fetching Data
✅ Solution: Use select to fetch only needed fields
❌ Problem: Inconsistent Response Format
✅ Solution: Use success() and error() utility functions
Token-Efficient Prompt Templates
Create CRUD API
Create CRUD API for <RESOURCE>:
- Routes: GET, POST, PUT, DELETE
- Validation with Zod
- Standard response format
- Error handlingAdd Authentication
Add auth to /api/<resource>:
- Check authentication
- Add authorization for specific roles
- Handle auth errorsAdd Filtering/Sorting
Add filtering to GET /api/<resource>:
- Search by <fields>
- Filter by <fields>
- Sort by <fields>
- PaginationQuick Reference
// Standard imports
import { NextRequest, NextResponse } from 'next/server'
import { db } from '@/lib/db'
import { z } from 'zod'
// Response helpers
const success = (data: any, status = 200) => NextResponse.json({ success: true, data }, { status })
const error = (message: string, status = 500) => NextResponse.json({ success: false, error: { message } }, { status })
// HTTP methods
export async function GET(req: NextRequest) { }
export async function POST(req: NextRequest) { }
export async function PUT(req: NextRequest, { params }: any) { }
export async function DELETE(req: NextRequest, { params }: any) { }
// Route params
export async function GET(req: NextRequest, { params }: { params: { id: string } }) {
const id = params.id
}
// Query params
const { searchParams } = new URL(req.url)
const page = searchParams.get('page')Important Reminders
- Use API routes - Prefer over server actions
- Validate everything - Use Zod for all inputs
- Standard responses - Use
success()anderror()helpers - Handle errors - Always use try-catch
- Select only needed fields - Avoid over-fetching
- Add caching - Use Cache-Control headers
- Secure endpoints - Add auth where needed
- Consistent naming - Follow REST conventions
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

