Membuat Mini Project: REST API Sederhana - Perwira Learning Center


1. Latar Belakang

    Sebagai penutup pembelajaran Express.js di Perwira Learning Center, saya akan membuat REST API CRUD sederhana tanpa menggunakan database (hanya dengan data JSON di memory). Project ini bertujuan untuk:

  • Melatih pemahaman alur data: Bagaimana data mengalir dari client → server → response
  • Mengasah logika algoritma: Implementasi operasi CRUD yang efisien
  • Memperkuat struktur Express.js: Penerapan routing, middleware, controllers secara praktis

Ini ibarat simulasi perang akhir - semua ilmu yang dipelajari dari pertemuan 1-9 diaplikasikan dalam satu project nyata!

2. Alat dan Bahan

a. Perangkat Lunak

  • Express.js - Framework utama
  • Node.js & npm - Runtime dan package manager
  • VS Code - Code editor
  • Postman - API testing tool
  • Git - Version control system

b. Perangkat Keras

  • Laptop/PC standar

c. Dependencies

bash
npm install express dotenv cors
npm install -D nodemon

3. Pembahasan

3.1 Project Overview: Student Management API

Kita akan membuat API Manajemen Mahasiswa dengan fitur:

text
📊 FITUR UTAMA:
1. CREATE  - Menambah data mahasiswa baru
2. READ    - Melihat semua/data spesifik mahasiswa
3. UPDATE  - Memperbarui data mahasiswa
4. DELETE  - Menghapus data mahasiswa
5. FILTER  - Filter berdasarkan jurusan/angkatan
6. SEARCH  - Cari berdasarkan nama
7. PAGINATION - Data per halaman

3.2 Struktur Project Sederhana

Untuk project mini ini, kita gunakan struktur yang sederhana namun terstruktur:

text
student-api/
├── src/
│   ├── server.js          # Entry point
│   ├── app.js             # Express configuration
│   ├── routes/            # API routes
│   │   └── students.js    # Student endpoints
│   ├── controllers/       # Business logic
│   │   └── studentController.js
│   ├── models/           # Data models
│   │   └── Student.js
│   └── data/             # Mock data
│       └── students.json
├── package.json
├── .env
├── .gitignore
└── README.md

3.3 Implementasi Lengkap Project

Step 1: Setup Project

bash
# 1. Buat project folder
mkdir student-api && cd student-api

# 2. Inisialisasi project
npm init -y

# 3. Install dependencies
npm install express dotenv cors
npm install -D nodemon

# 4. Buat struktur folder
mkdir -p src/{routes,controllers,models,data}

Step 2: File Konfigurasi

package.json

