Code Patterns & Conventions
Proven patterns and conventions for building maintainable, scalable features
Introduction
This section covers the code patterns and conventions used throughout this project. Following these patterns ensures consistency, maintainability, and reduces the cognitive load when navigating the codebase.
These patterns have been refined over multiple projects and embody best practices for type-safe, feature-based development with Next.js, tRPC, and Drizzle ORM.
Why Patterns Matter
Consistency
When all features follow the same structure, developers can:
- Find code quickly - Know exactly where to look for schemas, hooks, or functions
- Onboard faster - Learn once, apply everywhere
- Review efficiently - Spot deviations from established patterns
- Refactor confidently - Consistent structure makes changes predictable
Scalability
Patterns help the codebase scale by:
- Isolating concerns - Each file has a single, clear responsibility
- Enabling parallel development - Multiple developers can work on different features without conflicts
- Supporting incremental growth - Add features without restructuring existing code
- Facilitating testing - Clear boundaries make unit and integration tests straightforward
Maintainability
Well-defined patterns lead to:
- Fewer bugs - Type safety and validation catch errors early
- Easier debugging - Clear data flow and separation of concerns
- Better documentation - Code structure is self-documenting
- Lower technical debt - Consistent patterns prevent anti-patterns from spreading
Pattern Categories
1. Feature Modules
The 5-file pattern for organizing feature-specific code:
schema.ts- Zod schemas and TypeScript typesfunctions.ts- Server-side operations using Drizzle ORMhooks.ts- Client-side tRPC hooks for data fetchingfields.tsx- Reusable form field componentsprompts.tsx- Dialog wrappers for create/update/delete operations
Learn more about Feature Modules →
2. Error Handling
Robust error handling patterns:
- Using
CustomErrorwith scoped error codes - Returning
ActionResponsein tRPC procedures - Throwing
CustomErroronly in helper functions - Translating errors for i18n
- Toast notifications and error boundaries
Learn more about Error Handling →
3. Type Safety
Type-safe development practices:
- Always use
typeinstead ofinterface - Zod schemas for runtime validation and type inference
- Apply
.refine()only on final schemas (not base schemas) - Custom error messages via
.message()instead of hardcoded strings - End-to-end type safety with tRPC
Learn more about Type Safety →
Core Principles
All patterns in this guide follow these principles:
- Type safety first - Leverage TypeScript and Zod for compile-time and runtime safety
- Server-first architecture - Keep sensitive logic and data on the server
- Separation of concerns - Each file has a single, clear responsibility
- Declarative over imperative - Use declarative APIs when possible
- Convention over configuration - Minimize boilerplate through conventions
- Progressive enhancement - Start simple, add complexity only when needed
Quick Reference
| Pattern | Key Files | Primary Use Case |
|---|---|---|
| Feature Modules | schema.ts, functions.ts, hooks.ts | Building complete features with CRUD operations |
| Custom Errors | custom-error.ts, customErrors.json | Application-specific error handling |
| Action Response | tRPC procedures | Graceful error handling with toast notifications |
| Drizzle Operations | functions.ts | Type-safe database CRUD |
| Form Fields | fields.tsx | Reusable, styled form inputs |
| Prompt Dialogs | prompts.tsx | Confirmation dialogs with usePrompt() |
Getting Started
If you're new to this codebase:
- Read the Feature Modules guide - Understand the 5-file pattern
- Study existing features - Look at src/features/api-keys as a reference
- Follow the templates - Use /docs/templates/new-feature for step-by-step instructions
- Practice type safety - Review Type Safety patterns
- Handle errors properly - Learn the Error Handling approach