--- trigger: always_on --- # Backend Development Rules > Rules and guidelines for developing the E-Learning Platform Backend --- ## đŸŽ¯ General Principles ### 1. Code Organization - Follow MVC pattern: Models (Prisma) → Controllers → Routes - Keep controllers thin, business logic in services - One file per route group (e.g., `auth.routes.js`, `courses.routes.js`) - Maximum 200 lines per file (split if larger) ### 2. Naming Conventions ```javascript // Files: kebab-case user-service.js course-controller.js // Functions: camelCase getUserById() createCourse() // Classes: PascalCase UserService CourseController // Constants: UPPER_SNAKE_CASE MAX_FILE_SIZE JWT_SECRET // Database: snake_case user_id created_at ``` ### 3. Multi-Language Support **ALWAYS** use JSON structure for user-facing text: ```javascript // ✅ Correct { title: { th: "ā¸Ģā¸Ĩā¸ąā¸ā¸Ēā¸šā¸•ā¸Ŗ", en: "Course" } } // ❌ Wrong { title: "ā¸Ģā¸Ĩā¸ąā¸ā¸Ēā¸šā¸•ā¸Ŗ" } ``` --- ## 🔐 Security Rules ### 1. Authentication - **ALWAYS** validate JWT token in protected routes - **NEVER** trust client-side data - **ALWAYS** hash passwords with bcrypt (salt rounds: 10) - Token expiry: 24h (access), 7d (refresh) ### 2. Authorization ```javascript // Check authentication first if (!req.user) return res.status(401).json({ error: "Unauthorized" }); // Then check role if (!["ADMIN", "INSTRUCTOR"].includes(req.user.role.code)) { return res.status(403).json({ error: "Forbidden" }); } // Finally check ownership if (course.instructorId !== req.user.id && req.user.role.code !== "ADMIN") { return res.status(403).json({ error: "Forbidden" }); } ``` ### 3. Input Validation - **ALWAYS** validate with Joi or Zod - **NEVER** trust user input - Sanitize HTML content - Validate file types and sizes --- ## 📁 File Upload Rules ### 1. File Validation ```javascript // Check file type const allowedTypes = ['video/mp4', 'application/pdf', ...]; if (!allowedTypes.includes(file.mimetype)) { return error("Invalid file type"); } // Check file size if (file.size > MAX_FILE_SIZE) { return error("File too large"); } ``` ### 2. File Storage (MinIO/S3) ```javascript // Use consistent path structure const videoPath = `courses/${courseId}/lessons/${lessonId}/video.mp4`; const attachmentPath = `lessons/${lessonId}/attachments/${uniqueFilename}`; // Generate unique filenames const uniqueFilename = `${Date.now()}-${uuidv4()}-${originalFilename}`; ``` ### 3. File Limits - Video: 500 MB max - Attachment: 100 MB max per file - Max 10 attachments per lesson - Total 500 MB per lesson --- ## đŸ—„ī¸ Database Rules ### 1. Prisma Best Practices ```javascript // ✅ Use transactions for related operations await prisma.$transaction([ prisma.lesson.create(...), prisma.attachment.createMany(...) ]); // ✅ Include relations when needed const course = await prisma.course.findUnique({ where: { id }, include: { chapters: { include: { lessons: true } } } }); // ✅ Use select to limit fields const user = await prisma.user.findUnique({ where: { id }, select: { id: true, username: true, email: true } }); ``` ### 2. Soft Delete Pattern ```javascript // ✅ Use soft delete await prisma.course.update({ where: { id }, data: { isDeleted: true, deletedAt: new Date() } }); // ❌ Don't hard delete await prisma.course.delete({ where: { id } }); // Avoid this ``` ### 3. Query Optimization - Use `select` to fetch only needed fields - Use `include` wisely (avoid N+1 queries) - Add indexes for frequently queried fields - Use pagination for large datasets --- ## đŸŽ¯ API Response Format ### 1. Success Response ```javascript // Single resource res.status(200).json({ id: 1, title: { th: "...", en: "..." }, ... }); // List with pagination res.status(200).json({ data: [...], pagination: { page: 1, limit: 20, total: 150, totalPages: 8 } }); ``` ### 2. Error Response ```javascript res.status(400).json({ error: { code: "VALIDATION_ERROR", message: "Invalid input data", details: [ { field: "email", message: "Email is required" } ] } }); ``` ### 3. Status Codes - `200` OK - Success - `201` Created - Resource created - `204` No Content - Success with no response body - `400` Bad Request - Validation error - `401` Unauthorized - Not authenticated - `403` Forbidden - Not authorized - `404` Not Found - Resource not found - `409` Conflict - Duplicate entry - `422` Unprocessable Entity - Business logic error - `500` Internal Server Error - Server error --- ## 🔄 Business Logic Rules ### 1. Course Management - New courses start as `DRAFT` - Must have â‰Ĩ1 chapter and â‰Ĩ3 lessons before submission - Only course owner or primary instructor can submit - Admin can approve/reject ### 2. Lesson Prerequisites ```javascript // Check prerequisites before allowing access const canAccess = await checkLessonAccess(userId, lessonId); if (!canAccess.allowed) { return res.status(403).json({ error: { code: "LESSON_LOCKED", message: canAccess.reason, required_lessons: canAccess.requiredLessons } }); } ``` ### 3. Quiz Attempts ```javascript // Validate before allowing quiz attempt if (attempts >= quiz.maxAttempts) { return error("MAX_ATTEMPTS_EXCEEDED"); } if (lastAttempt && (now - lastAttempt) < cooldown) { return error("COOLDOWN_ACTIVE"); } ``` ### 4. Video Progress - Save progress every 5 seconds - Auto-complete at â‰Ĩ90% watched - Use `last_watched_at` for concurrent sessions ### 5. Multi-Instructor - Cannot remove last instructor - Only primary instructor can manage other instructors - Course owner always has full access --- ## đŸ§Ē Testing Rules ### 1. Test Coverage - Write tests for all business logic - Test happy path AND error cases - Test authentication and authorization - Test file uploads ### 2. Test Structure ```javascript describe('Course API', () => { describe('POST /api/instructor/courses', () => { it('should create course with valid data', async () => { // Arrange const courseData = {...}; // Act const response = await request(app) .post('/api/instructor/courses') .set('Authorization', `Bearer ${token}`) .send(courseData); // Assert expect(response.status).toBe(201); expect(response.body).toHaveProperty('id'); }); it('should return 401 without authentication', async () => { // Test unauthorized access }); }); }); ``` --- ## 📝 Code Documentation ### 1. JSDoc Comments ```javascript /** * Create a new course * @param {Object} req - Express request object * @param {Object} req.body - Course data * @param {Object} req.body.title - Course title (th/en) * @param {number} req.body.category_id - Category ID * @param {Object} res - Express response object * @returns {Promise} Created course */ async function createCourse(req, res) { // Implementation } ``` ### 2. Inline Comments ```javascript // Only for complex logic // Explain WHY, not WHAT // ✅ Good // Use highest score to determine pass/fail (per quiz settings) const finalScore = Math.max(...attemptScores); // ❌ Bad // Get max score const finalScore = Math.max(...attemptScores); ``` --- ## 🚨 Error Handling ### 1. Try-Catch Pattern ```javascript async function createCourse(req, res) { try { // Validate input const { error, value } = courseSchema.validate(req.body); if (error) { return res.status(400).json({ error: { code: "VALIDATION_ERROR", message: error.details[0].message } }); } // Business logic const course = await courseService.create(value, req.user.id); return res.status(201).json(course); } catch (err) { console.error('Create course error:', err); return res.status(500).json({ error: { code: "INTERNAL_ERROR", message: "Failed to create course" } }); } } ``` ### 2. Custom Error Classes ```javascript class ValidationError extends Error { constructor(message, details) { super(message); this.name = 'ValidationError'; this.statusCode = 400; this.details = details; } } class UnauthorizedError extends Error { constructor(message = 'Unauthorized') { super(message); this.name = 'UnauthorizedError'; this.statusCode = 401; } } ``` --- ## 🔍 Logging Rules ### 1. What to Log ```javascript // ✅ Log important events logger.info('User logged in', { userId, timestamp }); logger.info('Course created', { courseId, instructorId }); // ✅ Log errors with context logger.error('File upload failed', { error: err.message, userId, filename }); // ❌ Don't log sensitive data logger.info('User logged in', { password }); // NEVER! ``` ### 2. Log Levels - `error` - Errors that need attention - `warn` - Warnings (deprecated features, etc.) - `info` - Important events (login, course creation) - `debug` - Detailed debugging (development only) --- ## ⚡ Performance Rules ### 1. Database Queries ```javascript // ✅ Use pagination const courses = await prisma.course.findMany({ skip: (page - 1) * limit, take: limit }); // ✅ Limit relations depth const course = await prisma.course.findUnique({ include: { chapters: { include: { lessons: true } // Don't go deeper unless necessary } } }); ``` ### 2. Caching (Redis) ```javascript // Cache frequently accessed data const cacheKey = `course:${courseId}`; const cached = await redis.get(cacheKey); if (cached) { return JSON.parse(cached); } const course = await prisma.course.findUnique(...); await redis.setex(cacheKey, 3600, JSON.stringify(course)); return course; ``` ### 3. File Processing ```javascript // Process large files asynchronously const job = await queue.add('process-video', { videoPath, lessonId }); return res.status(202).json({ message: 'Video processing started', jobId: job.id }); ``` --- ## 🔒 Environment Variables ### Required Variables ```bash # Application NODE_ENV=development PORT=4000 APP_URL=http://localhost:4000 # Database DATABASE_URL=postgresql://... # Redis REDIS_URL=redis://... # MinIO/S3 S3_ENDPOINT=http://... S3_ACCESS_KEY=... S3_SECRET_KEY=... # JWT JWT_SECRET=... JWT_EXPIRES_IN=24h # Email (Mailhog in dev) SMTP_HOST=... SMTP_PORT=1025 ``` ### Loading Environment Variables ```javascript // ✅ Use dotenv require('dotenv').config(); // ✅ Validate required variables const requiredEnvVars = ['DATABASE_URL', 'JWT_SECRET', ...]; requiredEnvVars.forEach(key => { if (!process.env[key]) { throw new Error(`Missing required environment variable: ${key}`); } }); ``` --- ## đŸ“Ļ Dependencies ### Core Dependencies - `express` - Web framework - `@prisma/client` - Database ORM - `bcrypt` - Password hashing - `jsonwebtoken` - JWT authentication - `joi` or `zod` - Input validation - `multer` - File uploads - `aws-sdk` or `minio` - S3 storage - `redis` - Caching - `winston` - Logging ### Dev Dependencies - `nodemon` - Auto-restart - `jest` - Testing - `supertest` - API testing - `eslint` - Linting - `prettier` - Code formatting --- ## 🚀 Deployment Checklist - [ ] All environment variables set - [ ] Database migrations run - [ ] Redis connection tested - [ ] MinIO/S3 buckets created - [ ] JWT secret is strong and unique - [ ] CORS configured correctly - [ ] Rate limiting enabled - [ ] Logging configured - [ ] Error tracking setup (Sentry, etc.) - [ ] Health check endpoint working --- **Remember**: Security first, code quality second, features third!