Build A Feature End To End

Build a small vertical slice through the established route, UI, domain, persistence, validation, authorization, observability, and testing boundaries.

On this page

Vertical-slice dependency direction

  1. 1

    Define the behavior

    Write the actor, trigger, input, successful outcome, authorization rule, failure states, and operational signals before choosing files.

    Relevant Product code
    • src/features/contact/shared/schemas.ts
  2. 2

    Add a thin entrypoint

    A page, route handler, or server action handles framework glue and delegates real work to feature-owned code.

    Relevant Product code
    • src/app/api/contact/route.ts
    • src/app/(marketing)/contact/page.tsx
  3. 3

    Validate and authorize

    Treat browser input as untrusted and resolve identity, role, feature access, and sensitive-data permissions on the server.

    Relevant Product code
    • src/features/contact/server/services/submission.ts
    • src/lib/auth/rbac/access.ts
  4. 4

    Execute domain and persistence work

    Keep provider calls, business rules, database access, retries, and idempotency within explicit server responsibilities.

    Relevant Product code
    • src/features/contact/server/services/submission.ts
    • src/features/contact/server/persistence/messages.ts
  5. 5

    Return app-owned results

    Expose stable success and failure contracts instead of leaking provider SDK or database response shapes into the UI.

    Relevant Product code
    • src/features/contact/shared/contracts.ts
  6. 6

    Protect the risk

    Test invalid input, unauthorized access, retry/duplicate behavior where relevant, and the regression most likely to hurt a customer.

    Relevant Product code
    • src/app/api/contact/__tests__/route.test.ts
    • src/features/contact/server/services/__tests__/submission.test.ts

Recipe: add your core workflow

Introduce the workflow that differentiates your product without turning a route file, component, or generic helper into a second architecture.

Implementation locations

Inspect

  • src/app/api/contact/route.ts
  • src/features/contact/server/services/submission.ts
  • src/features/contact/server/persistence/messages.ts
  • src/features/contact/shared/schemas.ts
  • src/app/api/contact/__tests__/route.test.ts

Edit

  • src/app/<route>/page.tsx or route.ts
  • src/features/<domain>/ui
  • src/features/<domain>/server
  • src/features/<domain>/shared
  • src/features/<domain>/__tests__

Steps

  • Name the feature after the business domain, not the current screen.
  • Keep the App Router file limited to metadata, request/response glue, and delegation.
  • Define schemas and app-owned types before persistence or provider integration.
  • Authorize from server identity; never trust a client-supplied user, profile, role, price, or entitlement.
  • Make external events and important mutations safe under retry and duplicate delivery.
  • Add explicit loading, empty, error, success, and permission-denied states.
  • Document configuration, data ownership, failure modes, extension points, and removal implications with the change.

Verify

  • pnpm run test:features
  • pnpm run test:api
  • pnpm run quality
  • Complete the workflow with a non-admin test user and a failure scenario.

Common mistakes

  • Do not place domain behavior in page.tsx or route.ts.
  • Do not create index.ts barrels or pure re-export facades.
  • Do not add a generic helper until more than one domain owns the same stable invariant.
  • Do not treat a hidden button as authorization.

Definition of done for a customer workflow

AreaEvidence
BehaviorA named actor can complete the promised outcome, including empty, error, and recovery states.
SecurityServer authorization, validated input, safe logging, rate limits where exposed, and no leaked credentials or personal data.
ReliabilityRetry, duplicate delivery, partial failure, concurrent update, and eventual-consistency behavior are explicit where applicable.
DataOwnership, RLS/service-role boundary, indexes, pagination, retention, migration, and repair implications are understood.
OperationsLogs and stable identifiers make failure diagnosable; alerts and a recovery action exist for critical paths.
MaintenanceFocused tests, direct imports, understandable files, updated documentation, and a safe removal path exist.