json
{
  "name": "student-api",
  "version": "1.0.0",
  "description": "Student Management REST API",
  "main": "src/server.js",
  "scripts": {
    "start": "node src/server.js",
    "dev": "nodemon src/server.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": ["express", "rest-api", "crud", "students"],
  "author": "Perwira Learning Center",
  "license": "MIT",
  "dependencies": {
    "cors": "^2.8.5",
    "dotenv": "^16.3.1",
    "express": "^4.18.2"
  },
  "devDependencies": {
    "nodemon": "^3.0.1"
  }
}

.env

env
PORT=3007
NODE_ENV=development
API_VERSION=v1

.gitignore

gitignore
node_modules/
.env
.DS_Store
*.log

Step 3: Data Model

src/data/students.json

json
[
  {
    "id": 1,
    "nim": "20230001",
    "name": "Ahmad Fauzi",
    "email": "ahmad@example.com",
    "major": "Informatika",
    "year": 2023,
    "gpa": 3.75,
    "createdAt": "2024-01-15T10:00:00.000Z",
    "updatedAt": "2024-01-15T10:00:00.000Z"
  },
  {
    "id": 2,
    "nim": "20230002",
    "name": "Siti Aminah",
    "email": "siti@example.com",
    "major": "Sistem Informasi",
    "year": 2023,
    "gpa": 3.90,
    "createdAt": "2024-01-14T09:00:00.000Z",
    "updatedAt": "2024-01-14T09:00:00.000Z"
  },
  {
    "id": 3,
    "nim": "20220001",
    "name": "Budi Santoso",
    "email": "budi@example.com",
    "major": "Teknik Elektro",
    "year": 2022,
    "gpa": 3.50,
    "createdAt": "2024-01-13T08:00:00.000Z",
    "updatedAt": "2024-01-13T08:00:00.000Z"
  }
]

src/models/Student.js

javascript
const fs = require('fs').promises;
const path = require('path');

const dataPath = path.join(__dirname, '../data/students.json');

class Student {
  // Get all students
  static async getAll() {
    try {
      const data = await fs.readFile(dataPath, 'utf8');
      return JSON.parse(data);
    } catch (error) {
      throw new Error('Failed to read students data');
    }
  }

  // Get student by ID
  static async getById(id) {
    const students = await this.getAll();
    return students.find(student => student.id === parseInt(id));
  }

  // Create new student
  static async create(studentData) {
    const students = await this.getAll();
    const newId = students.length > 0 ? Math.max(...students.map(s => s.id)) + 1 : 1;
    
    const newStudent = {
      id: newId,
      ...studentData,
      createdAt: new Date().toISOString(),
      updatedAt: new Date().toISOString()
    };
    
    students.push(newStudent);
    await this.saveAll(students);
    return newStudent;
  }

  // Update student
  static async update(id, updateData) {
    const students = await this.getAll();
    const index = students.findIndex(s => s.id === parseInt(id));
    
    if (index === -1) return null;
    
    students[index] = {
      ...students[index],
      ...updateData,
      updatedAt: new Date().toISOString()
    };
    
    await this.saveAll(students);
    return students[index];
  }

  // Delete student
  static async delete(id) {
    const students = await this.getAll();
    const index = students.findIndex(s => s.id === parseInt(id));
    
    if (index === -1) return false;
    
    students.splice(index, 1);
    await this.saveAll(students);
    return true;
  }

  // Filter by major
  static async filterByMajor(major) {
    const students = await this.getAll();
    return students.filter(student => 
      student.major.toLowerCase() === major.toLowerCase()
    );
  }

  // Search by name
  static async searchByName(keyword) {
    const students = await this.getAll();
    const keywordLower = keyword.toLowerCase();
    return students.filter(student =>
      student.name.toLowerCase().includes(keywordLower)
    );
  }

  // Get by year
  static async getByYear(year) {
    const students = await this.getAll();
    return students.filter(student => student.year === parseInt(year));
  }

  // Save all students to file
  static async saveAll(students) {
    try {
      await fs.writeFile(dataPath, JSON.stringify(students, null, 2), 'utf8');
    } catch (error) {
      throw new Error('Failed to save students data');
    }
  }
}

module.exports = Student;

Step 4: Controller

src/controllers/studentController.js

javascript
const Student = require('../models/Student');

// Response formatter
const apiResponse = (success, message, data = null, pagination = null) => {
  const response = {
    success,
    message,
    timestamp: new Date().toISOString()
  };
  
  if (data !== null) response.data = data;
  if (pagination !== null) response.pagination = pagination;
  
  return response;
};

// Validation helper
const validateStudent = (data, isUpdate = false) => {
  const errors = [];
  
  if (!isUpdate || data.name !== undefined) {
    if (!data.name || data.name.trim() === '') {
      errors.push('Nama tidak boleh kosong');
    }
    if (data.name && data.name.length > 100) {
      errors.push('Nama maksimal 100 karakter');
    }
  }
  
  if (!isUpdate || data.nim !== undefined) {
    if (!data.nim || data.nim.trim() === '') {
      errors.push('NIM tidak boleh kosong');
    }
  }
  
  if (!isUpdate || data.email !== undefined) {
    const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
    if (data.email && !emailRegex.test(data.email)) {
      errors.push('Email tidak valid');
    }
  }
  
  if (data.gpa !== undefined && (data.gpa < 0 || data.gpa > 4)) {
    errors.push('IPK harus antara 0-4');
  }
  
  return errors;
};

// ==================== CONTROLLER METHODS ====================

// Get all students with pagination and filters
exports.getAllStudents = async (req, res) => {
  try {
    const { 
      page = 1, 
      limit = 10, 
      major, 
      year, 
      search,
      sortBy = 'name',
      order = 'asc'
    } = req.query;
    
    let students = await Student.getAll();
    
    // Apply filters
    if (major) {
      students = students.filter(s => 
        s.major.toLowerCase().includes(major.toLowerCase())
      );
    }
    
    if (year) {
      students = students.filter(s => s.year === parseInt(year));
    }
    
    if (search) {
      const searchLower = search.toLowerCase();
      students = students.filter(s =>
        s.name.toLowerCase().includes(searchLower) ||
        s.nim.toLowerCase().includes(searchLower)
      );
    }
    
    // Apply sorting
    students.sort((a, b) => {
      const aValue = a[sortBy] || '';
      const bValue = b[sortBy] || '';
      
      if (order === 'desc') {
        return bValue.toString().localeCompare(aValue.toString());
      }
      return aValue.toString().localeCompare(bValue.toString());
    });
    
    // Apply pagination
    const pageNum = parseInt(page);
    const limitNum = parseInt(limit);
    const startIndex = (pageNum - 1) * limitNum;
    const endIndex = pageNum * limitNum;
    
    const paginatedStudents = students.slice(startIndex, endIndex);
    
    const pagination = {
      page: pageNum,
      limit: limitNum,
      totalItems: students.length,
      totalPages: Math.ceil(students.length / limitNum),
      hasNextPage: endIndex < students.length,
      hasPrevPage: startIndex > 0
    };
    
    res.json(apiResponse(true, 'Data mahasiswa berhasil diambil', paginatedStudents, pagination));
  } catch (error) {
    console.error('Error in getAllStudents:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

// Get student by ID
exports.getStudentById = async (req, res) => {
  try {
    const { id } = req.params;
    const student = await Student.getById(id);
    
    if (!student) {
      return res.status(404).json(apiResponse(false, 'Mahasiswa tidak ditemukan'));
    }
    
    res.json(apiResponse(true, 'Data mahasiswa berhasil diambil', student));
  } catch (error) {
    console.error('Error in getStudentById:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

// Create new student
exports.createStudent = async (req, res) => {
  try {
    const studentData = req.body;
    
    // Validation
    const errors = validateStudent(studentData);
    if (errors.length > 0) {
      return res.status(400).json(apiResponse(false, 'Validasi gagal', { errors }));
    }
    
    // Check if NIM already exists
    const students = await Student.getAll();
    const existingStudent = students.find(s => s.nim === studentData.nim);
    if (existingStudent) {
      return res.status(409).json(apiResponse(false, 'NIM sudah terdaftar'));
    }
    
    const newStudent = await Student.create(studentData);
    res.status(201).json(apiResponse(true, 'Mahasiswa berhasil ditambahkan', newStudent));
  } catch (error) {
    console.error('Error in createStudent:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

// Update student
exports.updateStudent = async (req, res) => {
  try {
    const { id } = req.params;
    const updateData = req.body;
    
    // Check if student exists
    const existingStudent = await Student.getById(id);
    if (!existingStudent) {
      return res.status(404).json(apiResponse(false, 'Mahasiswa tidak ditemukan'));
    }
    
    // Validation
    const errors = validateStudent(updateData, true);
    if (errors.length > 0) {
      return res.status(400).json(apiResponse(false, 'Validasi gagal', { errors }));
    }
    
    // Check if NIM already exists (if NIM is being updated)
    if (updateData.nim && updateData.nim !== existingStudent.nim) {
      const students = await Student.getAll();
      const nimExists = students.find(s => s.nim === updateData.nim && s.id !== parseInt(id));
      if (nimExists) {
        return res.status(409).json(apiResponse(false, 'NIM sudah digunakan oleh mahasiswa lain'));
      }
    }
    
    const updatedStudent = await Student.update(id, updateData);
    res.json(apiResponse(true, 'Data mahasiswa berhasil diperbarui', updatedStudent));
  } catch (error) {
    console.error('Error in updateStudent:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

// Delete student
exports.deleteStudent = async (req, res) => {
  try {
    const { id } = req.params;
    
    const studentExists = await Student.getById(id);
    if (!studentExists) {
      return res.status(404).json(apiResponse(false, 'Mahasiswa tidak ditemukan'));
    }
    
    const deleted = await Student.delete(id);
    if (!deleted) {
      return res.status(500).json(apiResponse(false, 'Gagal menghapus mahasiswa'));
    }
    
    res.json(apiResponse(true, 'Mahasiswa berhasil dihapus'));
  } catch (error) {
    console.error('Error in deleteStudent:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

// Get statistics
exports.getStatistics = async (req, res) => {
  try {
    const students = await Student.getAll();
    
    const stats = {
      totalStudents: students.length,
      byMajor: {},
      byYear: {},
      averageGPA: 0,
      maxGPA: 0,
      minGPA: 4
    };
    
    if (students.length > 0) {
      let totalGPA = 0;
      
      students.forEach(student => {
        // Count by major
        stats.byMajor[student.major] = (stats.byMajor[student.major] || 0) + 1;
        
        // Count by year
        stats.byYear[student.year] = (stats.byYear[student.year] || 0) + 1;
        
        // GPA calculations
        totalGPA += student.gpa;
        if (student.gpa > stats.maxGPA) stats.maxGPA = student.gpa;
        if (student.gpa < stats.minGPA) stats.minGPA = student.gpa;
      });
      
      stats.averageGPA = totalGPA / students.length;
    } else {
      stats.minGPA = 0;
    }
    
    res.json(apiResponse(true, 'Statistik berhasil diambil', stats));
  } catch (error) {
    console.error('Error in getStatistics:', error);
    res.status(500).json(apiResponse(false, 'Terjadi kesalahan server'));
  }
};

Step 5: Routes

src/routes/students.js

javascript
const express = require('express');
const router = express.Router();
const {
  getAllStudents,
  getStudentById,
  createStudent,
  updateStudent,
  deleteStudent,
  getStatistics
} = require('../controllers/studentController');

// GET /api/v1/students - Get all students (with filters & pagination)
router.get('/', getAllStudents);

// GET /api/v1/students/statistics - Get statistics
router.get('/statistics', getStatistics);

// GET /api/v1/students/:id - Get student by ID
router.get('/:id', getStudentById);

// POST /api/v1/students - Create new student
router.post('/', createStudent);

// PUT /api/v1/students/:id - Update student (full update)
router.put('/:id', updateStudent);

// PATCH /api/v1/students/:id - Update student (partial update)
router.patch('/:id', updateStudent);

// DELETE /api/v1/students/:id - Delete student
router.delete('/:id', deleteStudent);

module.exports = router;

Step 6: Express App Configuration

src/app.js

javascript
const express = require('express');
const cors = require('cors');
require('dotenv').config();

const studentRoutes = require('./routes/students');

const app = express();

// ==================== MIDDLEWARE ====================

// CORS
app.use(cors());

// Body Parser
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

// Request Logger (Custom Middleware)
app.use((req, res, next) => {
  const timestamp = new Date().toISOString();
  console.log(`[${timestamp}] ${req.method} ${req.url}`);
  next();
});

// ==================== ROUTES ====================

const API_PREFIX = `/api/${process.env.API_VERSION || 'v1'}`;

// Health Check
app.get(`${API_PREFIX}/health`, (req, res) => {
  res.json({
    status: 'healthy',
    service: 'Student Management API',
    version: '1.0.0',
    timestamp: new Date().toISOString()
  });
});

// API Documentation
app.get(`${API_PREFIX}/`, (req, res) => {
  res.json({
    message: 'Welcome to Student Management API',
    version: '1.0.0',
    endpoints: {
      students: `${API_PREFIX}/students`,
      health: `${API_PREFIX}/health`
    },
    documentation: {
      getAll: 'GET /students?page=1&limit=10&major=Informatika&year=2023&search=nama',
      getOne: 'GET /students/:id',
      create: 'POST /students',
      update: 'PUT/PATCH /students/:id',
      delete: 'DELETE /students/:id',
      stats: 'GET /students/statistics'
    }
  });
});

// Student Routes
app.use(`${API_PREFIX}/students`, studentRoutes);

// ==================== ERROR HANDLING ====================

// 404 Handler
app.use((req, res) => {
  res.status(404).json({
    success: false,
    message: `Route not found: ${req.method} ${req.url}`,
    timestamp: new Date().toISOString()
  });
});

// Global Error Handler
app.use((err, req, res, next) => {
  console.error('Global Error:', err);
  
  res.status(500).json({
    success: false,
    message: 'Internal Server Error',
    timestamp: new Date().toISOString(),
    ...(process.env.NODE_ENV === 'development' && { error: err.message })
  });
});

module.exports = app;

Step 7: Server Entry Point

src/server.js

javascript
const app = require('./app');

const PORT = process.env.PORT || 3000;

const server = app.listen(PORT, () => {
  console.log(`
╔═══════════════════════════════════════════════════════════════╗
║                🚀 Student Management API Started               ║
╠═══════════════════════════════════════════════════════════════╣
║ Service: Student Management API                               ║
║ Version: 1.0.0                                                ║
║ Environment: ${process.env.NODE_ENV || 'development'}         ║
║ Port: ${PORT}                                                 ║
║ API URL: http://localhost:${PORT}/api/v1                      ║
║ Health Check: http://localhost:${PORT}/api/v1/health          ║
║ API Docs: http://localhost:${PORT}/api/v1                     ║
╚═══════════════════════════════════════════════════════════════╝
  `);
});

// Graceful shutdown
process.on('SIGTERM', () => {
  console.log('SIGTERM received. Shutting down gracefully...');
  server.close(() => {
    console.log('Process terminated');
  });
});

process.on('SIGINT', () => {
  console.log('SIGINT received. Shutting down gracefully...');
  server.close(() => {
    console.log('Process terminated');
  });
});

3.4 Testing API dengan Postman

📋 ENDPOINTS YANG TERSEDIA:

1. GET All Students (GET /api/v1/students)

http
GET http://localhost:3007/api/v1/students
GET http://localhost:3007/api/v1/students?page=1&limit=5
GET http://localhost:3007/api/v1/students?major=Informatika
GET http://localhost:3007/api/v1/students?year=2023
GET http://localhost:3007/api/v1/students?search=Ahmad
GET http://localhost:3007/api/v1/students?sortBy=gpa&order=desc

2. GET Student by ID (GET /api/v1/students/:id)

http
GET http://localhost:3007/api/v1/students/1

3. CREATE New Student (POST /api/v1/students)

http
POST http://localhost:3007/api/v1/students
Content-Type: application/json

{
  "nim": "20230003",
  "name": "Rina Wijaya",
  "email": "rina@example.com",
  "major": "Teknik Industri",
  "year": 2023,
  "gpa": 3.85
}

4. UPDATE Student (PUT /api/v1/students/:id)

http
PUT http://localhost:3007/api/v1/students/1
Content-Type: application/json

{
  "name": "Ahmad Fauzi Updated",
  "gpa": 3.80
}

5. DELETE Student (DELETE /api/v1/students/:id)

http
DELETE http://localhost:3007/api/v1/students/1

6. GET Statistics (GET /api/v1/students/statistics)

http
GET http://localhost:3007/api/v1/students/statistics

7. Health Check (GET /api/v1/health)

http
GET http://localhost:3007/api/v1/health

🧪 Testing dengan cURL:

bash
# Test GET all
curl -X GET "http://localhost:3007/api/v1/students"

# Test POST create
curl -X POST http://localhost:3007/api/v1/students \
  -H "Content-Type: application/json" \
  -d '{"nim":"20230004","name":"Test Student","major":"Informatika","year":2024,"gpa":3.5}'

# Test PUT update
curl -X PUT http://localhost:3007/api/v1/students/1 \
  -H "Content-Type: application/json" \
  -d '{"gpa":3.9}'

# Test DELETE
curl -X DELETE http://localhost:3007/api/v1/students/1

3.5 Alur Data dalam Project Ini

Mari kita trace alur lengkap untuk request "Create Student":

text
📊 ALUR DATA: CREATE STUDENT

1. CLIENT mengirim request:
   POST /api/v1/students
   Body: JSON dengan data mahasiswa baru

2. ROUTES (students.js):
   - Cocokkan route POST '/'
   - Panggil controller: createStudent()

3. MIDDLEWARE (app.js):
   - CORS check
   - Parse JSON body
   - Log request
   - Error handling

4. CONTROLLER (studentController.js):
   - Validasi input data
   - Cek duplikasi NIM
   - Panggil model untuk create

5. MODEL (Student.js):
   - Generate ID baru
   - Tambah timestamp
   - Simpan ke file JSON
   - Return data yang baru dibuat

6. RESPONSE ke CLIENT:
   Status: 201 Created
   Body: {
     "success": true,
     "message": "Mahasiswa berhasil ditambahkan",
     "data": { ... },
     "timestamp": "..."
   }

3.6 Latihan Praktikum

🎯 Exercise 1: Tambahkan Fitur Export Data

Buat endpoint untuk export data mahasiswa ke format CSV:

javascript
// routes/students.js - Tambah route baru
router.get('/export/csv', exportToCSV);

// controller/studentController.js
exports.exportToCSV = async (req, res) => {
  try {
    const students = await Student.getAll();
    
    // Convert to CSV format
    const csvHeader = 'ID,NIM,Name,Email,Major,Year,GPA\n';
    const csvRows = students.map(s => 
      `${s.id},${s.nim},${s.name},${s.email},${s.major},${s.year},${s.gpa}`
    ).join('\n');
    
    const csvContent = csvHeader + csvRows;
    
    // Set headers untuk download
    res.setHeader('Content-Type', 'text/csv');
    res.setHeader('Content-Disposition', 'attachment; filename=students.csv');
    
    res.send(csvContent);
  } catch (error) {
    res.status(500).json(apiResponse(false, 'Export failed'));
  }
};

🎯 Exercise 2: Implementasi Rate Limiting

Tambahkan rate limiting untuk mencegah abuse:

javascript
// app.js
const rateLimit = require('express-rate-limit');

const limiter = rateLimit({
  windowMs: 15 * 60 * 1000, // 15 minutes
  max: 100, // Limit each IP to 100 requests per windowMs
  message: {
    success: false,
    message: 'Too many requests, please try again later.',
    timestamp: new Date().toISOString()
  }
});

// Apply to all routes
app.use(limiter);

🎯 Exercise 3: Tambahkan Database (Optional)

Replace file JSON dengan MongoDB:

javascript
// models/Student.js (MongoDB version)
const mongoose = require('mongoose');

const studentSchema = new mongoose.Schema({
  nim: { type: String, required: true, unique: true },
  name: { type: String, required: true },
  email: { type: String, required: true },
  major: { type: String, required: true },
  year: { type: Number, required: true },
  gpa: { type: Number, min: 0, max: 4 },
  createdAt: { type: Date, default: Date.now },
  updatedAt: { type: Date, default: Date.now }
});

module.exports = mongoose.model('Student', studentSchema);

3.7 Best Practices yang Diimplementasikan

1. Separation of Concerns

javascript
// Routes: Hanya handle routing
// Controllers: Handle business logic
// Models: Handle data operations
// Utils: Helper functions

2. Consistent Response Format

javascript
// Semua response format sama
{
  "success": boolean,
  "message": string,
  "data": any,        // optional
  "pagination": any,  // optional
  "timestamp": string // selalu ada
}

3. Proper Error Handling

javascript
// HTTP Status Codes yang tepat:
// 200: OK
// 201: Created
// 400: Bad Request (validation error)
// 404: Not Found
// 409: Conflict (duplicate data)
// 500: Internal Server Error

4. Input Validation

javascript
// Validasi di controller sebelum proses
const errors = validateStudent(data);
if (errors.length > 0) {
  return res.status(400).json(...);
}

5. Security Considerations

javascript
// CORS enabled
// Body parsing
// No sensitive data in response
// Error messages tidak expose internal details

3.8 Troubleshooting Common Issues

Issue 1: Cannot GET /api/v1/students

Solution:

bash
# 1. Cek server running
curl http://localhost:3007/api/v1/health

# 2. Cek routes configuration
# Pastikan di app.js: app.use('/api/v1/students', studentRoutes)

# 3. Restart server
npm run dev

Issue 2: Body Parser tidak bekerja

Solution:

javascript
// Pastikan middleware ini ada sebelum routes:
app.use(express.json());
app.use(express.urlencoded({ extended: true }));

Issue 3: CORS Error di Browser

Solution:

javascript
// Pastikan CORS middleware di enable
app.use(cors());

// Atau konfigurasi lebih spesifik:
app.use(cors({
  origin: 'http://localhost:3000',
  methods: ['GET', 'POST', 'PUT', 'DELETE']
}));

Issue 4: File JSON tidak terbaca

Solution:

javascript
// Pastikan path benar
const dataPath = path.join(__dirname, '../data/students.json');

// Pastikan file ada dan format JSON valid
// Gunakan try-catch untuk error handling
try {
  const data = await fs.readFile(dataPath, 'utf8');
  return JSON.parse(data);
} catch (error) {
  throw new Error('Failed to read data file');
}

3.9 Project Extension Ideas

Tingkatkan project dengan menambah:

  1. Authentication & Authorization 
    • JWT tokens
    • User roles (admin, user)
    • Protected routes
  2. File Upload 
    • Upload foto profil mahasiswa
    • Upload transkrip nilai
    • Validasi file type & size
  3. Email Notification 
    • Kirim email konfirmasi
    • Notifikasi update data
    • Newsletter
  4. Real-time Features 
    • WebSocket untuk real-time updates
    • Live notification system
  5. Testing Suite 
    • Unit tests dengan Jest
    • Integration tests
    • API endpoint testing
  6. Deployment 
    • Deploy ke Heroku/AWS
    • CI/CD pipeline
    • Environment configuration

4. Daftar Pustaka

  1. Express.js Documentation (2024). Express Application Structure. Diakses dari: https://expressjs.com/en/guide/routing.html
  2. Node.js Best Practices (2024). Project Structure Guidelines. Diakses dari: https://github.com/goldbergyoni/nodebestpractices
  3. REST API Tutorial (2024). Best Practices for REST API Design. Diakses dari: https://restfulapi.net/
  4. MDN Web Docs (2024). HTTP Status Codes. Diakses dari: https://developer.mozilla.org/en-US/docs/Web/HTTP/Status
  5. Postman Learning Center (2024). API Testing Best Practices. Diakses dari: https://learning.postman.com/docs/getting-started/introduction/
  6. Clean Code JavaScript (2024). Coding Standards. Diakses dari: https://github.com/ryanmcdermott/clean-code-javascript
  7. JavaScript.info (2024). Modern JavaScript Tutorial. Diakses dari: https://javascript.info/


Posting Komentar

0 Komentar