Service Layer

ReferenceBackend architectureReusable domain logic

src/server/services contains domain logic that should not be tied directly to a page, route handler, or tRPC router.

This page is the practical reference for deciding what belongs in the service layer and why that separation matters in Fast Unicorn.

Architecture connection

This reference complements Project Structure and the feature docs for auth and billing.

Current domains

Billing

  • src/server/services/billing/stripe-client.ts
  • src/server/services/billing/checkout.ts
  • src/server/services/billing/customer.ts
  • src/server/services/billing/webhook-sync.ts

No auth service directory

There is no src/server/services/auth/ directory. Password recovery logic lives directly in the tRPC router at src/server/api/routers/customAuth.ts.

Why it exists

Why this layer exists

This architecture keeps the codebase maintainable as features grow.

Benefits:

  • less duplicated logic between API routes and tRPC routers
  • clearer ownership of domain behavior
  • easier testing
  • simpler route handlers
  • better long-term scalability

Shared backend behavior

Reusable business logic can serve multiple entrypoints without being duplicated in routes or routers.

Cleaner boundaries

Request parsing, transport contracts, and rendering stay separate from domain rules.

What belongs in services

Good candidates for src/server/services:

  • Stripe checkout orchestration
  • customer ownership checks
  • webhook event processing
  • billing tier mapping and status calculations

What should stay out

Avoid putting these concerns in services unless there is a strong reason:

  • JSX or route rendering concerns
  • request parsing specific to a single route handler
  • generic UI composition

Simple rule

If the backend behavior could be reused by more than one route or transport boundary, it usually belongs in src/server/services.

Working rule

If the same backend behavior could be reused by multiple entry points, it usually belongs in the service layer first and the router or route should call into it.