| name | backend |
| description | API, database, server logic, webhooks. Auto-use for any API/DB work. |
| version | 3.0.0 |
| allowed-tools | ["Read","Edit","Write","Bash","Grep","Glob"] |
Backend
Auto-use when: API, route, endpoint, database, Supabase, schema, migration, webhook, server action
Works with: frontend for UI, security for auth patterns
Auto-Apply Rules
1. Every Endpoint Must Have
export async function POST(request: Request) {
const { userId } = await auth()
if (!userId) return Response.json({ error: 'Unauthorized' }, { status: 401 })
const body = await request.json()
const result = Schema.safeParse(body)
if (!result.success) {
return Response.json({ error: 'Invalid', details: result.error.flatten() }, { status: 400 })
}
const resource = await db.findUnique({ where: { id: result.data.id } })
if (resource?.userId !== userId) {
return Response.json({ error: 'Not found' }, { status: 404 })
}
try {
const data = await db.create({ data: result.data })
return Response.json(data, { status: 201 })
} catch (error) {
console.error('Error:', error)
return Response.json({ error: 'Failed' }, { status: 500 })
}
}
2. Server Actions
'use server'
import { auth } from '@/lib/auth'
import { db } from '@/lib/db'
import { revalidatePath } from 'next/cache'
import { z } from 'zod'
const Schema = z.object({
title: z.string().min(1).max(200),
})
export async function createPost(input: unknown) {
const { userId } = await auth()
if (!userId) return { error: 'Unauthorized' }
const result = Schema.safeParse(input)
if (!result.success) return { error: 'Invalid', details: result.error.flatten() }
try {
const post = await db.post.create({
data: { ...result., : userId }
})
()
{ : post }
} (error) {
.(, error)
{ : }
}
}
3. Database Patterns
const posts = await db.post.findMany({
include: { author: true }
})
const posts = await db.post.findMany({
skip: (page - 1) * limit,
take: limit,
orderBy: { createdAt: 'desc' }
})
4. Webhooks
export async function POST(request: Request) {
const body = await request.text()
const signature = headers().get('stripe-signature')!
let event
try {
event = stripe.webhooks.constructEvent(body, signature, secret)
} catch {
return new Response('Invalid signature', { status: 400 })
}
const exists = await db.webhookEvent.findUnique({ where: { eventId: event.id } })
if (exists) return new Response('Already processed')
switch (event.type) {
case 'checkout.session.completed':
await handleCheckout(event.data.object)
break
}
await db..({ : { : event. } })
()
}
Quick Reference
API Route
app/api/users/route.ts -> GET /api/users, POST /api/users
app/api/users/[id]/route.ts -> GET/PATCH/DELETE /api/users/:id
Server Action vs API Route
Form submission, mutations -> Server Action (preferred)
External API access needed -> API Route
Webhooks -> API Route
Supabase with RLS
const { data } = await supabase
.from('posts')
.select('*')
Security Checklist
[] Auth check at start
[] Zod validation on all input
[] Ownership check on resource access
[] Generic errors to client
[] Detailed errors to logs
[] No secrets in code
[] Webhook signature verification
Red Flags (STOP)
| If You See | Fix |
|---|
No await auth() | Add auth check |
Schema.parse(body) without try | Use safeParse |
| No ownership check | Add authorization |
return { error: error.message } | Generic error |
db.query(\...${input}`)` | Use parameterized |
| No pagination | Add pagination |