1. Latar Belakang
Hari ke-empatbelas di Perwira Learning Center dan masih di Express.js kita akan Middleware Dasar. Midmembahas pembelajaran Express.js di Perwira Learning Center membahas middleware itu ibarat jalur produksi di pabrik:
- Request = Bahan baku masuk
- Middleware = Mesin-mesin di jalur produksi (pemeriksaan, pembersihan, pengolahan)
- Response = Produk jadi keluar
Setiap middleware adalah "mesin" yang melakukan tugas spesifik sebelum request sampai ke handler utama. Tanpa middleware, kita harus menulis semua logika di setiap route!
2. Alat dan Bahan
a. Perangkat Lunak
- Express.js Project yang sudah berjalan
- Postman - Untuk testing middleware behavior
- VS Code - Untuk coding
- Node.js - Runtime environment
b. Perangkat Keras
- Laptop/PC standar
3. Pembahasan
3.1 Apa itu Middleware?
Middleware = Fungsi yang memiliki akses ke request object (req), response object (res), dan fungsi next() di request-response cycle.
Analogi Jalur Produksi:
📦 RAW MATERIAL (Request)
↓
🔧 MIDDLEWARE 1: Quality Check
↓
🔧 MIDDLEWARE 2: Cleaning
↓
🔧 MIDDLEWARE 3: Processing
↓
🎯 FINAL HANDLER: Assembly
↓
📤 FINISHED PRODUCT (Response)Karakteristik Middleware:
- Bisa mengubah
reqdanresobject - Bisa mengakhiri request-response cycle (dengan
res.send()) - Bisa memanggil middleware berikutnya (dengan
next()) - Dieksekusi berurutan sesuai urutan pendefinisian
3.2 Jenis-jenis Middleware
// 1. APPLICATION-LEVEL MIDDLEWARE app.use((req, res, next) => { // Aplikasi-wide middleware next(); }); // 2. ROUTER-LEVEL MIDDLEWARE router.use((req, res, next) => { // Hanya untuk router tertentu next(); }); // 3. ERROR-HANDLING MIDDLEWARE app.use((err, req, res, next) => { // 4 parameter = error handler res.status(500).send('Error!'); }); // 4. BUILT-IN MIDDLEWARE app.use(express.json()); app.use(express.urlencoded({ extended: true })); // 5. THIRD-PARTY MIDDLEWARE app.use(cors()); app.use(helmet());
3.3 Praktik Lengkap: Sistem dengan Berbagai Middleware
Buat file middleware-practice.js:
const express = require('express'); const app = express(); const PORT = 3006; // ==================== 1. MIDDLEWARE DASAR ==================== // 🎯 MIDDLEWARE 1: Request Logger (Application-level) app.use((req, res, next) => { const timestamp = new Date().toISOString(); const method = req.method; const url = req.originalUrl; const ip = req.ip || req.connection.remoteAddress; console.log(`📝 [${timestamp}] ${method} ${url} from ${ip}`); // Tambahkan request ID untuk tracking req.requestId = `req_${Date.now()}_${Math.random().toString(36).substr(2, 9)}`; // Tambahkan start time untuk menghitung durasi req.startTime = Date.now(); next(); // Lanjut ke middleware berikutnya }); // 🎯 MIDDLEWARE 2: Security Headers app.use((req, res, next) => { // Set security headers res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('X-Frame-Options', 'DENY'); res.setHeader('X-XSS-Protection', '1; mode=block'); // Tambahkan custom header res.setHeader('X-Request-ID', req.requestId); next(); }); // 🎯 MIDDLEWARE 3: Body Parser (Built-in) app.use(express.json()); // Parse JSON bodies app.use(express.urlencoded({ extended: true })); // Parse URL-encoded bodies // 🎯 MIDDLEWARE 4: CORS Handler (Custom) app.use((req, res, next) => { // Simple CORS handling res.setHeader('Access-Control-Allow-Origin', '*'); res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-Request-ID'); // Handle preflight requests if (req.method === 'OPTIONS') { return res.status(200).end(); } next(); }); // ==================== 2. ROUTE-SPECIFIC MIDDLEWARE ==================== // 🔐 Middleware untuk Authentication const requireAuth = (req, res, next) => { console.log(`🔐 Auth middleware dipanggil untuk: ${req.method} ${req.originalUrl}`); const authHeader = req.headers.authorization; if (!authHeader || !authHeader.startsWith('Bearer ')) { return res.status(401).json({ success: false, error: 'Unauthorized', message: 'Token authentication required', requestId: req.requestId }); } // Simulasi token validation const token = authHeader.split(' ')[1]; const validTokens = ['admin-token-123', 'user-token-456']; if (!validTokens.includes(token)) { return res.status(403).json({ success: false, error: 'Forbidden', message: 'Invalid or expired token', requestId: req.requestId }); } // Simpan user info di request object req.user = { id: token === 'admin-token-123' ? 1 : 2, role: token === 'admin-token-123' ? 'admin' : 'user', token: token }; console.log(`✅ User authenticated: ${req.user.role} (ID: ${req.user.id})`); next(); }; // 👑 Middleware untuk Authorization (Admin only) const requireAdmin = (req, res, next) => { console.log(`👑 Admin check middleware dipanggil`); if (!req.user) { return res.status(401).json({ success: false, error: 'Unauthorized', message: 'Authentication required first' }); } if (req.user.role !== 'admin') { return res.status(403).json({ success: false, error: 'Forbidden', message: 'Admin privileges required', userRole: req.user.role, requiredRole: 'admin' }); } console.log(`✅ Admin access granted for user ${req.user.id}`); next(); }; // 📊 Middleware untuk Rate Limiting (Simple version) const requestCounts = new Map(); const rateLimiter = (req, res, next) => { const ip = req.ip || 'unknown'; const now = Date.now(); const windowMs = 60 * 1000; // 1 minute const maxRequests = 10; // Clean old entries const userRequests = requestCounts.get(ip) || []; const recentRequests = userRequests.filter(time => now - time < windowMs); if (recentRequests.length >= maxRequests) { return res.status(429).json({ success: false, error: 'Too Many Requests', message: 'Rate limit exceeded', retryAfter: Math.ceil((recentRequests[0] + windowMs - now) / 1000), limit: `${maxRequests} requests per minute` }); } // Add current request recentRequests.push(now); requestCounts.set(ip, recentRequests); // Add headers res.setHeader('X-RateLimit-Limit', maxRequests); res.setHeader('X-RateLimit-Remaining', maxRequests - recentRequests.length); res.setHeader('X-RateLimit-Reset', Math.ceil((recentRequests[0] + windowMs) / 1000)); next(); }; // ==================== 3. LOGGING MIDDLEWARE ==================== // Request Logging Middleware (dengan warna!) const coloredLogger = (req, res, next) => { const originalSend = res.send; const startTime = Date.now(); // Log request console.log(`➡️ ${getColoredMethod(req.method)} ${req.originalUrl}`); // Override res.send untuk log response res.send = function(body) { const duration = Date.now() - startTime; const statusCode = res.statusCode; const statusColor = getStatusColor(statusCode); console.log(`⬅️ ${statusColor}${statusCode}\x1b[0m ${req.method} ${req.originalUrl} - ${duration}ms`); // Log response body untuk debugging (hanya di development) if (process.env.NODE_ENV === 'development' && body) { try { const parsedBody = typeof body === 'string' ? JSON.parse(body) : body; console.log(` Response:`, JSON.stringify(parsedBody, null, 2).substring(0, 200) + '...'); } catch { console.log(` Response: ${typeof body === 'string' ? body.substring(0, 100) : 'binary data'}`); } } return originalSend.call(this, body); }; next(); }; // Helper functions untuk warna function getColoredMethod(method) { const colors = { GET: '\x1b[32m', // hijau POST: '\x1b[34m', // biru PUT: '\x1b[33m', // kuning DELETE: '\x1b[31m', // merah PATCH: '\x1b[35m' // magenta }; return `${colors[method] || '\x1b[37m'}${method}\x1b[0m`; } function getStatusColor(statusCode) { if (statusCode >= 500) return '\x1b[31m'; // merah untuk server error if (statusCode >= 400) return '\x1b[33m'; // kuning untuk client error if (statusCode >= 300) return '\x1b[36m'; // cyan untuk redirect return '\x1b[32m'; // hijau untuk success } // Gunakan logging middleware app.use(coloredLogger); // ==================== 4. DATA TRANSFORMATION MIDDLEWARE ==================== // Middleware untuk transformasi request data const requestTransformer = (req, res, next) => { console.log('🔄 Request Transformer Middleware'); // 1. Trim semua string fields if (req.body && typeof req.body === 'object') { Object.keys(req.body).forEach(key => { if (typeof req.body[key] === 'string') { req.body[key] = req.body[key].trim(); } }); } // 2. Convert string numbers to actual numbers if (req.query && typeof req.query === 'object') { Object.keys(req.query).forEach(key => { const value = req.query[key]; if (!isNaN(value) && value.trim() !== '') { req.query[key] = Number(value); } }); } // 3. Add metadata req.metadata = { transformed: true, timestamp: new Date().toISOString(), contentType: req.headers['content-type'] || 'unknown' }; next(); }; // Middleware untuk transformasi response data const responseTransformer = (req, res, next) => { const originalJson = res.json; res.json = function(data) { // Transform response structure const transformedData = { success: true, data: data, metadata: { requestId: req.requestId, timestamp: new Date().toISOString(), duration: Date.now() - (req.startTime || Date.now()), ...req.metadata } }; return originalJson.call(this, transformedData); }; next(); }; // Gunakan transformer middleware app.use(requestTransformer); app.use(responseTransformer); // ==================== 5. ROUTES DENGAN BERBAGAI MIDDLEWARE ==================== // 🏠 PUBLIC ROUTE (tanpa authentication) app.get('/', (req, res) => { res.json({ message: 'Welcome to Middleware Practice API!', endpoints: { public: { '/': 'This page', '/api/public': 'Public data', '/api/register': 'Register new user (POST)' }, protected: { '/api/profile': 'Get user profile (requires auth)', '/api/admin': 'Admin panel (requires admin)', '/api/limited': 'Rate limited endpoint' } }, requestInfo: { id: req.requestId, ip: req.ip, userAgent: req.headers['user-agent'], metadata: req.metadata } }); }); // 🌐 PUBLIC API app.get('/api/public', (req, res) => { res.json({ message: 'This is public data', serverTime: new Date().toISOString(), stats: { totalRequests: Array.from(requestCounts.values()).reduce((sum, arr) => sum + arr.length, 0), uniqueIPs: requestCounts.size } }); }); // 📝 REGISTER (public but with rate limiting) app.post('/api/register', rateLimiter, (req, res) => { const { name, email } = req.body; if (!name || !email) { return res.status(400).json({ success: false, error: 'Validation Error', message: 'Name and email are required' }); } // Simulasi user creation const newUser = { id: Date.now(), name, email, registeredAt: new Date().toISOString(), requestId: req.requestId }; res.status(201).json(newUser); }); // 🔐 PROTECTED ROUTE (requires authentication) app.get('/api/profile', requireAuth, (req, res) => { res.json({ message: 'Welcome to your profile!', user: req.user, profile: { name: 'John Doe', email: 'john@example.com', joined: '2024-01-01', lastLogin: new Date().toISOString() }, recentActivity: [ { action: 'Login', time: '10:30 AM' }, { action: 'Viewed profile', time: '10:31 AM' } ] }); }); // 👑 ADMIN ROUTE (requires admin role) app.get('/api/admin', requireAuth, requireAdmin, (req, res) => { res.json({ message: 'Welcome to Admin Panel!', user: req.user, adminFeatures: [ 'View all users', 'Manage permissions', 'System configuration', 'View analytics' ], systemStats: { totalUsers: 150, activeUsers: 89, serverUptime: '99.9%', memoryUsage: '45%' } }); }); // 🚫 RATE LIMITED ROUTE app.get('/api/limited', rateLimiter, (req, res) => { res.json({ message: 'This endpoint is rate limited', note: 'You can only call this 10 times per minute', yourIP: req.ip, remainingRequests: res.getHeader('X-RateLimit-Remaining') }); }); // 🔗 MIDDLEWARE CHAINING EXAMPLE app.get('/api/chain', // Middleware 1: Logging (req, res, next) => { console.log('🔗 Middleware 1: Logging'); req.chainStep = 1; next(); }, // Middleware 2: Validation (req, res, next) => { console.log('🔗 Middleware 2: Validation'); req.chainStep = 2; // Cek query parameter if (!req.query.apiKey) { return res.status(400).json({ error: 'Missing apiKey query parameter' }); } next(); }, // Middleware 3: Processing (req, res, next) => { console.log('🔗 Middleware 3: Processing'); req.chainStep = 3; req.processedData = { originalQuery: req.query, processedAt: new Date().toISOString() }; next(); }, // Final Handler (req, res) => { console.log('🎯 Final Handler'); res.json({ message: 'Middleware chain completed successfully!', steps: req.chainStep, processedData: req.processedData, requestId: req.requestId }); } ); // ==================== 6. ERROR HANDLING MIDDLEWARE ==================== // Custom error class untuk middleware class MiddlewareError extends Error { constructor(message, statusCode = 500) { super(message); this.statusCode = statusCode; this.name = 'MiddlewareError'; } } // Error simulation middleware app.get('/api/error/simulate', (req, res, next) => { const errorType = req.query.type || 'generic'; switch(errorType) { case 'validation': const error = new MiddlewareError('Validation failed', 400); error.details = { field: 'email', reason: 'Invalid format' }; return next(error); case 'database': return next(new Error('Database connection failed')); case 'async': // Simulasi async error setTimeout(() => { next(new Error('Async operation failed')); }, 100); break; default: throw new Error('Generic error simulation'); } }); // Global error handling middleware (harus punya 4 parameter) app.use((err, req, res, next) => { console.error('🔥 Error Handler Middleware:', err); const statusCode = err.statusCode || 500; const errorResponse = { success: false, error: { type: err.name || 'InternalServerError', message: err.message || 'Something went wrong', statusCode: statusCode, timestamp: new Date().toISOString(), requestId: req.requestId, path: req.originalUrl } }; // Tambahkan details jika ada if (err.details) { errorResponse.error.details = err.details; } // Stack trace hanya di development if (process.env.NODE_ENV === 'development') { errorResponse.error.stack = err.stack; } res.status(statusCode).json(errorResponse); }); // ==================== 7. ASYNC MIDDLEWARE ==================== // Async middleware example const asyncMiddleware = fn => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; // Database simulation const mockDatabase = { findUser: (id) => { return new Promise((resolve, reject) => { setTimeout(() => { if (id === 1) { resolve({ id: 1, name: 'John Doe', role: 'admin' }); } else if (id === 2) { resolve({ id: 2, name: 'Jane Smith', role: 'user' }); } else { reject(new Error('User not found')); } }, 100); }); } }; // Middleware async untuk fetch user data const fetchUserData = asyncMiddleware(async (req, res, next) => { const userId = parseInt(req.query.userId); if (!userId) { throw new MiddlewareError('userId query parameter is required', 400); } const user = await mockDatabase.findUser(userId); req.userData = user; next(); }); // Route dengan async middleware app.get('/api/user', fetchUserData, (req, res) => { res.json({ success: true, message: 'User data retrieved', data: req.userData, fetchedAt: new Date().toISOString() }); }); // ==================== 8. CONDITIONAL MIDDLEWARE ==================== // Middleware yang hanya berjalan di kondisi tertentu const developmentOnly = (req, res, next) => { if (process.env.NODE_ENV === 'development') { console.log('🔧 Development middleware active'); req.isDevelopment = true; // Tambahkan debug info res.setHeader('X-Debug-Mode', 'enabled'); res.setHeader('X-Node-Env', process.env.NODE_ENV); } next(); }; // Middleware berdasarkan path const apiLogger = (paths) => { return (req, res, next) => { if (paths.some(path => req.originalUrl.startsWith(path))) { console.log(`📊 API Logger: ${req.method} ${req.originalUrl}`); } next(); }; }; // Gunakan conditional middleware app.use(developmentOnly); app.use(apiLogger(['/api/admin', '/api/profile'])); // ==================== 9. THIRD-PARTY MIDDLEWARE SIMULATION ==================== // Simulasi helmet.js (security middleware) const securityMiddleware = (req, res, next) => { console.log('🛡️ Security middleware activated'); // Remove potentially sensitive headers res.removeHeader('X-Powered-By'); // Add security headers const securityHeaders = { 'Strict-Transport-Security': 'max-age=31536000; includeSubDomains', 'Content-Security-Policy': "default-src 'self'", 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'strict-origin-when-cross-origin' }; Object.entries(securityHeaders).forEach(([key, value]) => { res.setHeader(key, value); }); next(); }; // Simulasi morgan (logging middleware) const morganStyleLogger = (req, res, next) => { const start = Date.now(); res.on('finish', () => { const duration = Date.now() - start; const status = res.statusCode; const length = res.get('Content-Length') || 0; console.log(`${req.method} ${req.originalUrl} ${status} ${length} - ${duration}ms`); }); next(); }; // Gunakan third-party style middleware app.use(securityMiddleware); app.use(morganStyleLogger); // ==================== 10. MIDDLEWARE ORDER DEMONSTRATION ==================== // Demonstrasi pentingnya urutan middleware app.get('/api/order-demo', // Middleware A: Tambahkan property (req, res, next) => { console.log('Middleware A executed'); req.demo = 'A'; next(); }, // Middleware B: Modify property (req, res, next) => { console.log('Middleware B executed'); req.demo += ' → B'; next(); }, // Middleware C: Async operation asyncMiddleware(async (req, res, next) => { console.log('Middleware C (async) executed'); await new Promise(resolve => setTimeout(resolve, 50)); req.demo += ' → C'; next(); }), // Final handler (req, res) => { res.json({ message: 'Middleware order demonstration', executionOrder: req.demo, explanation: 'Middleware dieksekusi berurutan dari atas ke bawah', currentTime: new Date().toISOString() }); } ); // ==================== 404 HANDLER (Middleware terakhir) ==================== app.use('*', (req, res) => { res.status(404).json({ success: false, error: 'Not Found', message: `Cannot ${req.method} ${req.originalUrl}`, requestId: req.requestId, suggestion: 'Check the available endpoints at GET /' }); }); // ==================== RESPONSE TIME MIDDLEWARE ==================== // Middleware untuk mengukur response time app.use((req, res, next) => { const start = process.hrtime(); res.on('finish', () => { const diff = process.hrtime(start); const responseTime = diff[0] * 1e3 + diff[1] * 1e-6; // dalam ms // Log response time yang lambat if (responseTime > 1000) { // lebih dari 1 detik console.warn(`🐢 Slow response: ${req.method} ${req.originalUrl} took ${responseTime.toFixed(2)}ms`); } // Tambahkan header res.setHeader('X-Response-Time', `${responseTime.toFixed(2)}ms`); }); next(); }); // ==================== START SERVER ==================== app.listen(PORT, () => { console.log("=".repeat(70)); console.log("⚙️ MIDDLEWARE PRACTICE SERVER"); console.log("=".repeat(70)); console.log(`🌐 Server: http://localhost:${PORT}`); console.log(`🔧 Mode: ${process.env.NODE_ENV || 'development'}`); console.log(""); console.log("🎯 COBA MIDDLEWARE BERIKUT:"); console.log(""); console.log("1. Logging Middleware:"); console.log(" GET / → Lihat log warna-warni di terminal"); console.log(""); console.log("2. Authentication Middleware:"); console.log(" GET /api/profile → 401 Unauthorized"); console.log(" GET /api/profile"); console.log(" Headers: { Authorization: Bearer user-token-456 }"); console.log(""); console.log("3. Authorization Middleware:"); console.log(" GET /api/admin"); console.log(" Headers: { Authorization: Bearer user-token-456 } → 403 Forbidden"); console.log(" Headers: { Authorization: Bearer admin-token-123 } → Success"); console.log(""); console.log("4. Rate Limiting Middleware:"); console.log(" GET /api/limited → Refresh cepat untuk lihat 429 error"); console.log(""); console.log("5. Middleware Chaining:"); console.log(" GET /api/chain → Tanpa query"); console.log(" GET /api/chain?apiKey=123 → Dengan query"); console.log(""); console.log("6. Error Handling Middleware:"); console.log(" GET /api/error/simulate?type=validation"); console.log(" GET /api/error/simulate?type=database"); console.log(""); console.log("7. Async Middleware:"); console.log(" GET /api/user → Error (no userId)"); console.log(" GET /api/user?userId=1 → Success"); console.log(" GET /api/user?userId=999 → Error (not found)"); console.log(""); console.log("8. Middleware Order:"); console.log(" GET /api/order-demo → Lihat urutan eksekusi"); console.log("=".repeat(70)); });
3.4 Testing Middleware dengan Postman
Test 1: Authentication Middleware
# Tanpa token → 401 Unauthorized GET http://localhost:3006/api/profile # Dengan user token → Success GET http://localhost:3006/api/profile Authorization: Bearer user-token-456 # Dengan admin token → Success GET http://localhost:3006/api/profile Authorization: Bearer admin-token-123
Test 2: Authorization Middleware
# User mencoba akses admin → 403 Forbidden GET http://localhost:3006/api/admin Authorization: Bearer user-token-456 # Admin akses admin panel → Success GET http://localhost:3006/api/admin Authorization: Bearer admin-token-123
Test 3: Rate Limiting
# Coba refresh 10+ kali dalam 1 menit GET http://localhost:3006/api/limited
Test 4: Middleware Chain
# Tanpa apiKey → Error GET http://localhost:3006/api/chain # Dengan apiKey → Success GET http://localhost:3006/api/chain?apiKey=123
3.5 Middleware Flow Control
// Middleware dengan conditional next() app.use((req, res, next) => { console.log('Middleware 1'); // Skip ke error handler jika ada error if (req.query.error) { return next(new Error('Simulated error')); } // Skip ke route berikutnya jika condition true if (req.query.skip) { return next('route'); // Skip ke route handler berikutnya } // Continue ke middleware berikutnya next(); }); // Middleware yang hanya berjalan untuk path tertentu app.use('/admin', (req, res, next) => { console.log('Admin middleware hanya untuk /admin routes'); next(); }); // Multiple middleware untuk route tertentu app.get('/secure', middleware1, middleware2, middleware3, (req, res) => { res.send('Secure route'); } );
3.6 Best Practices Middleware
1. Order Matters!
// ❌ Wrong order app.use(errorHandler); // Error handler harus di akhir! app.use(authMiddleware); app.get('/api/data', handler); // ✅ Correct order app.use(authMiddleware); // Auth dulu app.get('/api/data', handler); // Route app.use(errorHandler); // Error handler di akhir
2. Use app.use() vs app.METHOD()
// app.use() → Untuk semua HTTP methods app.use('/api', apiMiddleware); // GET, POST, PUT, DELETE semua kena // app.METHOD() → Untuk method spesifik app.get('/api/data', getMiddleware); // Hanya GET app.post('/api/data', postMiddleware); // Hanya POST
3. Middleware Factory Pattern
// Middleware factory untuk konfigurasi const createLogger = (options = {}) => { return (req, res, next) => { const shouldLog = options.enabled !== false; if (shouldLog) { console.log(`[${options.name || 'Logger'}] ${req.method} ${req.url}`); } if (options.addTimestamp) { req.timestamp = new Date().toISOString(); } next(); }; }; // Gunakan dengan konfigurasi app.use(createLogger({ name: 'API', enabled: true, addTimestamp: true }));
4. Error Handling di Async Middleware
// ❌ Wrong (error tidak tertangkap) app.use(async (req, res, next) => { const data = await fetchData(); // Jika error, crash! next(); }); // ✅ Correct (gunakan try-catch atau wrapper) app.use(async (req, res, next) => { try { const data = await fetchData(); next(); } catch (error) { next(error); // Pass error ke error handler } }); // Atau gunakan wrapper function const asyncMiddleware = (fn) => (req, res, next) => { Promise.resolve(fn(req, res, next)).catch(next); }; app.use(asyncMiddleware(async (req, res, next) => { const data = await fetchData(); next(); }));
3.7 Common Third-Party Middleware
// Popular third-party middleware const express = require('express'); const helmet = require('helmet'); // Security headers const cors = require('cors'); // CORS handling const morgan = require('morgan'); // Logging const compression = require('compression'); // Response compression const cookieParser = require('cookie-parser'); // Cookie parsing const app = express(); // Basic setup dengan third-party middleware app.use(helmet()); // Security app.use(cors()); // CORS app.use(compression()); // Compression app.use(morgan('combined')); // Logging app.use(cookieParser()); // Cookies app.use(express.json()); // Body parsing // Custom middleware setelah third-party app.use(myCustomMiddleware);
3.8 Middleware Performance Tips
// 1. Skip middleware untuk static files app.use('/public', express.static('public')); // 2. Conditional middleware loading if (process.env.NODE_ENV === 'development') { app.use(require('morgan')('dev')); // Logging hanya di dev } // 3. Cache expensive operations const cache = new Map(); app.use((req, res, next) => { const cacheKey = req.originalUrl; if (cache.has(cacheKey)) { return res.json(cache.get(cacheKey)); } // Override res.json untuk caching const originalJson = res.json; res.json = function(data) { cache.set(cacheKey, data); setTimeout(() => cache.delete(cacheKey), 60000); // 1 minute TTL return originalJson.call(this, data); }; next(); });
3.9 Testing Middleware
// Unit test untuk middleware const testMiddleware = require('./middleware'); const request = require('supertest'); const express = require('express'); describe('Authentication Middleware', () => { it('should return 401 without token', async () => { const app = express(); app.use(testMiddleware.requireAuth); app.get('/test', (req, res) => res.send('OK')); const response = await request(app).get('/test'); expect(response.status).toBe(401); }); it('should call next() with valid token', async () => { const app = express(); app.use(testMiddleware.requireAuth); app.get('/test', (req, res) => res.json({ user: req.user })); const response = await request(app) .get('/test') .set('Authorization', 'Bearer valid-token'); expect(response.status).toBe(200); expect(response.body.user).toBeDefined(); }); });
3.10 Latihan Praktikum
Exercise 1: Audit Logging Middleware
// Buat middleware untuk audit logging: // 1. Log semua aktivitas user (login, logout, data changes) // 2. Simpan ke file/database // 3. Include: timestamp, user ID, action, IP address // 4. Skip logging untuk request tertentu (health checks)
Exercise 2: Request Validation Chain
// Buat middleware chain untuk validasi: // 1. Schema validation (format data) // 2. Business rule validation // 3. Permission validation // 4. Rate limit validation // 5. Jika semua lolos, baru execute handler
Exercise 3: Feature Flag Middleware
// Buat middleware untuk feature flags: // 1. Baca feature flags dari config // 2. Enable/disable routes berdasarkan flag // 3. A/B testing support // 4. Rollout percentage (10% users get new feature)
4. Daftar Pustaka
- Express.js Documentation (n.d.). Using Middleware. https://expressjs.com
- Codepolitan (2023). Menerapkan Middleware dalam Express.js untuk Pengembangan Web yang Efisien. https://www.codepolitan.com
- Express.js Documentation (n.d.). Middleare Cors. https://expressjs.com
- GeeksForGeeks (2026). Middleware in Express. https://www.geeksforgeeks.org
- Scaler (2024). Builtin MiddleWar in ExpressJS. https://www.scaler.com
.png)
0 Komentar