API Contracts Generator
Génère des contrats API cohérents entre Frontend (Next.js) et Backend (NestJS) avec types synchronisés, validation standardisée et error handling uniforme. À utiliser lors de la création d'APIs, DTOs, types frontend/backend, ou quand l'utilisateur mentionne "API", "DTO", "types", "contract", "validation", "frontend-backend", "synchronisation".
Best use case
API Contracts Generator is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Génère des contrats API cohérents entre Frontend (Next.js) et Backend (NestJS) avec types synchronisés, validation standardisée et error handling uniforme. À utiliser lors de la création d'APIs, DTOs, types frontend/backend, ou quand l'utilisateur mentionne "API", "DTO", "types", "contract", "validation", "frontend-backend", "synchronisation".
Teams using API Contracts Generator should expect a more consistent output, faster repeated execution, less prompt rewriting.
When to use this skill
- You want a reusable workflow that can be run more than once with consistent structure.
When not to use this skill
- You only need a quick one-off answer and do not need a reusable workflow.
- You cannot install or maintain the underlying files, dependencies, or repository context.
Installation
Claude Code / Cursor / Codex
Manual Installation
- Download SKILL.md from GitHub
- Place it in
.claude/skills/api-contracts-generator/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How API Contracts Generator Compares
| Feature / Agent | API Contracts Generator | Standard Approach |
|---|---|---|
| Platform Support | Not specified | Limited / Varies |
| Context Awareness | High | Baseline |
| Installation Complexity | Unknown | N/A |
Frequently Asked Questions
What does this skill do?
Génère des contrats API cohérents entre Frontend (Next.js) et Backend (NestJS) avec types synchronisés, validation standardisée et error handling uniforme. À utiliser lors de la création d'APIs, DTOs, types frontend/backend, ou quand l'utilisateur mentionne "API", "DTO", "types", "contract", "validation", "frontend-backend", "synchronisation".
Where can I find the source code?
You can find the source code on GitHub using the link provided at the top of the page.
SKILL.md Source
# API Contracts Generator
## 🎯 Mission
Garantir une **communication parfaite** entre Frontend (Next.js) et Backend (NestJS) via des contrats API cohérents, types synchronisés et validation standardisée.
## 🏗️ Philosophie des API Contracts
### Le Problème
Dans un projet full-stack, les erreurs de communication Frontend ↔ Backend sont fréquentes :
- ❌ Types incohérents (backend attend `clubId`, frontend envoie `id`)
- ❌ Validations divergentes (backend accepte 100 chars, frontend 50)
- ❌ Erreurs non standardisées (format différent selon l'endpoint)
- ❌ Documentation obsolète (Swagger non à jour)
### La Solution : API Contracts
Un **API Contract** définit le contrat entre frontend et backend :
- ✅ **DTOs Backend** : Structure des requêtes/réponses avec validation
- ✅ **Types Frontend** : TypeScript synchronisés avec le backend
- ✅ **Validation cohérente** : Mêmes règles backend et frontend
- ✅ **Error format standard** : Format uniforme pour toutes les erreurs
- ✅ **Documentation auto** : Swagger généré depuis le code
### Architecture de Communication
```
Frontend (Next.js)
↓ Server Action (avec types)
↓ Validation Zod
↓ fetch/axios
Backend (NestJS)
↓ Controller (avec DTOs)
↓ Validation class-validator
↓ Handler (CQRS)
↓ Response DTO
↑ JSON Response
Frontend (Next.js)
↑ Typed Response
↑ UI Update
```
## 📦 1. Backend DTOs (NestJS)
### Request DTOs (Input)
Les **Request DTOs** définissent la structure des données **envoyées par le frontend**.
#### Template Request DTO
```typescript
// volley-app-backend/src/club-management/presentation/dtos/create-club.dto.ts
import { IsString, IsNotEmpty, IsOptional, MaxLength, MinLength } from 'class-validator';
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class CreateClubDto {
@ApiProperty({
description: 'Club name',
example: 'Volley Club Paris',
minLength: 3,
maxLength: 100,
})
@IsString()
@IsNotEmpty()
@MinLength(3)
@MaxLength(100)
readonly name: string;
@ApiPropertyOptional({
description: 'Club description',
example: 'Best volleyball club in Paris',
maxLength: 500,
})
@IsString()
@IsOptional()
@MaxLength(500)
readonly description?: string;
}
```
**Règles pour Request DTOs** :
- ✅ Validation avec `class-validator` (IsString, IsNotEmpty, etc.)
- ✅ Swagger decorators `@ApiProperty` pour documentation
- ✅ `readonly` pour immutabilité
- ✅ Types primitifs (string, number, boolean, Date)
- ✅ Exemples dans Swagger (`example`)
- ❌ **JAMAIS** de logique métier (seulement validation)
### Response DTOs (Output)
Les **Response DTOs** définissent la structure des données **retournées par le backend**.
#### Template Response DTO
```typescript
// volley-app-backend/src/club-management/presentation/dtos/club-detail.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
export class OwnerDto {
@ApiProperty({ example: 'user-123' })
id: string;
@ApiProperty({ example: 'John Doe' })
name: string;
@ApiProperty({ example: 'john@example.com' })
email: string;
}
export class SubscriptionDto {
@ApiProperty({ example: 'FREE', enum: ['FREE', 'PRO', 'UNLIMITED'] })
plan: string;
@ApiProperty({ example: 'ACTIVE', enum: ['ACTIVE', 'INACTIVE', 'EXPIRED'] })
status: string;
@ApiProperty({ example: 1 })
maxTeams: number;
@ApiProperty({ example: 0 })
currentTeamsCount: number;
}
export class ClubDetailDto {
@ApiProperty({ example: 'club-123' })
id: string;
@ApiProperty({ example: 'Volley Club Paris' })
name: string;
@ApiPropertyOptional({ example: 'Best club in Paris' })
description?: string;
@ApiProperty({ type: OwnerDto })
owner: OwnerDto;
@ApiProperty({ type: SubscriptionDto })
subscription: SubscriptionDto;
@ApiProperty({ example: 15 })
membersCount: number;
@ApiProperty({ example: '2024-01-01T00:00:00.000Z' })
createdAt: Date;
}
```
**Règles pour Response DTOs** :
- ✅ Swagger decorators pour documentation complète
- ✅ Nested DTOs pour relations (OwnerDto, SubscriptionDto)
- ✅ Exemples réalistes
- ✅ Enum values documentés
- ✅ Types primitifs + nested objects
- ❌ **JAMAIS** d'entités domain brutes (utiliser des mappers)
### Pagination DTO (Standard)
```typescript
// volley-app-backend/src/shared/dtos/pagination.dto.ts
import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger';
import { Type } from 'class-transformer';
import { IsInt, IsOptional, Max, Min } from 'class-validator';
export class PaginationQueryDto {
@ApiPropertyOptional({ default: 1, minimum: 1 })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
page?: number = 1;
@ApiPropertyOptional({ default: 10, minimum: 1, maximum: 100 })
@IsOptional()
@Type(() => Number)
@IsInt()
@Min(1)
@Max(100)
limit?: number = 10;
}
export class PaginationMetaDto {
@ApiProperty({ example: 1 })
page: number;
@ApiProperty({ example: 10 })
limit: number;
@ApiProperty({ example: 50 })
total: number;
@ApiProperty({ example: 5 })
totalPages: number;
}
export class PaginatedResponseDto<T> {
@ApiProperty({ isArray: true })
data: T[];
@ApiProperty({ type: PaginationMetaDto })
meta: PaginationMetaDto;
}
```
### Controller Integration
```typescript
// volley-app-backend/src/club-management/presentation/controllers/clubs.controller.ts
import { Controller, Post, Get, Body, Param, Query, UseGuards } from '@nestjs/common';
import { ApiTags, ApiOperation, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
import { JwtAuthGuard } from '../../auth/guards/jwt-auth.guard';
import { CreateClubDto } from '../dtos/create-club.dto';
import { ClubDetailDto } from '../dtos/club-detail.dto';
import { ClubListDto } from '../dtos/club-list.dto';
import { PaginationQueryDto, PaginatedResponseDto } from '../../shared/dtos/pagination.dto';
import { CreateClubHandler } from '../../application/commands/create-club/create-club.handler';
import { GetClubHandler } from '../../application/queries/get-club/get-club.handler';
import { ListClubsHandler } from '../../application/queries/list-clubs/list-clubs.handler';
@ApiTags('Clubs')
@ApiBearerAuth()
@Controller('clubs')
@UseGuards(JwtAuthGuard)
export class ClubsController {
constructor(
private readonly createClubHandler: CreateClubHandler,
private readonly getClubHandler: GetClubHandler,
private readonly listClubsHandler: ListClubsHandler,
) {}
@Post()
@ApiOperation({ summary: 'Create a new club' })
@ApiResponse({ status: 201, description: 'Club created', type: String })
@ApiResponse({ status: 400, description: 'Validation error' })
async create(@Body() dto: CreateClubDto): Promise<{ id: string }> {
const command = new CreateClubCommand(dto.name, dto.description, 'current-user-id');
const id = await this.createClubHandler.execute(command);
return { id };
}
@Get(':id')
@ApiOperation({ summary: 'Get club details' })
@ApiResponse({ status: 200, description: 'Club found', type: ClubDetailDto })
@ApiResponse({ status: 404, description: 'Club not found' })
async findOne(@Param('id') id: string): Promise<ClubDetailDto> {
const query = new GetClubQuery(id);
return this.getClubHandler.execute(query);
}
@Get()
@ApiOperation({ summary: 'List clubs with pagination' })
@ApiResponse({ status: 200, description: 'Clubs list', type: PaginatedResponseDto })
async findAll(@Query() pagination: PaginationQueryDto): Promise<PaginatedResponseDto<ClubListDto>> {
const query = new ListClubsQuery(pagination.page, pagination.limit);
return this.listClubsHandler.execute(query);
}
}
```
## 🎨 2. Frontend Types (Next.js)
### Stratégie de Synchronisation
**Option 1 : Générer les types depuis Swagger** (Recommandé)
```bash
# Install openapi-typescript
npm install --save-dev openapi-typescript
# Generate types from backend Swagger
npx openapi-typescript http://localhost:3000/api-json -o src/types/api.ts
```
**Option 2 : Partager les types (Monorepo)**
```typescript
// shared/types/club.types.ts (partagé entre frontend et backend)
export interface CreateClubInput {
name: string;
description?: string;
}
export interface ClubDetail {
id: string;
name: string;
description?: string;
owner: {
id: string;
name: string;
email: string;
};
subscription: {
plan: string;
status: string;
maxTeams: number;
currentTeamsCount: number;
};
membersCount: number;
createdAt: Date;
}
```
**Option 3 : Dupliquer les types manuellement** (Moins recommandé)
```typescript
// volley-app-frontend/src/features/club-management/types/club.types.ts
// Dupliqué depuis backend CreateClubDto
export interface CreateClubInput {
name: string;
description?: string;
}
// Dupliqué depuis backend ClubDetailDto
export interface ClubDetail {
id: string;
name: string;
description?: string;
owner: {
id: string;
name: string;
email: string;
};
subscription: {
plan: string;
status: string;
maxTeams: number;
currentTeamsCount: number;
};
membersCount: number;
createdAt: Date;
}
```
### Validation Frontend avec Zod
```typescript
// volley-app-frontend/src/features/club-management/schemas/club.schema.ts
import { z } from 'zod';
// Schema SYNCHRONISÉ avec backend CreateClubDto
export const createClubSchema = z.object({
name: z
.string()
.min(3, 'Le nom doit contenir au moins 3 caractères')
.max(100, 'Le nom ne peut pas dépasser 100 caractères'),
description: z
.string()
.max(500, 'La description ne peut pas dépasser 500 caractères')
.optional(),
});
export type CreateClubInput = z.infer<typeof createClubSchema>;
```
**CRITIQUE** : Les règles de validation Zod doivent **EXACTEMENT** correspondre aux règles backend (class-validator).
## 🔗 3. Server Actions (Frontend → Backend)
### Template Server Action
```typescript
// volley-app-frontend/src/features/club-management/actions/create-club.action.ts
'use server';
import { revalidatePath } from 'next/cache';
import { createClubSchema, CreateClubInput } from '../schemas/club.schema';
import { clubsApi } from '../api/clubs.api';
export async function createClubAction(input: CreateClubInput) {
try {
// 1. Validate input (frontend validation)
const validated = createClubSchema.parse(input);
// 2. Call backend API
const response = await clubsApi.create(validated);
// 3. Revalidate cache
revalidatePath('/dashboard/coach');
// 4. Return success
return {
success: true as const,
data: response,
};
} catch (error) {
// 5. Handle errors
if (error instanceof z.ZodError) {
return {
success: false as const,
error: {
code: 'VALIDATION_ERROR',
message: 'Données invalides',
details: error.errors,
},
};
}
return {
success: false as const,
error: {
code: 'UNKNOWN_ERROR',
message: error.message || 'Une erreur est survenue',
},
};
}
}
// Type du retour
export type CreateClubResult =
| { success: true; data: { id: string } }
| { success: false; error: { code: string; message: string; details?: any } };
```
### API Client
```typescript
// volley-app-frontend/src/features/club-management/api/clubs.api.ts
import { CreateClubInput, ClubDetail, ClubList } from '../types/club.types';
import { PaginatedResponse } from '@/types/api.types';
const API_BASE_URL = process.env.NEXT_PUBLIC_API_URL || 'http://localhost:3000';
export const clubsApi = {
async create(input: CreateClubInput): Promise<{ id: string }> {
const response = await fetch(`${API_BASE_URL}/clubs`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${getToken()}`, // Helper to get JWT
},
body: JSON.stringify(input),
});
if (!response.ok) {
throw await handleApiError(response);
}
return response.json();
},
async getById(id: string): Promise<ClubDetail> {
const response = await fetch(`${API_BASE_URL}/clubs/${id}`, {
headers: {
Authorization: `Bearer ${getToken()}`,
},
});
if (!response.ok) {
throw await handleApiError(response);
}
return response.json();
},
async list(page: number = 1, limit: number = 10): Promise<PaginatedResponse<ClubList>> {
const response = await fetch(
`${API_BASE_URL}/clubs?page=${page}&limit=${limit}`,
{
headers: {
Authorization: `Bearer ${getToken()}`,
},
},
);
if (!response.ok) {
throw await handleApiError(response);
}
return response.json();
},
};
// Helper functions
function getToken(): string {
// Get JWT from cookies or localStorage
return '';
}
async function handleApiError(response: Response): Promise<Error> {
const error = await response.json();
return new ApiError(error.code, error.message, error.details);
}
class ApiError extends Error {
constructor(
public code: string,
message: string,
public details?: any,
) {
super(message);
this.name = 'ApiError';
}
}
```
## ⚠️ 4. Error Handling Standard
### Backend Error Format
```typescript
// volley-app-backend/src/shared/filters/http-exception.filter.ts
import { ExceptionFilter, Catch, ArgumentsHost, HttpException, HttpStatus } from '@nestjs/common';
import { Response } from 'express';
export interface ErrorResponse {
code: string;
message: string;
details?: any;
timestamp: string;
path: string;
}
@Catch()
export class HttpExceptionFilter implements ExceptionFilter {
catch(exception: unknown, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse<Response>();
const request = ctx.getRequest();
let status = HttpStatus.INTERNAL_SERVER_ERROR;
let errorResponse: ErrorResponse = {
code: 'INTERNAL_SERVER_ERROR',
message: 'Une erreur interne est survenue',
timestamp: new Date().toISOString(),
path: request.url,
};
if (exception instanceof HttpException) {
status = exception.getStatus();
const exceptionResponse = exception.getResponse();
if (typeof exceptionResponse === 'object') {
errorResponse = {
...errorResponse,
...(exceptionResponse as any),
};
} else {
errorResponse.message = exceptionResponse as string;
}
}
response.status(status).json(errorResponse);
}
}
```
### Frontend Error Handling
```typescript
// volley-app-frontend/src/lib/api-error.ts
export class ApiError extends Error {
constructor(
public code: string,
message: string,
public details?: any,
public status?: number,
) {
super(message);
this.name = 'ApiError';
}
static fromResponse(response: any): ApiError {
return new ApiError(
response.code || 'UNKNOWN_ERROR',
response.message || 'Une erreur est survenue',
response.details,
response.status,
);
}
// User-friendly messages
getUserMessage(): string {
const messages: Record<string, string> = {
VALIDATION_ERROR: 'Les données fournies sont invalides',
NOT_FOUND: 'La ressource demandée n\'existe pas',
UNAUTHORIZED: 'Vous devez être connecté pour effectuer cette action',
FORBIDDEN: 'Vous n\'avez pas les permissions nécessaires',
INTERNAL_SERVER_ERROR: 'Une erreur interne est survenue. Veuillez réessayer.',
};
return messages[this.code] || this.message;
}
}
```
## ✅ 5. Checklist API Contract
### Backend (NestJS)
- [ ] Request DTOs avec validation class-validator
- [ ] Response DTOs avec Swagger decorators
- [ ] Exemples réalistes dans Swagger
- [ ] Error handling standardisé
- [ ] Pagination DTO pour listes
- [ ] Swagger activé et accessible (`/api`)
### Frontend (Next.js)
- [ ] Types synchronisés avec backend (OpenAPI ou partagés)
- [ ] Validation Zod cohérente avec backend
- [ ] Server Actions avec types
- [ ] API client avec types
- [ ] Error handling standardisé
- [ ] Messages d'erreur traduits pour UI
### Synchronisation
- [ ] Script de génération des types (si OpenAPI)
- [ ] CI/CD vérifie la synchronisation
- [ ] Documentation Swagger à jour
- [ ] Types partagés si monorepo
## 🎓 Exemple Complet : CreateClub Flow
### 1. Backend DTO
```typescript
// backend/src/club-management/presentation/dtos/create-club.dto.ts
export class CreateClubDto {
@IsString()
@MinLength(3)
@MaxLength(100)
readonly name: string;
@IsString()
@IsOptional()
@MaxLength(500)
readonly description?: string;
}
```
### 2. Frontend Schema (Zod)
```typescript
// frontend/src/features/club-management/schemas/club.schema.ts
export const createClubSchema = z.object({
name: z.string().min(3).max(100),
description: z.string().max(500).optional(),
});
```
### 3. Server Action
```typescript
// frontend/src/features/club-management/actions/create-club.action.ts
export async function createClubAction(input: CreateClubInput) {
const validated = createClubSchema.parse(input); // Frontend validation
const response = await clubsApi.create(validated); // Backend call
revalidatePath('/dashboard/coach');
return { success: true, data: response };
}
```
### 4. Component Usage
```typescript
// frontend/src/features/club-management/components/ClubCreationForm.tsx
'use client';
import { useTransition } from 'react';
import { createClubAction } from '../actions/create-club.action';
export function ClubCreationForm() {
const [isPending, startTransition] = useTransition();
const handleSubmit = async (formData: FormData) => {
startTransition(async () => {
const result = await createClubAction({
name: formData.get('name') as string,
description: formData.get('description') as string,
});
if (result.success) {
router.push(`/clubs/${result.data.id}`);
} else {
setError(result.error.message);
}
});
};
return <form action={handleSubmit}>...</form>;
}
```
## 🚨 Erreurs Courantes à Éviter
1. ❌ **Types incohérents**
- ✅ FAIRE : Générer types frontend depuis Swagger ou partager
- ❌ NE PAS FAIRE : Dupliquer manuellement sans synchronisation
2. ❌ **Validations divergentes**
- ✅ FAIRE : Même règles backend (class-validator) et frontend (Zod)
- ❌ NE PAS FAIRE : Backend max=100, Frontend max=50
3. ❌ **Erreurs non standardisées**
- ✅ FAIRE : Format uniforme `{ code, message, details }`
- ❌ NE PAS FAIRE : Formats différents selon l'endpoint
4. ❌ **Swagger obsolète**
- ✅ FAIRE : Swagger généré automatiquement depuis les DTOs
- ❌ NE PAS FAIRE : Documentation manuelle non synchronisée
5. ❌ **Server Actions avec logique métier**
- ✅ FAIRE : Server Actions = orchestration mince (appel API + cache)
- ❌ NE PAS FAIRE : Logique métier dans Server Actions
## 📚 Skills Complémentaires
Pour aller plus loin :
- **server-actions** : Patterns Server Actions Next.js détaillés
- **ddd-bounded-context** : Architecture backend DDD
- **cqrs-command-query** : Commands/Queries pour APIs
---
**Rappel** : La **synchronisation parfaite** Frontend ↔ Backend garantit une communication sans bugs et une expérience développeur optimale.Related Skills
generator
Générateur de Skill - Crée de nouveaux fichiers SKILL.md depuis les définitions YAML d'agents
EchoKit Config Generator
Generate config.toml for EchoKit servers with interactive setup for ASR, TTS, LLM services, MCP servers, API key entry, and server launch
bigconfig-generator
Use this skill when creating or updating Bigeye monitoring configurations (bigconfig.yml files) for BigQuery tables. Works with metadata-manager skill.
ai-image-generator
使用 ModelScope 等平台生成 AI 图像。当用户需要生成图像、设计图标、创建角色立绘,或需要帮助编写 AI 绘画提示词时使用此技能。支持直接生成图像和仅优化提示词两种模式。
thumbnail-generator
Generate prompts for dev.to blog thumbnail/cover images in hand-drawn infographic style. Use when creating cover images, thumbnails, or featured images for blog posts. Recommended size 1000x420 pixels.
seedream-image-generator
Generate images using the Doubao SeeDream API based on text prompts. Use this skill when users request AI-generated images, artwork, illustrations, or visual content creation. The skill handles API calls, downloads generated images to the project's /pic folder, and supports batch generation of up to 4 sequential images.
og-image-generator
Generate and optimize Open Graph meta images for social media sharing. Use this skill when building web applications that need dynamic OG image generation with support for Vercel's @vercel/og library, pre-generated image storage, and social media optimization (Twitter Cards, Facebook, LinkedIn). Handles dynamic routes, performance optimization, and includes best practices for crawler compatibility and testing.
gemini-image-generator
Generate and edit images using Google Gemini. Use when the user asks to generate, create, edit, or modify images.
didactic-content-generator
Gere conteúdo didático de alta qualidade em HTML/CSS com ilustrações SVG, usando um sistema de Temas pré definidos e reutilizáveis. Capaz de criar ou editar apostilas, tutoriais e materiais educacionais que seguem filosofias pedagógicas claras.
article-image-generator
Generates consistent, professional cover images for business/fiscal articles using Ideogram with standardized prompts and naming conventions. Use when creating new articles, updating missing covers, or maintaining visual consistency across the content library.
apex-video-generator
Generate real estate marketing videos from property data. Use when creating property showcases, social media content, market reports, or neighborhood tours. Integrates Firecrawl scraped data with Remotion rendering.
pr-description-generator
Auto-activates when user mentions creating pull request, PR description, or merge request. Generates comprehensive PR descriptions from git diff and commit history.