Backend Patterns
Implementation conventions that keep the API predictable and safe.
Thin route handlers
Route handlers should only coordinate request lifecycle work:
- Session/auth checks
- Input parsing with Zod
- Service call
- Response serialization
Avoid placing business rules directly inside route files.
Service layer owns business logic
Services enforce domain rules:
- Group capacity limits
- Ownership and role checks
- Payment and subscription lifecycle state transitions
- Idempotency checks for Stripe webhooks
Transaction boundaries
Use prisma.$transaction() for multi-write operations that must succeed or fail atomically.
Primary example: group creation must also create owner membership in the same transaction.
Validation contracts
Every mutable endpoint accepts a Zod schema:
CreateGroupSchemaJoinGroupSchemaSelectSubscriptionSchemaCheckoutSchema
Treat these schemas as part of the public contract.
Error normalization
All errors should map to a stable shape.
{
"error": {
"code": "GROUP_FULL",
"message": "This group has reached its maximum member count.",
"statusCode": 400
}
}
Stripe webhook invariant
Webhook route must parse raw text body before signature verification.
const body = await req.text();
Parsing with req.json() breaks signature verification.
Idempotency guard
Webhook handlers must detect already-processed payment intents before applying state changes.