integration
Backend-Frontend integration patterns expert. Type-safe API contracts with Pydantic-Zod validation sync (Python FastAPI) or Prisma-TypeScript native (Next.js). Shadcn forms connected to backend, error handling, loading states. Use when creating full-stack features.
Best use case
integration is best used when you need a repeatable AI agent workflow instead of a one-off prompt.
Backend-Frontend integration patterns expert. Type-safe API contracts with Pydantic-Zod validation sync (Python FastAPI) or Prisma-TypeScript native (Next.js). Shadcn forms connected to backend, error handling, loading states. Use when creating full-stack features.
Teams using integration 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/integration/SKILL.mdinside your project - Restart your AI agent — it will auto-discover the skill
How integration Compares
| Feature / Agent | integration | 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?
Backend-Frontend integration patterns expert. Type-safe API contracts with Pydantic-Zod validation sync (Python FastAPI) or Prisma-TypeScript native (Next.js). Shadcn forms connected to backend, error handling, loading states. Use when creating full-stack features.
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
# Integration Skill - Backend ↔ Frontend Patterns
> **Expert intégration backend (Python FastAPI ou Next.js) ↔ frontend React + shadcn**
>
> Inspiré de : Stripe API patterns, Vercel full-stack architecture, Prisma best practices
---
## Scope
**Chargé par:** executor agent (quand feature full-stack détectée)
**Deux chemins d'intégration:**
### Path A: Python FastAPI + React + shadcn (REST API)
- Backend: Python + FastAPI + Pydantic
- Frontend: React + Next.js + shadcn
- Communication: REST API (JSON)
- Validation: Pydantic backend ↔ Zod frontend (mirrored)
### Path B: Next.js Full-Stack + React + shadcn (Server Actions)
- Backend: Next.js Server Actions + Prisma
- Frontend: React + Next.js + shadcn
- Communication: Server Actions (RPC-like)
- Validation: Zod + Prisma native types
**Patterns couverts:**
1. **Type-safe API contracts** (Pydantic → TypeScript OU Prisma → TypeScript native)
2. **Validation synchronisée** (Pydantic ↔ Zod OU Zod + Prisma enums)
3. **Forms shadcn → Backend** (FastAPI routes OU Server Actions)
4. **Error handling** (standardisé backend → UI feedback)
5. **Loading states** (React Query OU useOptimistic)
6. **Database patterns** (Prisma best practices)
---
## Pattern #1: Type-Safe API Contract (Path A - FastAPI)
### Backend (Pydantic schemas)
```python
# backend/app/schemas/task.py
from pydantic import BaseModel, Field, validator
from datetime import datetime
from typing import Literal
class TaskBase(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: str | None = None
status: Literal["pending", "in_progress", "completed"] = "pending"
priority: Literal["low", "medium", "high"] = "medium"
@validator("title")
def title_not_empty(cls, v):
if not v.strip():
raise ValueError("Title cannot be empty")
return v.strip()
class TaskCreate(TaskBase):
"""Input creation (pas d'ID)"""
pass
class TaskUpdate(BaseModel):
"""Update partiel (tous optionnels)"""
title: str | None = Field(None, min_length=1, max_length=200)
description: str | None = None
status: Literal["pending", "in_progress", "completed"] | None = None
priority: Literal["low", "medium", "high"] | None = None
class TaskResponse(TaskBase):
"""Output avec metadata DB"""
id: str
created_at: datetime
updated_at: datetime
user_id: str
class Config:
from_attributes = True
```
### Frontend (TypeScript types synchronisés)
```typescript
// frontend/src/types/task.ts
// Mirror exact Pydantic schemas
export type TaskStatus = "pending" | "in_progress" | "completed"
export type TaskPriority = "low" | "medium" | "high"
export interface TaskBase {
title: string
description?: string | null
status: TaskStatus
priority: TaskPriority
}
export interface TaskCreate extends TaskBase {
// Pas d'ID pour création
}
export interface TaskUpdate {
// Tous optionnels pour update partiel
title?: string
description?: string | null
status?: TaskStatus
priority?: TaskPriority
}
export interface TaskResponse extends TaskBase {
id: string
created_at: string // ISO string du datetime Python
updated_at: string
user_id: string
}
```
**Principe:** Types frontend **MIRRORED** exactement depuis Pydantic pour type-safety.
---
## Pattern #2: Validation Synchronisée (Pydantic ↔ Zod)
### Backend Validation (Pydantic)
```python
# backend/app/schemas/task.py
class TaskCreate(BaseModel):
title: str = Field(..., min_length=1, max_length=200)
description: str | None = None
@validator("title")
def title_not_empty(cls, v):
if not v.strip():
raise ValueError("Title cannot be empty")
return v.strip()
```
### Frontend Validation (Zod - SYNCHRONISÉ)
```typescript
// frontend/src/schemas/task.ts
import { z } from "zod"
// Mirror EXACT validation Pydantic
export const taskCreateSchema = z.object({
title: z.string()
.min(1, "Title cannot be empty")
.max(200, "Title too long")
.refine(val => val.trim().length > 0, "Title cannot be empty"),
description: z.string().optional().nullable(),
status: z.enum(["pending", "in_progress", "completed"]).default("pending"),
priority: z.enum(["low", "medium", "high"]).default("medium"),
})
export type TaskCreateInput = z.infer<typeof taskCreateSchema>
```
**RÈGLE CRITIQUE:** Validation frontend **identique** backend.
- Même min/max lengths
- Même regex patterns
- Même error messages (traduits si nécessaire)
**Principe:** Frontend validation = UX rapide, Backend validation = sécurité.
(Defense in depth - jamais trust client)
---
## Pattern #3: Shadcn Form → FastAPI Complete
### Backend API Route
```python
# backend/app/api/routes/tasks.py
from fastapi import APIRouter, HTTPException, Depends
from app.schemas.task import TaskCreate, TaskResponse
from app.services.task_service import task_service
router = APIRouter(prefix="/tasks", tags=["tasks"])
@router.post("/", response_model=TaskResponse, status_code=201)
async def create_task(
task: TaskCreate,
user_id: str = Depends(get_current_user)
):
"""Crée nouvelle task avec validation"""
try:
return task_service.create_task(user_id, task)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
except Exception as e:
raise HTTPException(status_code=500, detail="Internal server error")
```
### Frontend API Client
```typescript
// frontend/src/lib/api/tasks.ts
import { TaskCreate, TaskResponse } from "@/types/task"
const API_BASE = process.env.NEXT_PUBLIC_API_URL || "http://localhost:8000"
export async function createTask(task: TaskCreate): Promise<TaskResponse> {
const response = await fetch(`${API_BASE}/api/tasks`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"Authorization": `Bearer ${getToken()}`,
},
body: JSON.stringify(task),
})
if (!response.ok) {
const error = await response.json()
throw new Error(error.detail || "Failed to create task")
}
return response.json()
}
```
### Shadcn Form Component (Complete)
```tsx
// frontend/src/components/task-form.tsx
"use client"
import { zodResolver } from "@hookform/resolvers/zod"
import { useForm } from "react-hook-form"
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { toast } from "sonner"
import { Button } from "@/components/ui/button"
import { Input } from "@/components/ui/input"
import { Textarea } from "@/components/ui/textarea"
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from "@/components/ui/form"
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "@/components/ui/select"
import { taskCreateSchema, type TaskCreateInput } from "@/schemas/task"
import { createTask } from "@/lib/api/tasks"
export function TaskForm({ onSuccess }: { onSuccess?: () => void }) {
const queryClient = useQueryClient()
// Form avec Zod validation
const form = useForm<TaskCreateInput>({
resolver: zodResolver(taskCreateSchema),
defaultValues: {
title: "",
description: "",
status: "pending",
priority: "medium",
},
})
// Mutation avec React Query
const mutation = useMutation({
mutationFn: createTask,
onSuccess: () => {
// Invalidate cache pour refresh liste
queryClient.invalidateQueries({ queryKey: ["tasks"] })
toast.success("Task created successfully")
form.reset()
onSuccess?.()
},
onError: (error: Error) => {
toast.error(error.message)
},
})
const onSubmit = (data: TaskCreateInput) => {
mutation.mutate(data)
}
return (
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-4">
{/* Title */}
<FormField
control={form.control}
name="title"
render={({ field }) => (
<FormItem>
<FormLabel>Title</FormLabel>
<FormControl>
<Input placeholder="Enter task title" {...field} />
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Description */}
<FormField
control={form.control}
name="description"
render={({ field }) => (
<FormItem>
<FormLabel>Description</FormLabel>
<FormControl>
<Textarea
placeholder="Enter task description"
{...field}
value={field.value || ""}
/>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
{/* Priority */}
<FormField
control={form.control}
name="priority"
render={({ field }) => (
<FormItem>
<FormLabel>Priority</FormLabel>
<Select onValueChange={field.onChange} defaultValue={field.value}>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select priority" />
</SelectTrigger>
</FormControl>
<SelectContent>
<SelectItem value="low">Low</SelectItem>
<SelectItem value="medium">Medium</SelectItem>
<SelectItem value="high">High</SelectItem>
</SelectContent>
</Select>
<FormMessage />
</FormItem>
)}
/>
{/* Submit */}
<Button type="submit" disabled={mutation.isPending}>
{mutation.isPending ? "Creating..." : "Create Task"}
</Button>
</form>
</Form>
)
}
```
**Checklist connexion complète:**
- ✅ Zod schema mirrored depuis Pydantic
- ✅ TypeScript types synchronisés
- ✅ React Hook Form + Zod resolver
- ✅ React Query mutation (loading + error states)
- ✅ Toast notifications (success/error)
- ✅ Cache invalidation (refresh après create)
- ✅ Form reset après success
---
## Pattern #4: Error Handling (Backend → Frontend)
### Backend Error Responses (standardisé)
```python
# backend/app/api/routes/tasks.py
from fastapi import HTTPException
from pydantic import ValidationError
@router.post("/")
async def create_task(task: TaskCreate):
try:
return task_service.create_task(task)
except ValueError as e:
# Business logic error
raise HTTPException(
status_code=400,
detail={"message": str(e), "type": "validation_error"}
)
except PermissionError as e:
# Authorization error
raise HTTPException(
status_code=403,
detail={"message": str(e), "type": "permission_error"}
)
except Exception as e:
# Unexpected error (log + generic message)
logger.error(f"Unexpected error: {e}", exc_info=True)
raise HTTPException(
status_code=500,
detail={"message": "Internal server error", "type": "server_error"}
)
```
### Frontend Error Handling
```typescript
// frontend/src/lib/api/client.ts
export class APIError extends Error {
constructor(
message: string,
public status: number,
public type?: string
) {
super(message)
}
}
export async function apiClient<T>(
url: string,
options?: RequestInit
): Promise<T> {
const response = await fetch(url, {
...options,
headers: {
"Content-Type": "application/json",
...options?.headers,
},
})
if (!response.ok) {
const error = await response.json().catch(() => ({}))
throw new APIError(
error.detail?.message || error.detail || "Request failed",
response.status,
error.detail?.type
)
}
return response.json()
}
```
```tsx
// frontend/src/components/task-form.tsx
const mutation = useMutation({
mutationFn: createTask,
onError: (error: APIError) => {
// Error handling basé sur type
switch (error.type) {
case "validation_error":
toast.error(`Validation error: ${error.message}`)
break
case "permission_error":
toast.error("You don't have permission to perform this action")
break
case "server_error":
toast.error("Server error. Please try again later.")
break
default:
toast.error(error.message)
}
},
})
```
**Principe:** Errors backend standardisés avec types → Frontend gère selon type.
(Inspiration: Stripe API errors)
---
## Pattern #5: Loading States (React Query)
### Liste avec Loading/Error/Empty States
```tsx
// frontend/src/components/task-list.tsx
"use client"
import { useQuery } from "@tanstack/react-query"
import { Card } from "@/components/ui/card"
import { Skeleton } from "@/components/ui/skeleton"
import { Alert, AlertDescription } from "@/components/ui/alert"
import { getTasks } from "@/lib/api/tasks"
export function TaskList() {
const { data, isLoading, error } = useQuery({
queryKey: ["tasks"],
queryFn: getTasks,
})
// Loading state
if (isLoading) {
return (
<div className="space-y-4">
{[...Array(3)].map((_, i) => (
<Card key={i} className="p-4">
<Skeleton className="h-6 w-3/4 mb-2" />
<Skeleton className="h-4 w-full" />
</Card>
))}
</div>
)
}
// Error state
if (error) {
return (
<Alert variant="destructive">
<AlertDescription>
Failed to load tasks: {error.message}
</AlertDescription>
</Alert>
)
}
// Empty state
if (!data || data.length === 0) {
return (
<div className="text-center p-8 text-muted-foreground">
No tasks yet. Create your first task!
</div>
)
}
// Data state
return (
<div className="space-y-4">
{data.map((task) => (
<Card key={task.id} className="p-4">
<h3 className="font-semibold">{task.title}</h3>
<p className="text-sm text-muted-foreground">{task.description}</p>
</Card>
))}
</div>
)
}
```
**Checklist states:**
- ✅ Loading (Skeleton UI)
- ✅ Error (Alert destructive)
- ✅ Empty (Message vide)
- ✅ Data (Liste tasks)
---
## Pattern #6: Optimistic Updates
```tsx
// frontend/src/hooks/useTasks.ts
import { useMutation, useQueryClient } from "@tanstack/react-query"
import { updateTask } from "@/lib/api/tasks"
import type { TaskResponse, TaskUpdate } from "@/types/task"
export function useUpdateTask() {
const queryClient = useQueryClient()
return useMutation({
mutationFn: ({ id, data }: { id: string; data: TaskUpdate }) =>
updateTask(id, data),
// Optimistic update AVANT requête serveur
onMutate: async ({ id, data }) => {
// Cancel outgoing queries
await queryClient.cancelQueries({ queryKey: ["tasks"] })
// Snapshot previous value
const previousTasks = queryClient.getQueryData<TaskResponse[]>(["tasks"])
// Optimistically update cache
queryClient.setQueryData<TaskResponse[]>(["tasks"], (old) =>
old?.map((task) =>
task.id === id ? { ...task, ...data } : task
) || []
)
// Return context avec previous value
return { previousTasks }
},
// Si erreur → Rollback
onError: (err, variables, context) => {
if (context?.previousTasks) {
queryClient.setQueryData(["tasks"], context.previousTasks)
}
toast.error("Failed to update task")
},
// Après success → Refetch pour sync
onSettled: () => {
queryClient.invalidateQueries({ queryKey: ["tasks"] })
},
})
}
```
**Principe:** Update UI immédiatement (optimistic), rollback si erreur serveur.
(Stripe Dashboard pattern)
---
## Checklist Intégration Full-Stack
### Backend (FastAPI + Pydantic)
- [ ] Pydantic schemas définis (Base, Create, Update, Response)
- [ ] Validation Pydantic complète (min/max, regex, custom validators)
- [ ] API routes avec error handling standardisé
- [ ] Services layer (business logic séparée)
- [ ] Error responses structurées (message + type)
### Frontend (React + shadcn + Zod)
- [ ] TypeScript types mirrored depuis Pydantic
- [ ] Zod schemas synchronisés avec Pydantic validation
- [ ] API client avec error handling
- [ ] React Query setup (mutations + queries)
- [ ] Shadcn forms avec react-hook-form + Zod resolver
- [ ] Loading states (Skeleton UI)
- [ ] Error states (Alert components)
- [ ] Empty states (messages appropriés)
- [ ] Success feedback (toast notifications)
- [ ] Cache invalidation (après mutations)
- [ ] Optimistic updates (si applicable)
---
## Anti-Patterns à Éviter
❌ **Validation différente frontend vs backend**
```typescript
// Frontend: max 100 chars
z.string().max(100)
// Backend: max 200 chars
Field(..., max_length=200)
// → Incohérence = bugs
```
✅ **Validation synchronisée exactement**
---
❌ **Types frontend pas à jour**
```typescript
// Backend ajouté field "priority"
// Frontend oublie → TypeScript errors partout
```
✅ **Types générés ou mirrored manuellement avec checklist**
---
❌ **Pas de loading states**
```tsx
{data?.map(...)} // Pas de loading → Flash vide
```
✅ **Skeleton UI pendant loading**
---
❌ **Errors non gérés**
```tsx
onError: () => {} // Error silencieux = mauvaise UX
```
✅ **Toast + error messages clairs**
---
## Workflow Executor avec Integration Skill
**Quand executor crée feature full-stack:**
```
1. executor détecte: feature nécessite backend + frontend
2. Load skills: backend + frontend + integration
3. Phase A - Backend:
- Crée Pydantic schemas (selon integration patterns)
- Crée API route avec error handling standardisé
- Crée service layer
4. Phase B - Frontend:
- Crée TypeScript types (mirrored Pydantic)
- Crée Zod schemas (synchronized validation)
- Crée API client
- Crée shadcn form (complete pattern)
- Loading/Error/Empty states
5. Validation:
- Checklist intégration complète
- Types synchronisés ✓
- Validation identique ✓
- Error handling ✓
```
---
## Principes
1. **Type-Safety First** - Types synchronisés Pydantic ↔ TypeScript
2. **Validation Mirrored** - Frontend validation = Backend validation
3. **Error Handling Standardisé** - Errors structurés backend → Frontend gère
4. **Loading States Obligatoires** - Skeleton UI, pas flash vide
5. **Optimistic Updates** - Meilleure UX, rollback si erreur
6. **Defense in Depth** - Validation frontend (UX) + backend (sécurité)
**Inspiré de:**
- Stripe Dashboard (error handling + loading states + optimistic updates)
- Vercel (full-stack TypeScript patterns)
---
**Version**: 1.0.0
**Last updated**: 2025-01-10
**Maintained by**: executor agent (loaded pour features full-stack)
---
## Pattern #7: Prisma + Next.js Full-Stack (Path B - Type-Safe Native)
### Database Schema (Prisma)
```prisma
// prisma/schema.prisma
generator client {
provider = "prisma-client-js"
}
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
model User {
id String @id @default(cuid())
email String @unique
name String
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
tasks Task[]
@@index([email])
}
model Task {
id String @id @default(cuid())
title String
description String?
status TaskStatus @default(PENDING)
priority TaskPriority @default(MEDIUM)
createdAt DateTime @default(now())
updatedAt DateTime @updatedAt
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
userId String
@@index([userId])
@@index([status])
}
enum TaskStatus {
PENDING
IN_PROGRESS
COMPLETED
}
enum TaskPriority {
LOW
MEDIUM
HIGH
}
```
**Commandes Prisma:**
```bash
# Générer client TypeScript
npx prisma generate
# Créer migration
npx prisma migrate dev --name add_tasks
# Push schema (dev rapide)
npx prisma db push
# Ouvrir Prisma Studio
npx prisma studio
```
---
### Prisma Client Singleton (Pattern Vercel)
```typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const globalForPrisma = globalThis as unknown as {
prisma: PrismaClient | undefined
}
export const prisma =
globalForPrisma.prisma ??
new PrismaClient({
log: process.env.NODE_ENV === 'development' ? ['query', 'error', 'warn'] : ['error'],
})
if (process.env.NODE_ENV !== 'production') globalForPrisma.prisma = prisma
```
**Principe:** Singleton pour éviter épuisement connexions DB (Vercel best practice)
---
### Next.js API Route avec Prisma (Server Actions)
```typescript
// app/actions/tasks.ts
'use server'
import { revalidatePath } from 'next/cache'
import { prisma } from '@/lib/prisma'
import { z } from 'zod'
import { TaskStatus, TaskPriority } from '@prisma/client'
// Validation Zod (synchronized avec Prisma enums)
const taskCreateSchema = z.object({
title: z.string().min(1, "Title required").max(200, "Title too long"),
description: z.string().optional(),
status: z.nativeEnum(TaskStatus).default(TaskStatus.PENDING),
priority: z.nativeEnum(TaskPriority).default(TaskPriority.MEDIUM),
userId: z.string().cuid(),
})
export type TaskCreateInput = z.infer<typeof taskCreateSchema>
export async function createTask(input: TaskCreateInput) {
try {
// Validation
const validated = taskCreateSchema.parse(input)
// Prisma create
const task = await prisma.task.create({
data: validated,
include: {
user: {
select: { id: true, name: true, email: true }
}
}
})
// Revalidate cache
revalidatePath('/dashboard')
return { success: true, data: task }
} catch (error) {
if (error instanceof z.ZodError) {
return { success: false, error: error.errors[0].message }
}
return { success: false, error: "Failed to create task" }
}
}
export async function getTasks(userId: string) {
const tasks = await prisma.task.findMany({
where: { userId },
include: {
user: {
select: { id: true, name: true }
}
},
orderBy: { createdAt: 'desc' }
})
return tasks
}
export async function updateTask(id: string, userId: string, data: Partial<TaskCreateInput>) {
try {
// Check ownership
const task = await prisma.task.findUnique({
where: { id },
select: { userId: true }
})
if (!task || task.userId !== userId) {
return { success: false, error: "Task not found or access denied" }
}
// Update
const updated = await prisma.task.update({
where: { id },
data,
})
revalidatePath('/dashboard')
return { success: true, data: updated }
} catch (error) {
return { success: false, error: "Failed to update task" }
}
}
export async function deleteTask(id: string, userId: string) {
try {
// Check ownership
const task = await prisma.task.findUnique({
where: { id },
select: { userId: true }
})
if (!task || task.userId !== userId) {
return { success: false, error: "Task not found or access denied" }
}
await prisma.task.delete({
where: { id }
})
revalidatePath('/dashboard')
return { success: true }
} catch (error) {
return { success: false, error: "Failed to delete task" }
}
}
```
**Avantages Server Actions:**
- ✅ Type-safe natif (Prisma types auto-générés)
- ✅ Pas de route API explicite
- ✅ Revalidation cache Next.js intégrée
- ✅ Streaming support
---
### Frontend avec Server Actions + Prisma Types
```tsx
// app/dashboard/page.tsx (Server Component)
import { getTasks } from '@/app/actions/tasks'
import { TaskList } from '@/components/task-list'
import { getCurrentUser } from '@/lib/auth'
export default async function DashboardPage() {
const user = await getCurrentUser()
const tasks = await getTasks(user.id)
return (
<div className="container py-6">
<h1 className="text-3xl font-bold mb-6">Dashboard</h1>
<TaskList initialTasks={tasks} userId={user.id} />
</div>
)
}
```
```tsx
// components/task-list.tsx (Client Component)
'use client'
import { useState, useOptimistic } from 'react'
import { Task } from '@prisma/client'
import { deleteTask, updateTask } from '@/app/actions/tasks'
import { Button } from '@/components/ui/button'
import { Card } from '@/components/ui/card'
import { toast } from 'sonner'
type TaskWithUser = Task & {
user: { id: string; name: string }
}
export function TaskList({
initialTasks,
userId
}: {
initialTasks: TaskWithUser[]
userId: string
}) {
const [tasks, setTasks] = useState(initialTasks)
const [optimisticTasks, addOptimisticTask] = useOptimistic(
tasks,
(state, deletedId: string) => state.filter(t => t.id !== deletedId)
)
const handleDelete = async (taskId: string) => {
// Optimistic update
addOptimisticTask(taskId)
// Server action
const result = await deleteTask(taskId, userId)
if (result.success) {
setTasks(tasks.filter(t => t.id !== taskId))
toast.success("Task deleted")
} else {
// Rollback optimistic update
setTasks(tasks)
toast.error(result.error)
}
}
const handleStatusChange = async (taskId: string, newStatus: Task['status']) => {
const result = await updateTask(taskId, userId, { status: newStatus })
if (result.success) {
setTasks(tasks.map(t =>
t.id === taskId ? { ...t, status: newStatus } : t
))
toast.success("Status updated")
} else {
toast.error(result.error)
}
}
return (
<div className="space-y-4">
{optimisticTasks.map(task => (
<Card key={task.id} className="p-4">
<div className="flex items-center justify-between">
<div>
<h3 className="font-semibold">{task.title}</h3>
<p className="text-sm text-muted-foreground">{task.description}</p>
<p className="text-xs text-muted-foreground mt-1">
Status: {task.status} | Priority: {task.priority}
</p>
</div>
<div className="flex gap-2">
<Button
size="sm"
variant="outline"
onClick={() => handleStatusChange(
task.id,
task.status === 'PENDING' ? 'IN_PROGRESS' : 'COMPLETED'
)}
>
Next Status
</Button>
<Button
size="sm"
variant="destructive"
onClick={() => handleDelete(task.id)}
>
Delete
</Button>
</div>
</div>
</Card>
))}
</div>
)
}
```
---
### Prisma Relations & Include Patterns
```typescript
// Get task with user
const task = await prisma.task.findUnique({
where: { id },
include: {
user: true, // Include relation complète
}
})
// Select specific fields only (optimisation)
const task = await prisma.task.findUnique({
where: { id },
include: {
user: {
select: { id: true, name: true, email: true }
}
}
})
// Nested includes (deep relations)
const user = await prisma.user.findUnique({
where: { id },
include: {
tasks: {
where: { status: 'PENDING' },
orderBy: { createdAt: 'desc' },
take: 10,
}
}
})
// Count relations
const user = await prisma.user.findUnique({
where: { id },
include: {
_count: {
select: { tasks: true }
}
}
})
```
---
### Prisma Transactions (ACID guarantees)
```typescript
// Sequential operations transaction
const result = await prisma.$transaction(async (tx) => {
// 1. Create task
const task = await tx.task.create({
data: { title: "New task", userId }
})
// 2. Update user stats
await tx.user.update({
where: { id: userId },
data: {
tasksCount: { increment: 1 }
}
})
// 3. Create notification
await tx.notification.create({
data: {
userId,
message: `Task "${task.title}" created`
}
})
return task
})
// Rollback automatique si erreur
```
**Interactive transactions (complexes):**
```typescript
const result = await prisma.$transaction(
async (tx) => {
// Multiple queries avec logique conditionnelle
const user = await tx.user.findUnique({ where: { id: userId } })
if (!user) throw new Error("User not found")
if (user.tasksCount >= 100) {
throw new Error("Task limit reached")
}
return await tx.task.create({ data: taskData })
},
{
maxWait: 5000, // Max wait acquire lock
timeout: 10000, // Max transaction duration
}
)
```
---
### Prisma Middleware (Logging, Soft Delete)
```typescript
// lib/prisma.ts
import { PrismaClient } from '@prisma/client'
const prisma = new PrismaClient()
// Logging middleware
prisma.$use(async (params, next) => {
const before = Date.now()
const result = await next(params)
const after = Date.now()
console.log(`Query ${params.model}.${params.action} took ${after - before}ms`)
return result
})
// Soft delete middleware
prisma.$use(async (params, next) => {
if (params.model === 'Task') {
if (params.action === 'delete') {
// Change to update with deletedAt
params.action = 'update'
params.args['data'] = { deletedAt: new Date() }
}
if (params.action === 'findMany' || params.action === 'findFirst') {
// Filter out soft deleted
params.args.where = { ...params.args.where, deletedAt: null }
}
}
return next(params)
})
export { prisma }
```
---
## Pattern #8: Prisma + Zod Full Validation
### Generate Zod from Prisma (automatique)
```bash
# Install zod-prisma
npm install zod-prisma-types
# Update prisma schema
# prisma/schema.prisma
generator zod {
provider = "zod-prisma-types"
output = "../src/lib/zod"
}
# Generate
npx prisma generate
```
**Résultat auto-généré:**
```typescript
// src/lib/zod/index.ts (auto-generated)
import { z } from 'zod'
export const TaskSchema = z.object({
id: z.string().cuid(),
title: z.string().min(1).max(200),
description: z.string().nullable(),
status: z.enum(['PENDING', 'IN_PROGRESS', 'COMPLETED']),
priority: z.enum(['LOW', 'MEDIUM', 'HIGH']),
createdAt: z.date(),
updatedAt: z.date(),
userId: z.string().cuid(),
})
export const TaskCreateSchema = TaskSchema.omit({
id: true,
createdAt: true,
updatedAt: true,
})
export const TaskUpdateSchema = TaskCreateSchema.partial()
```
**Usage dans Server Actions:**
```typescript
import { TaskCreateSchema } from '@/lib/zod'
export async function createTask(input: unknown) {
const validated = TaskCreateSchema.parse(input) // Auto-validated
return prisma.task.create({ data: validated })
}
```
---
## Checklist Prisma + Next.js Integration
### Setup
- [ ] Prisma schema défini (`schema.prisma`)
- [ ] Enums définis pour status/priority/etc
- [ ] Relations définies (`@relation`)
- [ ] Indexes créés (`@@index`)
- [ ] Prisma client généré (`npx prisma generate`)
- [ ] Migrations appliquées (`npx prisma migrate dev`)
### Backend (Server Actions)
- [ ] Prisma client singleton (`lib/prisma.ts`)
- [ ] Zod validation schemas (auto-generated ou manuels)
- [ ] Server actions avec error handling
- [ ] Ownership checks (userId validation)
- [ ] Cache revalidation (`revalidatePath`)
- [ ] Transactions si opérations multiples
### Frontend (React)
- [ ] Prisma types importés (`@prisma/client`)
- [ ] Server Components fetch data (Prisma direct)
- [ ] Client Components actions (Server Actions)
- [ ] Optimistic updates (`useOptimistic`)
- [ ] Loading states (Suspense boundaries)
- [ ] Error boundaries
### Performance
- [ ] Select only needed fields (pas `include: { user: true }` si besoin juste id)
- [ ] Indexes sur colonnes filtrées/triées
- [ ] Connection pooling configuré (Vercel/Railway)
- [ ] Pagination si listes longues
---
## Quand utiliser quel Path?
| Path | Stack | Cas d'usage |
|------|-------|-------------|
| **Path A (FastAPI)** | Python + FastAPI + Pydantic + React + shadcn | Backend Python séparé, API REST, microservices, machine learning intégré |
| **Path B (Prisma)** | Next.js + Prisma + React + shadcn | Full-stack Next.js, Server Actions, déploiement Vercel simplifié |
**Recommandation:**
- **Path A (FastAPI)** si besoin backend Python pur ou API séparée
- **Path B (Prisma)** si stack Next.js full-stack avec Server Actions
---
## Workflow Executor avec Integration Skill + Prisma
**User:** "Crée CRUD tasks avec Prisma"
**Executor:**
```
1. Load skills: frontend + backend + integration
2. Détecte: Prisma mentionné → Prisma pattern
3. Backend (Server Actions):
- Prisma schema (Task model + enums)
- npx prisma migrate dev
- Server actions (create, get, update, delete)
- Zod validation (nativeEnum TaskStatus)
4. Frontend:
- Server Component (getTasks Prisma direct)
- Client Component (Server Actions + useOptimistic)
- Shadcn form
5. Checklist Prisma ✓
```
---
**Patterns couverts:** ✅
- **Path A (FastAPI):** Pydantic → Zod sync, REST API, React Query
- **Path B (Prisma):** Prisma native types, Server Actions, useOptimistic
- Prisma best practices (schema, singleton, relations, transactions, middleware)
- Error handling standardisé
- Loading states (Skeleton UI)
- Optimistic updates
**Version**: 1.2.0
**Updated**: 2025-01-10 - Nettoyé références tRPC/SQLAlchemy, focus FastAPI + PrismaRelated Skills
moai-context7-lang-integration
Enterprise-grade Context7 MCP integration patterns for language-specific documentation access with real-time library resolution and intelligent caching
MCP Integration
Model Context Protocol (MCP) integration specialist. Use when creating MCP server configurations, implementing MCP integrations, or optimizing MCP performance. Specializes in MCP server architecture and integration patterns.
mapbox-integration-patterns
Official integration patterns for Mapbox GL JS across popular web frameworks. Covers setup, lifecycle management, token handling, search integration, and common pitfalls. Based on Mapbox's create-web-app scaffolding tool.
lsp-integration
This skill should be used when the user asks to "add LSP server", "configure language server", "set up LSP in plugin", "add code intelligence", "integrate language server protocol", "use pyright-lsp", "use typescript-lsp", "use rust-lsp", "socket transport", "initializationOptions", mentions LSP servers, or discusses extensionToLanguage mappings. Provides guidance for integrating Language Server Protocol servers into Claude Code plugins for enhanced code intelligence.
kuroco-frontend-integration
Kurocoとフロントエンドフレームワークの統合パターンおよびAI自動デプロイメント。使用キーワード:「Kuroco Nuxt」「Kuroco Next.js」「フロントエンド連携」「Nuxt3」「Nuxt2」「App Router」「Pages Router」「SSG」「SSR」「静的生成」「コンテンツ表示」「ログイン実装」「会員登録」「signup」「KurocoPages」「フロントエンド環境構築」「Vue」「React」「useAsyncData」「$fetch」「asyncData」「composable」「useAuth」「認証状態」「プロフィール取得」「profile」「generateStaticParams」「動的ルート」「v-html」「dangerouslySetInnerHTML」「XSS対策」「サードパーティCookie」「credentials include」「AI自動デプロイ」「Kuroco自動化」「サイト登録API」「自動ビルド」「自動デプロイ」「temp-upload」「presigned URL」「S3アップロード」「artifact_url」「KurocoFront」「プレビューデプロイ」「stage_url」「add_site」「site_key」「kuroco_front/deploy」「CI/CD」「フロントエンドビルド」「ZIP配備」「自動公開」「nuxt generate」「next build」「vite build」「デプロイAPI」「一時アップロード」「署名付きURL」。Nuxt/Next.jsでのKuroco連携、認証実装、SSG/SSR設定、AI自動デプロイに関する質問で使用。
hubspot-integration
Expert patterns for HubSpot CRM integration including OAuth authentication, CRM objects, associations, batch operations, webhooks, and custom objects. Covers Node.js and Python SDKs. Use when: hubs...
gRPC Integration
Comprehensive guide to gRPC for high-performance microservices communication with Protocol Buffers.
External KI Integration
Skill for accessing external AI services (ChatGPT, Claude, Hugging Face, etc.) via browser automation (Chrome Relay) and APIs to assist with tasks.
dapr-integration
Integrate Dapr building blocks for event-driven microservices - Pub/Sub, State Management, Secrets, Service Invocation, and Jobs API. Use when implementing event-driven architecture for Phase 5. (project)
benchling-integration
Benchling R&D platform integration. Access registry (DNA, proteins), inventory, ELN entries, workflows via API, build Benchling Apps, query Data Warehouse, for lab data management automation.
arch-cross-service-integration
Use when designing or implementing cross-service communication, data synchronization, or service boundary patterns.
api-integration
API 集成和设计最佳实践。用于设计、实现和优化 RESTful API、GraphQL API 或其他 API 集成。包括错误处理、认证、限流、版本控制等。