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:

  • CreateGroupSchema
  • JoinGroupSchema
  • SelectSubscriptionSchema
  • CheckoutSchema

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.