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
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
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.tssrc/app/(marketing)/contact/page.tsx
- 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.tssrc/lib/auth/rbac/access.ts
- 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.tssrc/features/contact/server/persistence/messages.ts
- 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
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.tssrc/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
| Area | Evidence |
|---|---|
| Behavior | A named actor can complete the promised outcome, including empty, error, and recovery states. |
| Security | Server authorization, validated input, safe logging, rate limits where exposed, and no leaked credentials or personal data. |
| Reliability | Retry, duplicate delivery, partial failure, concurrent update, and eventual-consistency behavior are explicit where applicable. |
| Data | Ownership, RLS/service-role boundary, indexes, pagination, retention, migration, and repair implications are understood. |
| Operations | Logs and stable identifiers make failure diagnosable; alerts and a recovery action exist for critical paths. |
| Maintenance | Focused tests, direct imports, understandable files, updated documentation, and a safe removal path exist. |