Retour • 20/03/2025
Next Safe Action mais pour les API Routes (NextJS)
Écris par Melvyn Malherbe le 20/03/2025
Tu cherches un outil qui permet de faire comme Next Safe Action mais pour les API Routes ? Ça existe !
Je te présente Next-Zod-Route
une librairie qui te permet de transformer tes API Routes de ça :
const BodySchema = z.object({
name: z.string(),
});
const SearchParamsSchema = z.object({
query: z.string(),
});
export const POST = async (
req: Request,
context: { searchParams: Promise<Record<string, string>> }
) => {
const safeBody = BodySchema.safeParse(await req.json());
const baseSearchParams = await context.searchParams;
const safeSearchParams = SearchParamsSchema.safeParse(baseSearchParams);
if (!safeBody.success) {
return NextResponse.json({ error: safeBody.error.message }, { status: 400 });
}
if (!safeSearchParams.success) {
return NextResponse.json(
{ error: safeSearchParams.error.message },
{ status: 400 }
);
}
const body = safeBody.data;
const searchParams = safeSearchParams.data;
return NextResponse.json({ body, searchParams });
};
En ça :
export const POST = route
.body(
z.object({
name: z.string(),
})
)
.query(
z.object({
q: z.string(),
})
)
.handler(async (req, { body, query }) => {
return NextResponse.json({ body, query });
});
Beaucoup plus simple non ?
Et c'est pas tout car next-zod-route
te permet d'ajouter :
- des middlewares qui peuvent modifier la réponse
- des validations pour le body, les params et les search params
- retourner un simple object javascript au lieu d'un NextResponse
- avoir des erreurs customisées
Validation des paramètres
Avec next-zod-route
, tu peux facilement valider les paramètres de route, les paramètres de requête et le corps de la requête. Voyons comment ça fonctionne :
const route = createZodRoute()
.params(
z.object({
id: z.string().uuid(),
})
)
.query(
z.object({
search: z.string().min(1),
})
)
.body(
z.object({
field: z.string(),
})
)
.handler(async (req, { params, query, body }) => {
// params.id est correctement typé comme string
// query.search est correctement typé comme string
// body.field est correctement typé comme string
return NextResponse.json({ params, query, body });
});
Le typage est automatiquement inféré à partir de tes schémas Zod, ce qui rend ton code plus sûr et plus lisible.
Middlewares
Tu peux facilement ajouter des middlewares pour modifier ou valider les requêtes avant qu'elles n'atteignent ton handler :
const authMiddleware: MiddlewareFunction = async ({ next, request }) => {
// Code exécuté avant le handler
const startTime = performance.now();
// Transmission de données au contexte (ici tu peux récupérer le auth en fonction de la requête)
const result = await next({ ctx: { user: { id: 'user-123', role: 'admin' } } });
// Code exécuté après le handler
const endTime = performance.now();
console.log(`Request took ${Math.round(endTime - startTime)}ms`);
return result;
};
export const GET = createZodRoute()
.use(authMiddleware)
.handler((request, context) => {
// Accès aux données du middleware
const { user } = context.ctx;
return Response.json({ user });
});
Les middlewares peuvent :
- Exécuter du code avant et après le handler
- Partager des données via le contexte
- Court-circuiter la chaîne de middlewares (par exemple pour les vérifications d'authentification)
- Modifier la réponse
Gestion des erreurs personnalisées
Tu peux personnaliser la gestion des erreurs pour traiter les cas spécifiques :
class CustomError extends Error {
statusCode: number;
constructor(message: string, statusCode: number) {
super(message);
this.name = 'CustomError';
this.statusCode = statusCode;
}
}
const handleServerError = (error: Error) => {
if (error instanceof CustomError) {
return new Response(
JSON.stringify({ message: error.name, details: error.message }),
{ status: error.statusCode }
);
}
return new Response(JSON.stringify({ message: 'Something went wrong' }), {
status: 500,
});
};
export const GET = createZodRoute({
handleServerError,
}).handler(() => {
throw new CustomError('Not Found', 404);
});
Métadonnées avec validation
La fonctionnalité de métadonnées te permet d'associer des données validées à tes routes, particulièrement utile pour les vérifications d'autorisation :
const permissionsMetadataSchema = z.object({
permissions: z.array(z.string()).optional(),
});
const permissionMiddleware: MiddlewareFunction = async ({
next,
metadata,
request,
}) => {
const isAuthorized = hasPermission(request, metadata.permissions);
if (!hasAllPermissions) {
// Court-circuiter avec une réponse 403 Forbidden
return new Response(
JSON.stringify({
error: 'Forbidden',
message: 'You do not have the required permissions',
}),
{
status: 403,
headers: { 'Content-Type': 'application/json' },
}
);
}
// Continuer avec le contexte autorisé
return next({ ctx: { authorized: true } });
};
export const GET = createZodRoute()
.defineMetadata(permissionsMetadataSchema)
.use(permissionMiddleware)
// Statique
.metadata({ requiredPermissions: ['read:users'] })
.handler((request, context) => {
const { authorized } = context.ctx;
return Response.json({ success: true, authorized });
});
Retour flexible
Tu peux retourner soit un objet Response
standard, soit un simple objet JavaScript qui sera automatiquement converti en réponse JSON :
// Retour direct d'un objet Response
export const GET = createZodRoute().handler(() => {
return new Response(JSON.stringify({ custom: 'response' }), {
status: 201,
headers: { 'X-Custom-Header': 'test' },
});
});
// Retour d'un objet JavaScript (converti automatiquement en JSON)
export const GET = createZodRoute().handler(() => {
return { data: 'value' }; // Status 200 par défaut
});
Chaîne de middlewares avancée
Les middlewares peuvent travailler ensemble pour construire des fonctionnalités complexes :
export const GET = createZodRoute()
.use(async ({ next }) => {
// Premier middleware
const result = await next({ ctx: { user: { id: 'user-123' } } });
return result;
})
.use(async ({ next, ctx }) => {
// Deuxième middleware, accès au contexte du premier
const user = ctx.user;
const result = await next({ ctx: { permissions: ['read', 'write'] } });
return result;
})
.handler((request, context) => {
// Accès au contexte complet des deux middlewares
const { user, permissions } = context.ctx;
return Response.json({ user, permissions });
});
Conclusion
next-zod-route
est une solution complète pour gérer tes API Routes dans Next.js avec :
- Validation des entrées (params, query, body) avec Zod
- Système de middlewares flexible
- Gestion personnalisée des erreurs
- Métadonnées validées pour l'autorisation
- Compatibilité avec différents formats de corps (JSON, form-data)
Si tu développes des API routes dans Next.js et que tu cherches une alternative à Next Safe Action, cette librairie est faite pour toi !
Pour l'installer :
npm install next-zod-route
# ou
yarn add next-zod-route
# ou
pnpm add next-zod-route
Retrouve le code source et la documentation complète sur GitHub.