Architecture Overview
Understanding the high-level system design and feature-based architecture
Introduction
This project is built on a modern, type-safe stack designed for scalability, performance, and developer experience. The architecture follows a feature-based approach where related code is grouped together by domain, making the codebase easier to navigate and maintain.
High-Level System Design
The application is structured around these core principles:
- Type-safe API layer - Using tRPC for end-to-end type safety
- Server-first architecture - Leveraging Next.js App Router for server-side rendering and data fetching
- Feature modules - Organizing code by business domain rather than technical layer
- Database abstraction - Using Drizzle ORM with reusable CRUD operations
- Component-driven UI - Building with Shadcn UI and Tailwind CSS
Project Structure
src/
├── app/ # Next.js App Router (pages, layouts, API routes)
├── components/ # Shared UI components (atoms, molecules)
├── config/ # App-wide configuration (colors, site settings)
├── db/ # Database schema, migrations, and operations
├── emails/ # Email templates (React Email)
├── features/ # Feature-specific logic (domain modules)
├── forms/ # Form-related abstractions and components
├── hooks/ # Shared React hooks
├── i18n/ # Internationalization setup
├── layouts/ # Page layout components
├── lib/ # Utilities and 3rd-party library configs
├── messages/ # Translation files
├── providers/ # React context providers
├── routes/ # Declarative routing configuration
├── store/ # Client-side state stores (Zustand)
├── trpc/ # tRPC routers and server configuration
└── types/ # Global type definitionsFeature-Based Architecture
Each major domain of the application (e.g., auth, organizations, subscriptions) is encapsulated in its own feature folder within src/features/. This approach:
- Keeps related code together - All logic for a feature lives in one place
- Reduces cognitive load - Developers can focus on one domain at a time
- Improves maintainability - Changes are localized to specific features
- Enables team scaling - Different teams can own different features
Standard Feature Structure
A typical feature folder follows the 5-file pattern:
src/features/organizations/
├── schema.ts # Zod schemas for validation and types
├── functions.ts # Server-side logic and database operations
├── hooks.ts # Client-side tRPC hooks
├── fields.tsx # Reusable form field components
├── prompts.tsx # Dialog/modal wrappers
└── components/ # Feature-specific UI componentsSee the Features guide for detailed information on creating feature modules.
Key Architectural Concepts
Database Access
We use Drizzle ORM for all database interactions:
- Type-safe queries - Full TypeScript support with autocomplete
- Schema definitions - Tables defined in
src/db/tables/ - Reusable operations - Standard CRUD via
createDrizzleOperations
Example database operation:
import { userOperations } from "@/db/tables/users.ts";
// Get a user by ID
const user = await userOperations.getById(userId);
// Create a new user
const newUser = await userOperations.create({
name: "John Doe",
email: "john@example.com",
});Learn more in the Database guide.
API & Data Fetching
tRPC provides end-to-end type safety between client and server:
- Type inference - Client automatically knows the shape of server responses
- No code generation - Types are inferred directly from implementations
- Unified error handling - Consistent error formatting and translation
- Automatic serialization - Using SuperJSON for dates, Sets, Maps, etc.
Example tRPC usage:
// Server-side procedure
export const userRouter = createTRPCRouter({
getById: publicProcedure
.input(z.object({ id: z.string() }))
.query(async ({ input }) => {
return await userOperations.getById(input.id);
}),
});
// Client-side hook
const { data: user } = api.users.getById.useQuery({ id: "123" });Learn more in the tRPC guide and API guide.
Authentication
Better-Auth handles all authentication flows:
- Multi-provider support - Email, OAuth (Google, GitHub), Magic Links
- Server-side access - Via
getAuth()in Server Components - Client-side access - Via
useAuth()hook in Client Components - Secure session management - HTTP-only cookies, CSRF protection
Example authentication check:
// Server Component
import { getAuth } from "@/lib/auth/server";
export default async function ProfilePage() {
const auth = await getAuth();
if (!auth.user) {
redirect("/login");
}
return <div>Welcome, {auth.user.name}</div>;
}
// Client Component
"use client";
import { useAuth } from "@/lib/auth/client";
export function UserMenu() {
const { user, signOut } = useAuth();
return <button onClick={signOut}>Sign Out</button>;
}Learn more in the Authentication guide.
Routing
We use Declarative Routing for type-safe navigation:
- Type-safe routes - Compile-time checking of route parameters
- Automatic breadcrumbs - Generated from route definitions
- i18n integration - Automatic locale prefix handling
- SEO optimization - Metadata generation from route config
Example route usage:
import { PageProfile } from "@/routes";
// Generate a URL
const url = PageProfile({ userId: "123" });
// Result: "/en/profile/123"
// Generate a link component
<PageProfile.Link userId="123">View Profile</PageProfile.Link>Learn more in the Routing guide.
Middleware Chain
The tRPC middleware stack processes every request in a specific order. This ensures consistent behavior across all API endpoints.
See the Middleware guide for detailed information on each middleware.
Error Handling
Errors are handled consistently across the application:
- Custom error classes -
NotFoundError,ValidationError,UnauthorizedError - Automatic translation - Based on user's locale
- Structured responses - Including Zod validation details for forms
- Sentry integration - Automatic error tracking and alerting
Learn more in the Errors guide.
Design Principles
The architecture is guided by these core principles:
- Type Safety - TypeScript and Zod everywhere
- Separation of Concerns - UI, logic, and data access are separate
- Reusability - Small, composable components and functions
- Performance - Server-side rendering and strategic caching
- Modularity - Feature-based organization for scalability
- DRY Principle - Abstractions to avoid repetition
Learn more in the Design Principles guide.