Install to workspace from root:
- npm install cors --workspace=api // not --workspace=apps/api
- npm run dev:back // Runs dev in backend workspace - tsx --watch src/server.ts
- npm run build:back // Builds backend dist/
- npm run start:back // Starts compiled js app from dist/
- npm run dev:front // runs dev in frontend workspace
Short description of what the project does and why it exists.
Core principles for all technical decisions:
- Prefer simplicity and easy to understand code over clever code
- Type safety first (TypeScript strict mode)
- Separation of concerns (routes → controllers → services → repositories)
- One error class to rule them all. Write explicit error messages
- Configuration via environment variables
- Node.js (ES6)
- TypeScript
- Express
- MySQL (
mysql2/promise)
- jsonwebtoken
- bcrypt
- tsx (dev runtime)
- nodemon (optional)
- dotenv
- prettier
apps/
├── shared/types.ts
├── api/
│ ├── dist/
│ ├── src/
│ │ ├── middleware/authMiddleware.ts
│ │ ├── mongodb/{routes/, controllers/, connection.ts}
│ │ └── mysql/{routes/, controllers/, services/, repositories/, db/}
│ ├── server.ts
│ ├── tsconfig.json
│ └── .env
├── web/
│ ├── dist/
│ ├── src/
│ │ ├── components/
│ │ ├── handouts/
│ │ ├── pages/...
│ └── utils/initToolbarSidebar.ts
├── dist/
├── package.json
├── tsconfig.json
Routes
- Define endpoints
- No business logic
Controllers
- Parse request
- Call service
- Return response
Services
- Business logic
- Orchestration
Repositories
- Database access only
- No business logic
Domain
- Types
- Factories
- Entities
- Validation logic
- Define domain type
- Write service logic
- Write repository function
- Write controller
- Register route
- Test manually
- main → stable
- dev → integration
- feature/xyz → new features
npm install npm run dev
Example scripts:
{ "scripts": { "dev": "tsx watch src/server.ts", "build": "tsc", "start": "node dist/server.js" } }
.env
PORT=3000 DATABASE_URL= JWT_SECRET=
Never commit .env.
Document WHY choices were made.
- Prevent runtime bugs
- Better refactoring
- Clear contracts between layers
- Database logic isolated
- Business logic testable
- Thin controllers
- Stateless
- API-friendly
- Easy horizontal scaling
- Native async/await
- Lightweight
- No ORM overhead
Before adding a package:
- Is it actively maintained?
- Is it widely used?
- Does it reduce complexity?
- Can we write this ourselves easily?
- Does it duplicate built-in functionality?
Avoid:
- Overlapping libraries
- Heavy frameworks for small problems
- Magic-heavy ORMs unless necessary
- Domain errors
- Validation errors
- Infrastructure errors
- Centralized error middleware
Example pattern:
class AppError extends Error { constructor(message: string, public statusCode: number) { super(message); } }
Development
- console.log
Production
- Structured logger (pino / winston)
- Unit test services
- Integration test API routes
- Mock repositories in service tests
- Use test database for integration
- Build with tsc
- Run node dist/server.js
- Environment-specific configs
- Docker for reproducibility
- Stateless API
- Separate database instance
- Redis for caching (if needed)
- Background jobs via queue (BullMQ)
- Named exports preferred
- Default export only for routers
- No business logic in controllers
- No DB logic in services
- Strict typing everywhere
- Define domain types
- Add repository
- Add service
- Add controller
- Add route
- Add validation
- Update README if architecture changes
Document:
- Planned refactors
- Known limitations
- Performance bottlenecks
- Architecture upgrades