1. Latar Belakang
Hari keenam pembelajaran Express.js di Perwira Learning Center membahas Pengolahan Data Request. Ini ibarat menerima pesanan di restoran:
- Params = Nomor meja (tetap, wajib ada)
- Query = Pilihan tambahan (pedas/kurang pedas, es/kurang es)
- Body = Menu yang dipesan (nasi goreng, ayam bakar, dll)
Tanpa memahami cara mengambil data dari client, server kita seperti pelayan yang tuli: tidak tahu apa yang mau dipesan customer!
2. Alat dan Bahan
a. Perangkat Lunak
- Express.js Project yang sudah berjalan
- Postman - Untuk testing berbagai jenis request
- VS Code - Untuk coding
- Browser - Untuk testing GET requests
- Node.js - Runtime environment
b. Perangkat Keras
- Laptop/PC standar
3. Pembahasan
3.1 Tiga Jenis Data dari Client
Analogi Restoran:
// Contoh request ke restoran: GET /meja/5/pesanan?pedas=true&minuman=es-teh Body: { "makanan": "nasi goreng seafood", "jumlah": 2, "catatan": "tidak pakai telur" } // Breakdown: // Params: /meja/5/pesanan → Meja nomor 5 // Query: ?pedas=true&minuman=es-teh → Pilihan tambahan // Body: { makanan, jumlah, catatan } → Pesanan utama
3.2 Perbedaan Params, Query, dan Body
| Fitur | Params | Query | Body |
|---|---|---|---|
| Lokasi | URL path | URL setelah ? | Request body |
| Contoh | /users/123 | ?page=2&limit=10 | {name: "Andi"} |
| HTTP Method | Semua | GET terutama | POST, PUT, PATCH |
| Wajib/Opsional | Wajib (bagian dari route) | Opsional | Opsional (tapi sering wajib) |
| Ukuran | Terbatas | Terbatas | Bisa besar |
| Use Case | Identifikasi resource | Filter, sort, pagination | Data utama untuk create/update |
3.3 Praktik Lengkap: Sistem Pendaftaran Event
Buat file data-processing.js:
const express = require('express'); const app = express(); const PORT = 3004; // Middleware UNTUK PARSE DATA app.use(express.json()); // Untuk JSON body app.use(express.urlencoded({ extended: true })); // Untuk form data // ==================== DATABASE SIMULASI ==================== let events = [ { id: 1, title: "Workshop JavaScript Dasar", category: "workshop", speaker: "Budi Santoso", date: "2024-03-15", time: "09:00", duration: 3, capacity: 50, registered: 35, price: 150000, location: "Gedung A Lt. 3", tags: ["javascript", "pemula", "coding"], isActive: true }, { id: 2, title: "Seminar AI untuk Bisnis", category: "seminar", speaker: "Dr. Sari Wijaya", date: "2024-03-20", time: "13:00", duration: 2, capacity: 100, registered: 85, price: 0, location: "Auditorium Utama", tags: ["ai", "bisnis", "teknologi"], isActive: true }, { id: 3, title: "Bootcamp Full-Stack Web Dev", category: "bootcamp", speaker: "Team DevHub", date: "2024-04-01", time: "10:00", duration: 40, capacity: 30, registered: 28, price: 2500000, location: "Online", tags: ["web", "fullstack", "react", "nodejs"], isActive: true } ]; let registrations = []; let nextEventId = 4; let nextRegId = 1; // ==================== HOME ROUTE ==================== app.get('/', (req, res) => { res.json({ message: "🎯 Event Registration System API", description: "Demo pengolahan data request: Params, Query, dan Body", endpoints: { events: { getAll: "GET /api/events", getById: "GET /api/events/:id", search: "GET /api/events/search?q=...", filter: "GET /api/events/filter?category=...&price=...", create: "POST /api/events", update: "PUT /api/events/:id", delete: "DELETE /api/events/:id", register: "POST /api/events/:eventId/register" }, registrations: { getAll: "GET /api/registrations", getByEvent: "GET /api/events/:eventId/registrations", getById: "GET /api/registrations/:regId" } }, dataTypes: { params: "URL segment (/:id)", query: "URL query (?key=value)", body: "Request body (JSON/Form)" } }); }); // ==================== 1. PARAMETERS (ROUTE PARAMS) ==================== // 🔍 GET EVENT BY ID (Parameter: id) app.get('/api/events/:id', (req, res) => { console.log("📌 ROUTE PARAMS:", req.params); const eventId = parseInt(req.params.id); // Validasi: apakah id number? if (isNaN(eventId)) { return res.status(400).json({ success: false, error: "Invalid Parameter", message: "Event ID harus berupa angka", received: req.params.id }); } const event = events.find(e => e.id === eventId); if (!event) { return res.status(404).json({ success: false, error: "Not Found", message: `Event dengan ID ${eventId} tidak ditemukan` }); } res.json({ success: true, message: `Event ID ${eventId} ditemukan`, data: event }); }); // 👥 GET REGISTRATIONS FOR SPECIFIC EVENT (Multiple params) app.get('/api/events/:eventId/registrations/:status?', (req, res) => { console.log("📌 MULTIPLE PARAMS:", req.params); const eventId = parseInt(req.params.eventId); const status = req.params.status; // Optional parameter // Validasi event const event = events.find(e => e.id === eventId); if (!event) { return res.status(404).json({ success: false, error: "Event Not Found", message: `Event dengan ID ${eventId} tidak ditemukan` }); } // Filter registrations let eventRegistrations = registrations.filter(r => r.eventId === eventId); if (status) { eventRegistrations = eventRegistrations.filter(r => r.status === status); } res.json({ success: true, message: `Registrasi untuk Event "${event.title}"${status ? ` (status: ${status})` : ''}`, event: { id: event.id, title: event.title, registered: event.registered, capacity: event.capacity }, count: eventRegistrations.length, data: eventRegistrations }); }); // ==================== 2. QUERY PARAMETERS ==================== // 🔎 SEARCH EVENTS (Query: q) app.get('/api/events/search', (req, res) => { console.log("🔍 QUERY PARAMS:", req.query); const searchQuery = req.query.q?.toLowerCase() || ''; if (!searchQuery) { return res.status(400).json({ success: false, error: "Missing Query Parameter", message: "Query parameter 'q' diperlukan untuk pencarian", example: "/api/events/search?q=javascript" }); } const results = events.filter(event => event.title.toLowerCase().includes(searchQuery) || event.speaker.toLowerCase().includes(searchQuery) || event.tags.some(tag => tag.toLowerCase().includes(searchQuery)) || event.category.toLowerCase().includes(searchQuery) ); res.json({ success: true, message: `Hasil pencarian untuk "${searchQuery}"`, query: searchQuery, count: results.length, data: results }); }); // 🎯 FILTER EVENTS (Multiple query parameters) app.get('/api/events/filter', (req, res) => { console.log("🎯 MULTIPLE QUERY PARAMS:", req.query); const { category, minPrice, maxPrice, dateFrom, dateTo, sortBy = 'date', order = 'asc', page = 1, limit = 10 } = req.query; // Start with all events let filteredEvents = [...events]; // 1. Filter by category if (category) { filteredEvents = filteredEvents.filter(e => e.category.toLowerCase() === category.toLowerCase() ); } // 2. Filter by price range if (minPrice || maxPrice) { const min = parseFloat(minPrice) || 0; const max = parseFloat(maxPrice) || Infinity; filteredEvents = filteredEvents.filter(e => e.price >= min && e.price <= max ); } // 3. Filter by date range if (dateFrom || dateTo) { const fromDate = dateFrom ? new Date(dateFrom) : new Date('1900-01-01'); const toDate = dateTo ? new Date(dateTo) : new Date('2100-12-31'); filteredEvents = filteredEvents.filter(e => { const eventDate = new Date(e.date); return eventDate >= fromDate && eventDate <= toDate; }); } // 4. Filter active only (default) filteredEvents = filteredEvents.filter(e => e.isActive); // 5. Sorting filteredEvents.sort((a, b) => { if (sortBy === 'date') { return order === 'asc' ? new Date(a.date) - new Date(b.date) : new Date(b.date) - new Date(a.date); } if (sortBy === 'price') { return order === 'asc' ? a.price - b.price : b.price - a.price; } if (sortBy === 'title') { return order === 'asc' ? a.title.localeCompare(b.title) : b.title.localeCompare(a.title); } return 0; }); // 6. Pagination const pageNum = parseInt(page); const limitNum = parseInt(limit); const startIndex = (pageNum - 1) * limitNum; const endIndex = pageNum * limitNum; const paginatedEvents = filteredEvents.slice(startIndex, endIndex); const totalPages = Math.ceil(filteredEvents.length / limitNum); res.json({ success: true, message: "Events filtered successfully", filters: { category, priceRange: { min: minPrice, max: maxPrice }, dateRange: { from: dateFrom, to: dateTo }, sort: { by: sortBy, order } }, pagination: { currentPage: pageNum, totalPages, totalItems: filteredEvents.length, itemsPerPage: limitNum, hasNextPage: endIndex < filteredEvents.length, hasPrevPage: startIndex > 0 }, count: paginatedEvents.length, data: paginatedEvents }); }); // 📊 GET ALL EVENTS WITH QUERY OPTIONS app.get('/api/events', (req, res) => { console.log("📊 QUERY FOR ALL EVENTS:", req.query); const { category, freeOnly, upcomingOnly, sort = 'date:asc' } = req.query; let filteredEvents = [...events]; // Filter by category if (category) { filteredEvents = filteredEvents.filter(e => e.category.toLowerCase() === category.toLowerCase() ); } // Filter free events only if (freeOnly === 'true') { filteredEvents = filteredEvents.filter(e => e.price === 0); } // Filter upcoming events if (upcomingOnly === 'true') { const today = new Date(); filteredEvents = filteredEvents.filter(e => new Date(e.date) >= today); } // Filter active only filteredEvents = filteredEvents.filter(e => e.isActive); // Sorting const [sortField, sortOrder] = sort.split(':'); filteredEvents.sort((a, b) => { let aVal, bVal; if (sortField === 'date') { aVal = new Date(a.date); bVal = new Date(b.date); } else if (sortField === 'price') { aVal = a.price; bVal = b.price; } else if (sortField === 'title') { aVal = a.title; bVal = b.title; } else { aVal = a[sortField]; bVal = b[sortField]; } if (sortOrder === 'desc') { return aVal < bVal ? 1 : -1; } return aVal > bVal ? 1 : -1; }); res.json({ success: true, message: "Events retrieved successfully", filters: { category, freeOnly, upcomingOnly, sort }, count: filteredEvents.length, data: filteredEvents }); }); // ==================== 3. REQUEST BODY ==================== // ➕ CREATE NEW EVENT (POST dengan Body) app.post('/api/events', (req, res) => { console.log("📦 REQUEST BODY:", req.body); console.log("📦 REQUEST HEADERS:", req.headers['content-type']); // Validasi: data harus ada if (!req.body || Object.keys(req.body).length === 0) { return res.status(400).json({ success: false, error: "Empty Request Body", message: "Request body tidak boleh kosong", requiredFields: ["title", "category", "speaker", "date"] }); } const { title, category, speaker, date, time = "10:00", duration = 2, capacity = 50, price = 0, location = "TBD", tags = [] } = req.body; // Validasi required fields const missingFields = []; if (!title) missingFields.push("title"); if (!category) missingFields.push("category"); if (!speaker) missingFields.push("speaker"); if (!date) missingFields.push("date"); if (missingFields.length > 0) { return res.status(400).json({ success: false, error: "Missing Required Fields", message: "Field berikut wajib diisi", missing: missingFields }); } // Validasi data types const errors = []; if (typeof title !== 'string') errors.push("title harus string"); if (typeof capacity !== 'number') errors.push("capacity harus number"); if (typeof price !== 'number') errors.push("price harus number"); if (!Array.isArray(tags)) errors.push("tags harus array"); if (errors.length > 0) { return res.status(400).json({ success: false, error: "Invalid Data Types", message: "Tipe data tidak valid", errors: errors }); } // Create new event const newEvent = { id: nextEventId++, title, category: category.toLowerCase(), speaker, date, time, duration: parseInt(duration), capacity: parseInt(capacity), registered: 0, price: parseFloat(price), location, tags: Array.isArray(tags) ? tags : [tags], isActive: true, createdAt: new Date().toISOString(), updatedAt: new Date().toISOString() }; events.push(newEvent); // Response dengan data yang diterima dan diproses res.status(201).json({ success: true, message: "Event berhasil dibuat", receivedData: req.body, // Data asli dari client processedData: newEvent, // Data setelah diproses location: `/api/events/${newEvent.id}` }); }); // ✏️ UPDATE EVENT (PUT dengan Body + Params) app.put('/api/events/:id', (req, res) => { console.log("📦 PUT REQUEST:"); console.log(" Params:", req.params); console.log(" Body:", req.body); const eventId = parseInt(req.params.id); const updateData = req.body; // Validasi event exists const eventIndex = events.findIndex(e => e.id === eventId); if (eventIndex === -1) { return res.status(404).json({ success: false, error: "Not Found", message: `Event dengan ID ${eventId} tidak ditemukan` }); } // Validasi body tidak kosong if (!updateData || Object.keys(updateData).length === 0) { return res.status(400).json({ success: false, error: "Empty Update Data", message: "Data update tidak boleh kosong", currentData: events[eventIndex] }); } // Update event (full update dengan PUT) events[eventIndex] = { ...events[eventIndex], ...updateData, updatedAt: new Date().toISOString() }; res.json({ success: true, message: `Event ID ${eventId} berhasil diupdate`, updatedFields: Object.keys(updateData), data: events[eventIndex] }); }); // 📝 REGISTER FOR EVENT (Params + Body) app.post('/api/events/:eventId/register', (req, res) => { const eventId = parseInt(req.params.eventId); const registrationData = req.body; console.log("🎫 REGISTRATION DATA:"); console.log(" Event ID (params):", eventId); console.log(" Registration Data (body):", registrationData); // 1. Cek event exists dan masih ada kuota const event = events.find(e => e.id === eventId); if (!event) { return res.status(404).json({ success: false, error: "Event Not Found", message: `Event dengan ID ${eventId} tidak ditemukan` }); } if (!event.isActive) { return res.status(400).json({ success: false, error: "Event Inactive", message: `Event "${event.title}" sudah tidak aktif` }); } if (event.registered >= event.capacity) { return res.status(400).json({ success: false, error: "Event Full", message: `Event "${event.title}" sudah penuh`, capacity: event.capacity, registered: event.registered }); } // 2. Validasi data pendaftaran const { name, email, phone } = registrationData; if (!name || !email) { return res.status(400).json({ success: false, error: "Missing Required Fields", message: "Nama dan email wajib diisi", received: registrationData }); } // 3. Cek apakah email sudah terdaftar di event ini const alreadyRegistered = registrations.some( r => r.eventId === eventId && r.email === email ); if (alreadyRegistered) { return res.status(409).json({ success: false, error: "Already Registered", message: `Email ${email} sudah terdaftar untuk event ini` }); } // 4. Buat registrasi const newRegistration = { id: nextRegId++, eventId, name, email, phone: phone || "", registrationDate: new Date().toISOString(), status: "pending", paymentStatus: event.price > 0 ? "unpaid" : "free", ticketNumber: `TICK-${eventId}-${nextRegId.toString().padStart(4, '0')}` }; registrations.push(newRegistration); // 5. Update jumlah pendaftar di event event.registered += 1; res.status(201).json({ success: true, message: `Berhasil mendaftar untuk "${event.title}"`, registration: newRegistration, event: { id: event.id, title: event.title, date: event.date, time: event.time, location: event.location, registered: event.registered, capacity: event.capacity }, summary: { totalRegistrations: registrations.length, eventRegistrations: registrations.filter(r => r.eventId === eventId).length, remainingSlots: event.capacity - event.registered } }); }); // ==================== 4. FORM DATA vs JSON ==================== // 📝 HANDLE FORM SUBMISSION (application/x-www-form-urlencoded) app.post('/api/contact', (req, res) => { console.log("📝 FORM DATA RECEIVED:", req.body); console.log("📝 CONTENT TYPE:", req.headers['content-type']); const { name, email, message, subject = "General Inquiry" } = req.body; // Validasi if (!name || !email || !message) { return res.status(400).json({ success: false, error: "Incomplete Form", message: "Semua field wajib diisi", received: req.body }); } // Simulasikan pengiriman email res.json({ success: true, message: "Pesan berhasil dikirim", data: { from: `${name} <${email}>`, subject, message, receivedAt: new Date().toISOString(), referenceId: `CONTACT-${Date.now()}` } }); }); // 📤 UPLOAD SIMULATION (multipart/form-data) // Note: Untuk upload file sebenarnya butuh middleware seperti multer app.post('/api/upload', (req, res) => { // Untuk form-data, kita perlu middleware khusus // Tapi kita bisa simulasi menerima metadata res.json({ success: true, message: "Endpoint upload (simulasi)", note: "Untuk upload file, gunakan middleware seperti multer", example: { headers: { "Content-Type": "multipart/form-data" }, body: { "file": "[binary data]", "description": "text field" } } }); }); // ==================== 5. HEADERS & VALIDASI ==================== // 🔐 API KEY VALIDATION (Headers) app.post('/api/secure/events', (req, res) => { console.log("🔐 REQUEST HEADERS:", req.headers); const apiKey = req.headers['x-api-key']; if (!apiKey) { return res.status(401).json({ success: false, error: "Unauthorized", message: "API key diperlukan", headerRequired: "x-api-key" }); } // Validasi API key (simulasi) const validApiKeys = ["secret-key-123", "admin-key-456"]; if (!validApiKeys.includes(apiKey)) { return res.status(403).json({ success: false, error: "Forbidden", message: "API key tidak valid" }); } // Jika valid, proses request const { title, category } = req.body; res.json({ success: true, message: "Event created with API key authentication", data: { title, category }, auth: { apiKey: "***" + apiKey.slice(-4), // Hanya tampilkan 4 karakter terakhir timestamp: new Date().toISOString() } }); }); // ==================== 6. COMBINED EXAMPLE ==================== // 🎯 COMPLEX SEARCH WITH ALL DATA TYPES app.get('/api/search/advanced', (req, res) => { console.log("🎯 ADVANCED SEARCH:"); console.log(" Query:", req.query); console.log(" Headers:", req.headers); const { q, // query string category, minPrice, maxPrice, date, sort = "relevance:desc", page = 1, limit = 20 } = req.query; // Juga bisa ambil dari headers const userAgent = req.headers['user-agent']; const acceptLanguage = req.headers['accept-language']; let results = [...events]; // Filtering logic (sama seperti sebelumnya) if (q) { const query = q.toLowerCase(); results = results.filter(e => e.title.toLowerCase().includes(query) || e.speaker.toLowerCase().includes(query) || e.tags.some(tag => tag.toLowerCase().includes(query)) ); } if (category) { results = results.filter(e => e.category === category); } // Pagination const pageNum = parseInt(page); const limitNum = parseInt(limit); const startIndex = (pageNum - 1) * limitNum; const endIndex = pageNum * limitNum; const paginatedResults = results.slice(startIndex, endIndex); res.json({ success: true, message: "Advanced search results", metadata: { searchParams: req.query, clientInfo: { userAgent: userAgent?.substring(0, 50) + '...', language: acceptLanguage }, pagination: { page: pageNum, limit: limitNum, totalResults: results.length, totalPages: Math.ceil(results.length / limitNum) } }, count: paginatedResults.length, data: paginatedResults }); }); // ==================== ERROR HANDLING ==================== // 404 Handler app.use('*', (req, res) => { res.status(404).json({ success: false, error: "Route Not Found", message: `Cannot ${req.method} ${req.originalUrl}`, receivedData: { params: req.params, query: req.query, body: req.body ? "Body exists (hidden for security)" : "No body" } }); }); // Error handler untuk data processing errors app.use((err, req, res, next) => { console.error('🔥 Data Processing Error:', err); // Cek jika error dari JSON parsing if (err instanceof SyntaxError && err.status === 400 && 'body' in err) { return res.status(400).json({ success: false, error: "Invalid JSON", message: "Request body mengandung JSON yang tidak valid", tip: "Pastikan format JSON benar (gunakan double quotes)" }); } res.status(500).json({ success: false, error: "Data Processing Error", message: "Terjadi kesalahan dalam memproses data request" }); }); // ==================== START SERVER ==================== app.listen(PORT, () => { console.log("=".repeat(60)); console.log("📥 DATA PROCESSING PRACTICE SERVER"); console.log("=".repeat(60)); console.log(`🌐 Server: http://localhost:${PORT}`); console.log(`📅 Events: ${events.length} events available`); console.log(`📝 Registrations: ${registrations.length} total`); console.log(""); console.log("🎯 CONTOH REQUEST UNTUK DICOBA:"); console.log(""); console.log("1. PARAMS (Route Parameters):"); console.log(" GET /api/events/1"); console.log(" GET /api/events/2/registrations/confirmed"); console.log(""); console.log("2. QUERY (Query Parameters):"); console.log(" GET /api/events/search?q=javascript"); console.log(" GET /api/events/filter?category=workshop&minPrice=0&maxPrice=200000"); console.log(" GET /api/events?freeOnly=true&sort=date:desc"); console.log(""); console.log("3. BODY (Request Body - gunakan Postman):"); console.log(" POST /api/events"); console.log(" Body: {"); console.log(' "title": "Belajar Node.js",'); console.log(' "category": "workshop",'); console.log(' "speaker": "John Doe",'); console.log(' "date": "2024-04-10"'); console.log(" }"); console.log(""); console.log("4. KOMBINASI:"); console.log(" POST /api/events/1/register"); console.log(" Body: {"); console.log(' "name": "Andi",'); console.log(' "email": "andi@email.com",'); console.log(' "phone": "08123456789"'); console.log(" }"); console.log(""); console.log("5. HEADERS:"); console.log(" POST /api/secure/events"); console.log(' Headers: x-api-key: "secret-key-123"'); console.log("=".repeat(60)); });
3.4 Cara Testing dengan Postman
Test 1: Route Parameters
GET http://localhost:3004/api/events/1 GET http://localhost:3004/api/events/abc ← Error: bukan angka
Test 2: Query Parameters
GET http://localhost:3004/api/events/search?q=javascript GET http://localhost:3004/api/events/filter?category=workshop&minPrice=0&maxPrice=100000&sort=price:asc&page=1&limit=5 GET http://localhost:3004/api/events?freeOnly=true&upcomingOnly=true
Test 3: Request Body (JSON)
POST http://localhost:3004/api/events
Headers: Content-Type: application/json
Body (raw JSON):
{
"title": "Workshop REST API",
"category": "workshop",
"speaker": "Jane Smith",
"date": "2024-04-15",
"capacity": 40,
"price": 200000,
"tags": ["api", "rest", "express"]
}Test 4: Form Data
POST http://localhost:3004/api/contact Headers: Content-Type: application/x-www-form-urlencoded Body (form-data): name: Budi Santoso email: budi@email.com message: Saya ingin bertanya tentang workshop subject: Inquiry
3.5 Validasi Data yang Lebih Advanced
// Middleware untuk validasi const validateEventData = (req, res, next) => { const { title, date, capacity, price } = req.body; const errors = []; // Validasi title if (!title || title.length < 5) { errors.push("Title minimal 5 karakter"); } // Validasi date if (date) { const eventDate = new Date(date); const today = new Date(); if (eventDate < today) { errors.push("Tanggal event tidak boleh di masa lalu"); } } // Validasi capacity if (capacity && (capacity < 1 || capacity > 1000)) { errors.push("Kapasitas harus antara 1-1000"); } // Validasi price if (price && price < 0) { errors.push("Harga tidak boleh negatif"); } if (errors.length > 0) { return res.status(400).json({ success: false, error: "Validation Failed", errors: errors }); } next(); }; // Gunakan middleware app.post('/api/events', validateEventData, (req, res) => { // Handler hanya dijalankan jika validasi lolos });
3.6 Menggunakan req.params, req.query, req.body Bersamaan
// Contoh: Update review untuk product tertentu dengan filter app.put('/api/products/:productId/reviews/:reviewId', (req, res) => { const { productId, reviewId } = req.params; // dari URL const { rating, comment } = req.body; // dari request body const { adminKey } = req.query; // dari query string console.log({ params: { productId, reviewId }, body: { rating, comment }, query: { adminKey } }); // Logika bisnis menggunakan semua data // ... });
3.7 File Upload (Pengenalan)
// Instal multer dulu: npm install multer const multer = require('multer'); const upload = multer({ dest: 'uploads/' }); // Handle file upload app.post('/api/upload/photo', upload.single('photo'), (req, res) => { console.log("File:", req.file); // Info file console.log("Body:", req.body); // Text fields lainnya res.json({ success: true, message: "File uploaded successfully", file: req.file, otherData: req.body }); });
3.8 Best Practices
1. Always Validate Input
// ❌ Tidak aman app.post('/api/users', (req, res) => { const user = req.body; // langsung save ke database ← DANGER! }); // ✅ Validasi dulu app.post('/api/users', (req, res) => { const { name, email, age } = req.body; // Validasi if (!name || name.length < 2) { return res.status(400).json({ error: "Name invalid" }); } if (!email || !email.includes('@')) { return res.status(400).json({ error: "Email invalid" }); } // Baru proses });
2. Sanitize Data
// Hapus whitespace berlebih const cleanName = name.trim(); // Convert ke lowercase untuk konsistensi const cleanEmail = email.toLowerCase().trim(); // Parse angka dengan aman const cleanAge = parseInt(age) || 0;
3. Use Default Values
app.get('/api/products', (req, res) => { const page = parseInt(req.query.page) || 1; const limit = parseInt(req.query.limit) || 10; const sort = req.query.sort || 'name:asc'; // Lebih aman daripada langsung pakai req.query });
3.9 Common Mistakes
Mistake 1: Not Parsing Numbers
// ❌ Wrong const id = req.params.id; // "123" (string) const price = req.body.price; // "150000" (string) // ✅ Correct const id = parseInt(req.params.id); const price = parseFloat(req.body.price);
Mistake 2: Assuming Data Exists
// ❌ Wrong (akan error jika query tidak ada) const search = req.query.search.toLowerCase(); // ✅ Correct const search = req.query.search?.toLowerCase() || '';
Mistake 3: Not Setting Content-Type
// Di Postman, jangan lupa set: // Content-Type: application/json // Kalau tidak, req.body akan undefined
3.10 Latihan Praktikum
Exercise 1: User Profile API
// Buat endpoint untuk: // 1. GET /api/users/:userId/profile // 2. PUT /api/users/:userId/profile // - Body: { name, bio, avatarUrl, socialLinks } // - Query: ?validate=true (untuk validasi ekstra) // 3. GET /api/users/search // - Query: ?name=...&location=...&skills=... (array)
Exercise 2: Order System
// Buat endpoint untuk order makanan: // POST /api/restaurants/:restId/orders // Body: { // items: [{ id, quantity, notes }], // deliveryAddress: { ... }, // paymentMethod: "cash/card", // specialInstructions: "..." // } // Query: ?priority=true (untuk express delivery)
4. Daftar Pustaka
- Express.js Documentation (2024). Request Object. Diakses dari https://expressjs.com/en/api.html#req
- MDN Web Docs (2024). HTTP Request Methods. Diakses dari https://developer.mozilla.org/en-US/docs/Web/HTTP/Methods
- OWASP Security Guidelines (2024). Input Validation Cheat Sheet. Diakses dari https://cheatsheetseries.owasp.org/cheatsheets/Input_Validation_Cheat_Sheet.html
- Postman Learning Center (2024). Sending Requests Tutorial. Diakses dari https://learning.postman.com/docs/sending-requests/requests/
- Perwira Learning Center (2024). Modul Data Processing & Validation. Materi internal PLC.
- JavaScript.info (2024). Fetch API and Forms. Diakses dari https://javascript.info/fetch
- Multer Documentation (2024). File Upload Middleware. Diakses dari https://github.com/expressjs/multer

0 Komentar