This project demonstrates two different architectural approaches for building e-commerce applications using a monorepo structure with pnpm workspaces:
- Entity-based architecture (traditional MVC pattern)
- Domain-based architecture (Domain-Driven Design)
- Root package.json manages workspace configuration
- Four main packages: entity-backend, entity-frontend, domain-backend, domain-frontend
- Use pnpm commands with filters:
pnpm --filter <package-name> <command>
- Shared dependencies at root level, package-specific dependencies in respective package.json
- Organize code by database entities (User, Product, Order, etc.)
- Controllers handle business logic for each entity
- Routes are grouped by entity type
- Keep CRUD operations simple and straightforward
- Use traditional MVC patterns
- Organize code by business domains (user management, product catalog, order processing)
- Follow clean architecture layers:
- Domain layer: Business entities, value objects, domain services
- Application layer: Use cases, application services, DTOs
- Infrastructure layer: Database access, external APIs, HTTP routes
- Separate business logic from infrastructure concerns
- Use dependency injection and repository patterns
- Use PascalCase for class files:
UserController.ts
, ProductService.ts
- Use camelCase for directories:
user/
, product/
, infrastructure/
- Use kebab-case for route files:
user-routes.ts
, product-routes.ts
- Use camelCase for variables and functions:
getUserById
, createOrder
- Use PascalCase for classes and interfaces:
UserRepository
, OrderService
- Use UPPER_SNAKE_CASE for constants:
JWT_SECRET
, DATABASE_URL
- Use Prisma ORM for both architectures
- SQLite for development, PostgreSQL for production
- Follow consistent naming for database fields:
- Use camelCase in Prisma schema
- Use snake_case for actual database columns (via
@@map
)
GET /api/users # Get all users
GET /api/users/:id # Get user by ID
POST /api/users # Create new user
PUT /api/users/:id # Update user
DELETE /api/users/:id # Delete user
- Use JWT tokens for authentication
- Include
Authorization: Bearer <token>
header
- Implement middleware for protected routes
- Use strict TypeScript configuration
- Define interfaces for all data structures
- Use proper type annotations for function parameters and return types
- Avoid
any
type unless absolutely necessary
- Use consistent error response format:
{
error: string,
message?: string,
details?: any
}
- Implement global error handlers
- Use proper HTTP status codes
- Write unit tests for business logic
- Use integration tests for API endpoints
- Mock external dependencies
- Test both happy paths and error scenarios
- Use Composition API with
<script setup lang="ts">
- Implement proper state management with Pinia
- Use Nuxt UI & Tailwind CSS for styling
- Follow Vue 3 best practices
When working in the domain-based architecture:
- Keep domain logic pure (no external dependencies)
- Use repository interfaces in domain layer
- Implement repositories in infrastructure layer
- Separate use cases from HTTP handling
- Use value objects for complex data types
When working in the entity-based architecture:
- Keep controllers focused on single entity
- Use service classes for complex business logic
- Group related functionality in same controller
- Keep routes simple and RESTful
- Ensure code follows the appropriate architectural pattern
- Check for proper separation of concerns
- Verify proper error handling
- Confirm tests are included for new features
- Review for security best practices
- Use database indexing appropriately
- Implement caching where beneficial
- Optimize N+1 query problems
- Use pagination for large datasets
- Validate all input data
- Use parameterized queries (Prisma handles this)
- Implement rate limiting
- Secure sensitive endpoints with authentication
- Never expose sensitive data in API responses
- Choose the appropriate architecture for your feature
- Follow the established patterns for that architecture
- Write tests for your implementation
- Update documentation as needed
- Ensure proper error handling is in place
export class UserController {
async getAll(req: Request, res: Response) {
// Implementation
}
async getById(req: Request, res: Response) {
// Implementation
}
async create(req: Request, res: Response) {
// Implementation
}
}
// Domain layer
export interface UserRepository {
findById(id: string): Promise<User | null>;
save(user: User): Promise<User>;
}
// Application layer
export class UserService {
constructor(private userRepository: UserRepository) {}
async getUserById(id: string): Promise<User> {
// Use case implementation
}
}
This project serves as a learning tool to understand the trade-offs between different architectural approaches in real-world applications.