Scaffolding de routes API Next.js

VérifiéSûr

Génère et révise les routes API Next.js App Router avec handleRoute, requireRole, validation Zod et pagination par curseur.

Spar Skills Guide Bot
DeveloppementIntermédiaire
21/10/2026
Claude Code
#nextjs#api-routes#error-handling#cursor-pagination#zod

Recommandé pour

Synthèse automatique

Encadre la création et la révision des routes API Next.js App Router d'un dépôt donné, en imposant le triptyque handleRoute / requireRole / parseJsonBody et la pagination par curseur.

Points forts

  • Fournit un squelette de route.ts directement copiable, avec les imports et les helpers exacts du dépôt
  • Explique le rôle central de handleRoute (enveloppe d'erreur, mapping P2025 vers 404, trace_id) plutôt que de l'imposer sans justification
  • Donne l'idiome complet de pagination par curseur : take limit+1, tie-break sur id, page.next_cursor
  • Rattache chaque règle à un précédent concret (finding QA H-1, revue HI-1) ce qui rend les consignes crédibles et vérifiables

Limites

  • La section « Common Mistakes » est tronquée : le tableau est annoncé mais vide, donc la liste des pièges reste incomplète
  • Fortement couplé à un dépôt précis (chemins services/app/src/app/api, modules auth/http, api/openapi) et inutilisable tel quel ailleurs
  • Suppose connus les helpers parseQuery, ok/created/noContent et la hiérarchie AppError, dont le code n'est pas fourni
Quand l'utiliser

À utiliser dès qu'on ajoute une méthode HTTP à une route existante ou qu'on crée un endpoint Next.js dans ce dépôt, y compris pour relire un diff de route.ts.

Quand l'éviter

À éviter sur un autre projet ou pour des routes non App Router (Pages Router, Express, tRPC), dont les conventions et helpers diffèrent.

Analyse de sécurité

Sûr
Score qualité91/100

The skill is pure documentation: it prescribes a coding convention for Next.js API routes (wrapping handlers in handleRoute, requireRole auth checks, zod validation, cursor pagination). It contains no shell commands, no network or file-system operations, no credential handling, and no instructions to bypass safety controls. Code samples are illustrative TypeScript calling internal helpers, and the only executable guidance is 'avoid hand-rolled try/catch', which is a quality convention rather than a risky action. No prompt-injection or role-manipulation attempt was present in the content.

Aucun point d'attention détecté

Comment cette analyse est faite

Exemples

New list endpoint
Add a GET /api/products endpoint that lists products with an optional name filter and cursor pagination, following the API route scaffold conventions.
New method on existing route
Add a DELETE handler to services/app/src/app/api/stock/entries/route.ts that requires the stock:write role and returns 204.
Review a route diff
Review this route.ts diff for missing requireRole or handleRoute calls, raw req.json() parsing, and ad-hoc try/catch error handling.

name: api-route-scaffold description: Use when adding a new Next.js App Router API route under services/app/src/app/api/** — new endpoint file (route.ts), new HTTP method on an existing route, or a new list/GET endpoint needing pagination. Also use when reviewing a route.ts diff for missing requireRole/handleRoute or ad-hoc error handling.

API Route Scaffold

Overview

All 46+ route files under services/app/src/app/api/**/route.ts follow one shape: handleRoute(async () => { requireRole/requireSession; parseJsonBody/parseQuery; call the domain module; return ok/created/noContent }). handleRoute (in services/app/src/lib/http.ts) is the ONLY place that translates errors to the API's standard {code, message, details, trace_id} envelope (api/openapi/_common.yaml) — domain code never formats an HTTP response itself. Deviating from this (hand-rolled try/catch, skipping requireRole, validating with raw await req.json()) reintroduces exactly the bug QA finding H-1 fixed: an uncaught Prisma P2025 ("no record found") falling through to a generic 500 instead of the documented 404.

The Pattern (evidence: stock/entries/route.ts, products/route.ts)

import { z } from "zod";
import { requireRole } from "@/modules/auth";
import { doTheThing } from "@/modules/<domain>";
import { created, handleRoute, ok, parseJsonBody, parseQuery } from "@/lib/http";

const BodySchema = z.object({ /* zod schema mirroring api/openapi/<domain>.yaml */ });

export async function POST(req: Request): Promise<Response> {
  return handleRoute(async () => {
    const ctx = await requireRole("<domain>:write");   // throws 403 if role lacks permission
    const input = await parseJsonBody(req, BodySchema); // throws 400 ValidationError on bad JSON/shape
    const result = await doTheThing(ctx.tenantId, ctx.userId, input);
    return created(result); // 201; use ok() for 200, noContent() for 204
  });
}

Never write your own try { ... } catch { return Response.json(...) } — handleRoute already: maps any AppError subclass (NotFoundError, ValidationError, ForbiddenError, UnauthorizedError, ...) to its documented status/body; maps Prisma's P2025 (findUniqueOrThrow/findFirstOrThrow on an RLS-invisible or missing row) to NotFoundError automatically — this is why domain code is free to use findUniqueOrThrow directly instead of manual null-checks; logs genuinely unexpected errors server-side with a trace_id (never leaked to the client) so failures are traceable instead of silent 500s.

List Endpoints: Cursor Pagination Convention

Every GET list endpoint (evidence: catalog/products.ts listProducts, catalog/suppliers.ts) uses the same Prisma cursor idiom, never offset/page-number pagination:

// Route: query schema
const ListQuerySchema = z.object({
  cursor: z.string().optional(),
  limit: z.coerce.number().int().min(1).max(100).optional(), // ALWAYS clamp -- see HI-1 below
  // ...domain filters
});

// Module: query
const limit = filters.limit ?? 20;
const rows = await tx.someModel.findMany({
  where: { /* ... */ },
  orderBy: [{ createdAt: "desc" }, { id: "desc" }], // tie-break on id -- required for a stable cursor
  take: limit + 1,                                   // fetch one extra to detect has_more
  ...(filters.cursor ? { cursor: { id: filters.cursor }, skip: 1 } : {}),
});
const hasMore = rows.length > limit;
const page = hasMore ? rows.slice(0, limit) : rows;
return { items: page, page: { next_cursor: hasMore ? (page.at(-1)?.id ?? null) : null, has_more: hasMore } };

If the list needs a derived value per row (e.g. current stock), batch it with ONE query over the whole page's ids (see getCurrentStockForProducts in stock-operation-scaffold), never one query per row in a loop — that exact per-row loop was code-reviewer finding HI-1 across four endpoints, precisely because a hand-written list endpoint skipped this convention.

Common Mistakes

| Mistake | Fix | |---------|-----| | Custom try/catch instead of handleRoute | Always wrap the whole handler body in handleRoute(async () => { ... }) | | Calling the domain function before requireRole/requireSession | Auth check is always the FIRST line inside handleRoute's callback | | Validating body with raw await req.json() | Use parseJsonBody(req, ZodSchema) — gets the standard 400 ValidationError shape for free | | Unbounded limit on a list endpoint | z.coerce.number().int().min(1).max(100).optional() — an unclamped limit turns a list endpoint into an unbounded query | | Per-row query in a .map/loop over a list result | Batch with WHERE id = ANY($1)-style query over the whole page, per HI-1 | | New AppError-like condition without a subclass | Add/reuse an AppError subclass in libs/shared so handleRoute/toErrorResponse map it automatically — don't format Response.json by hand in domain code |

Du fichier au premier essai

Le téléchargement contient exactement le contenu affiché sur cette fiche. Cette copie peut différer de la dernière version du dépôt source.

Consulter la source et ses fichiers annexes

Ce téléchargement contient uniquement SKILL.md. Les scripts, références, modèles et dépendances éventuels ne sont pas inclus. Récupérez les fichiers nécessaires depuis la source en conservant leur arborescence avant de poursuivre.

1. Télécharger le fichier

Télécharger SKILL.md

Depuis la racine d’un projet de test, enregistrez le fichier sous .skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md, ou utilisez la commande suivante. Ce dossier sert à examiner la skill avant son installation.

Commande pour macOS/Linux ou WSL : un shell POSIX et curl sont nécessaires. Sous Windows sans WSL, utilisez le bouton de téléchargement et créez le dossier manuellement.

mkdir -p '.skill-downloads/next-js-api-route-scaffolding-mvda8739' && curl --fail --show-error --location 'https://skills-guides.com/api/skills/next-js-api-route-scaffolding-mvda8739/download' --output '.skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md.part' && mv '.skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md.part' '.skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md'

2. Vérifier les prérequis

Lisez SKILL.md avant de l’utiliser. Si le fichier cite des scripts ou services externes, examinez-les et préparez les accès nécessaires dans votre environnement de test.

Aucune section de prérequis reconnue dans ce fichier. Cela ne signifie pas qu’il est autonome : les bibliothèques, outils et accès peuvent être cités ailleurs dans le contenu ou dans le dépôt source.

Les plateformes de la fiche sont des indications du catalogue. Les chemins ci-dessous suivent la documentation des outils ; ils ne prouvent pas que cette skill y a été exécutée. Aucune version minimale de ces outils n’est attestée pour cette fiche.

3. Charger la skill

Après vérification, copiez le dossier complet dans le projet avec l’une des commandes ci-dessous, puis ouvrez une nouvelle session de l’outil. La commande s’arrête si un dossier du même nom existe déjà : comparez les fichiers avant de le remplacer.

Claude Code
test -f '.skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md' && test -s '.skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md' && mkdir -p '.claude/skills' && test ! -e '.claude/skills/api-route-scaffold' && cp -R '.skill-downloads/next-js-api-route-scaffolding-mvda8739' '.claude/skills/api-route-scaffold'

OpenSkills

OpenSkills doit déjà être installé. La procédure locale install/read est contrôlée avec OpenSkills 1.5.0, qui exige Node.js ≥ 20.6.0. Aucun slug du catalogue n’est résolu automatiquement.

Installation officielle d’OpenSkills
openskills install './.skill-downloads/next-js-api-route-scaffolding-mvda8739'

openskills read 'next-js-api-route-scaffolding-mvda8739'

La lecture doit afficher le contenu du fichier installé et son dossier de base. Elle ne lance ni la skill ni ses dépendances.

Pour un agent qui lit AGENTS.md, synchronisez ensuite l’index. Cette commande modifie AGENTS.md : relisez le diff avant de l’utiliser.

openskills sync

Mode manuel ou autre outil : joignez SKILL.md à une conversation, ou ouvrez-le dans un agent capable de lire les fichiers. Ajoutez les ressources annexes nécessaires. Il n’existe pas de dossier universel reconnu par tous les outils.

4. Faire un premier essai

Utilisez une entrée courte et fictive : un endpoint d’API, un petit document ou un brief d’interface. Dans une nouvelle conversation, joignez le fichier ou donnez son chemin à l’agent, puis adaptez cette demande :

Lis .skill-downloads/next-js-api-route-scaffolding-mvda8739/SKILL.md et applique ses instructions à cet exemple : [décrire une entrée de test]. Indique les fichiers, dépendances ou accès qui manquent. Produis le résultat demandé par la skill sans inventer de données ni exécuter de script non examiné.

Résultat attendu : une sortie correspondant aux instructions de la skill, ou une liste précise des prérequis manquants. Une simple confirmation de lecture ne valide pas le premier usage. Si une ressource externe est indisponible, ce parcours reste à tester.

Skills similaires