api-patterns
Patterns standard pour les API Next.js. Utiliser pour créer, modifier ou débugger des endpoints.AUTO-ACTIVATE when user mentions (FR/EN): - API, endpoint, route, /api/, NextResponse - GET, POST, PUT, DELETE, PATCH - 500 error, 400 error, API error - créer API, create API, new endpoint - Prisma dans API, fetch, response
When & Why to Use This Skill
This Claude skill provides standardized patterns and best practices for Next.js API development, specifically designed to streamline the creation, modification, and debugging of endpoints. It integrates essential features like NextAuth authentication, Prisma ORM operations, Zod schema validation, and uniform error handling, helping developers maintain a consistent and robust backend architecture while significantly reducing token consumption compared to manual searches.
Use Cases
- Generating secure POST/PUT/PATCH endpoints with mandatory Zod validation and Prisma data persistence.
- Implementing standardized GET routes that include authentication checks, query parameter parsing, and automated PascalCase to camelCase response mapping.
- Setting up dynamic API routes (e.g., [id]) with proper handling for Next.js 15 async params and resource-specific error codes.
- Debugging and refactoring existing API endpoints to comply with uniform status codes and standardized error logging.
- Ensuring database integrity by applying mandatory 'updatedAt' timestamps and site-specific filtering across all API operations.
| name | api-patterns |
|---|---|
| description | > |
API Patterns - Quick Reference
Token Efficiency: ~600 tokens vs 5k+ pour recherche manuelle Last Update: 25 November 2025
Quick Stats
- 470+ endpoints dans /api/
- Pattern standard avec error handling uniforme
- Validation Zod obligatoire sur POST/PUT/PATCH
- Mapping PascalCase→camelCase pour réponses
Pattern Standard - GET
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
export async function GET(req: NextRequest) {
try {
// 1. Auth check
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
// 2. Parse query params
const { searchParams } = new URL(req.url);
const siteId = searchParams.get('siteId');
// 3. Prisma query avec relations
const items = await prisma.model.findMany({
where: { siteId: siteId || undefined },
include: {
RelatedModel: true, // PascalCase from Prisma
},
orderBy: { createdAt: 'desc' }
});
// 4. Map PascalCase → camelCase pour frontend
const mapped = items.map(item => ({
...item,
relatedModel: item.RelatedModel, // camelCase for frontend
RelatedModel: undefined,
}));
return NextResponse.json(mapped);
} catch (error) {
console.error('GET /api/xxx error:', error);
return NextResponse.json(
{ error: 'Erreur serveur' },
{ status: 500 }
);
}
}
Pattern Standard - POST avec Validation
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
import { z } from 'zod';
// Schema Zod OBLIGATOIRE
const createSchema = z.object({
name: z.string().min(1, 'Nom requis').max(100),
siteId: z.string().min(1, 'Site requis'),
type: z.enum(['TYPE_A', 'TYPE_B']),
description: z.string().optional(),
});
export async function POST(req: NextRequest) {
try {
// 1. Auth check
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
// 2. Parse et validate body
const body = await req.json();
const validation = createSchema.safeParse(body);
if (!validation.success) {
return NextResponse.json(
{ error: 'Données invalides', details: validation.error.flatten() },
{ status: 400 }
);
}
const data = validation.data;
// 3. Business logic checks
const exists = await prisma.model.findFirst({
where: { name: data.name, siteId: data.siteId }
});
if (exists) {
return NextResponse.json(
{ error: 'Un élément avec ce nom existe déjà' },
{ status: 409 }
);
}
// 4. Create avec updatedAt OBLIGATOIRE (pas de @default)
const created = await prisma.model.create({
data: {
...data,
createdById: session.user.id,
updatedAt: new Date(), // REQUIS - pas de @default
}
});
return NextResponse.json(created, { status: 201 });
} catch (error) {
console.error('POST /api/xxx error:', error);
return NextResponse.json(
{ error: 'Erreur serveur' },
{ status: 500 }
);
}
}
Pattern Standard - Dynamic Route [id]
// app/api/items/[id]/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '@/lib/auth';
import { prisma } from '@/lib/prisma';
type RouteParams = { params: Promise<{ id: string }> };
export async function GET(req: NextRequest, { params }: RouteParams) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const { id } = await params; // Await params in Next.js 15
// Parse ID selon le type (Int vs String)
const numericId = parseInt(id, 10);
if (isNaN(numericId)) {
return NextResponse.json({ error: 'ID invalide' }, { status: 400 });
}
const item = await prisma.model.findUnique({
where: { id: numericId },
include: { RelatedModel: true }
});
if (!item) {
return NextResponse.json({ error: 'Non trouvé' }, { status: 404 });
}
// Map pour frontend
return NextResponse.json({
...item,
relatedModel: item.RelatedModel,
RelatedModel: undefined,
});
} catch (error) {
console.error('GET /api/items/[id] error:', error);
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
}
}
export async function DELETE(req: NextRequest, { params }: RouteParams) {
try {
const session = await getServerSession(authOptions);
if (!session?.user) {
return NextResponse.json({ error: 'Non autorisé' }, { status: 401 });
}
const { id } = await params;
const numericId = parseInt(id, 10);
await prisma.model.delete({
where: { id: numericId }
});
return NextResponse.json({ success: true });
} catch (error) {
console.error('DELETE /api/items/[id] error:', error);
return NextResponse.json({ error: 'Erreur serveur' }, { status: 500 });
}
}
Checklist Création API
- Auth check avec
getServerSession(authOptions) - Validation Zod pour POST/PUT/PATCH
-
updatedAt: new Date()sur create/update -
siteIdinclus si modèle le requiert - Mapping PascalCase→camelCase pour réponses
- Error handling avec status codes appropriés
- Logs
console.errorpour debugging
Status Codes Standards
| Code | Usage |
|---|---|
| 200 | GET success, PUT/PATCH success |
| 201 | POST success (création) |
| 400 | Validation error, bad request |
| 401 | Non authentifié |
| 403 | Non autorisé (permission) |
| 404 | Ressource non trouvée |
| 409 | Conflit (doublon) |
| 500 | Erreur serveur |
Ressources Progressives
resources/error-handling.md- Patterns d'erreur détaillésresources/pagination.md- Pagination avec cursorsresources/batch-operations.md- Opérations en lot
Reference: PRISMA_RELATIONS_MAPPING.md, ARCHITECTURE_INDEX.md