# REST API

> 🚀 **Quick Start**: Architectural style untuk building scalable, maintainable web services dengan proper HTTP semantics

## 📋 Table of Contents

* [Overview](#overview)
* [REST Principles](#rest-principles)
* [HTTP Methods](#http-methods)
* [Status Codes](#status-codes)
* [API Design](#api-design)
* [Best Practices](#best-practices)
* [Implementation](#implementation)
* [Testing](#testing)
* [References](#references)

## 🎯 Overview

**REST** (Representational State Transfer) adalah architectural style untuk designing networked applications. REST menggunakan HTTP protocol secara semantik dan menyediakan set of constraints yang menghasilkan properties seperti performance, scalability, dan simplicity. RESTful APIs telah menjadi standard untuk building web services dan mobile applications.

### Key Characteristics

* **Client-Server Architecture**: Separation of concerns antara client dan server
* **Stateless**: Server tidak menyimpan client state antar requests
* **Cacheable**: Responses harus menunjukkan apakah cacheable
* **Uniform Interface**: Consistent interface antar components
* **Layered System**: Hierarchical layers untuk architecture

### REST vs Traditional APIs

| Feature          | REST API     | Traditional API |
| ---------------- | ------------ | --------------- |
| State Management | Stateless    | Stateful        |
| Interface        | HTTP Methods | Custom Methods  |
| Data Format      | JSON/XML     | Varies          |
| Caching          | Built-in     | Manual          |
| Scalability      | High         | Limited         |

## 🏆 REST Principles

### 1. Client-Server

Client dan server memiliki independent evolution. Server menyediakan resources, client mengkonsumsi resources.

```javascript
// Client-Server separation example
// Client (Frontend)
class UserService {
  constructor(apiBaseURL) {
    this.apiBaseURL = apiBaseURL;
  }

  async getUser(id) {
    const response = await fetch(`${this.apiBaseURL}/users/${id}`);
    return response.json();
  }

  async createUser(userData) {
    const response = await fetch(`${this.apiBaseURL}/users`, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json'
      },
      body: JSON.stringify(userData)
    });
    return response.json();
  }
}

// Server (Backend)
app.get('/users/:id', (req, res) => {
  const userId = req.params.id;
  const user = database.getUser(userId);
  res.json(user);
});

app.post('/users', (req, res) => {
  const userData = req.body;
  const newUser = database.createUser(userData);
  res.status(201).json(newUser);
});
```

### 2. Stateless

Server tidak menyimpan client state. Setiap request harus mengandung semua informasi yang diperlukan.

```javascript
// Stateless API with JWT
// Request without state
fetch('/api/users', {
  headers: {
    'Authorization': 'Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...',
    'Content-Type': 'application/json'
  }
});

// Server validates JWT for every request
function authenticateToken(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}
```

### 3. Cacheable

Responses harus menunjukkan apakah cacheable untuk improve performance.

```javascript
// Cacheable response dengan proper headers
app.get('/api/products', (req, res) => {
  const products = database.getProducts();

  res.set({
    'Cache-Control': 'public, max-age=300', // 5 minutes
    'ETag': products.etag,
    'Last-Modified': products.lastModified
  });

  res.json(products);
});

// Conditional request handling
app.get('/api/products', (req, res) => {
  const products = database.getProducts();

  const ifNoneMatch = req.headers['if-none-match'];
  const ifModifiedSince = req.headers['if-modified-since'];

  if (ifNoneMatch === products.etag ||
      new Date(ifModifiedSince) >= products.lastModified) {
    return res.status(304).end(); // Not Modified
  }

  res.json(products);
});
```

### 4. Uniform Interface

Consistent interface dengan standard HTTP methods dan status codes.

```javascript
// CRUD operations dengan proper HTTP methods
// Create
app.post('/api/users', (req, res) => {
  const userData = req.body;
  const newUser = database.createUser(userData);
  res.status(201).json(newUser);
});

// Read
app.get('/api/users/:id', (req, res) => {
  const user = database.getUser(req.params.id);
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  res.json(user);
});

// Update
app.put('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  const updateData = req.body;
  const updatedUser = database.updateUser(userId, updateData);
  res.json(updatedUser);
});

// Delete
app.delete('/api/users/:id', (req, res) => {
  const userId = req.params.id;
  database.deleteUser(userId);
  res.status(204).end();
});
```

### 5. Layered System

Architecture dengan multiple layers yang berinteraksi melalui well-defined interfaces.

```javascript
// Layered architecture example
// Controller Layer
app.get('/api/users', (req, res) => {
  try {
    const users = userService.getAllUsers();
    res.json(users);
  } catch (error) {
    res.status(500).json({ error: error.message });
  }
});

// Service Layer
class UserService {
  constructor(userRepository) {
    this.userRepository = userRepository;
  }

  getAllUsers() {
    const users = this.userRepository.findAll();
    return users.map(user => ({
      id: user.id,
      name: user.name,
      email: user.email,
      createdAt: user.createdAt
    }));
  }

  getUserById(id) {
    const user = this.userRepository.findById(id);
    if (!user) {
      throw new Error('User not found');
    }
    return user;
  }
}

// Repository Layer
class UserRepository {
  async findAll() {
    return await db.collection('users').find({}).toArray();
  }

  async findById(id) {
    return await db.collection('users').findOne({ _id: ObjectId(id) });
  }

  async create(userData) {
    const result = await db.collection('users').insertOne(userData);
    return result.ops[0];
  }
}
```

## 🚀 HTTP Methods

### Standard HTTP Methods

| Method  | Operation      | Idempotent | Safe |
| ------- | -------------- | ---------- | ---- |
| GET     | Read           | ✅          | ✅    |
| POST    | Create         | ❌          | ❌    |
| PUT     | Update/Replace | ✅          | ❌    |
| PATCH   | Partial Update | ❌          | ❌    |
| DELETE  | Delete         | ✅          | ❌    |
| HEAD    | Headers Only   | ✅          | ✅    |
| OPTIONS | Capabilities   | ✅          | ✅    |

### Method Implementation Examples

```javascript
// GET - Retrieve resource
app.get('/api/products', (req, res) => {
  const { page = 1, limit = 10, category } = req.query;

  const products = productService.getProducts({
    page: parseInt(page),
    limit: parseInt(limit),
    category,
    sortBy: req.query.sortBy || 'name',
    order: req.query.order || 'asc'
  });

  res.json({
    data: products,
    pagination: {
      page: parseInt(page),
      limit: parseInt(limit),
      total: products.total
    }
  });
});

// POST - Create new resource
app.post('/api/products', (req, res) => {
  const productData = {
    name: req.body.name,
    description: req.body.description,
    price: req.body.price,
    category: req.body.category,
    inStock: req.body.inStock || true
  };

  const validation = validateProductData(productData);
  if (!validation.isValid) {
    return res.status(400).json({
      error: 'Validation failed',
      details: validation.errors
    });
  }

  const newProduct = productService.createProduct(productData);
  res.status(201).json(newProduct);
});

// PUT - Replace entire resource
app.put('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  const productData = {
    name: req.body.name,
    description: req.body.description,
    price: req.body.price,
    category: req.body.category,
    inStock: req.body.inStock,
    updatedAt: new Date()
  };

  const updatedProduct = productService.replaceProduct(productId, productData);
  if (!updatedProduct) {
    return res.status(404).json({ error: 'Product not found' });
  }

  res.json(updatedProduct);
});

// PATCH - Partial update
app.patch('/api/products/:id', (req, res) => {
  const productId = req.params.id;
  const updates = req.body;

  const existingProduct = productService.getProductById(productId);
  if (!existingProduct) {
    return res.status(404).json({ error: 'Product not found' });
  }

  const updatedProduct = { ...existingProduct, ...updates };
  const savedProduct = productService.updateProduct(productId, updatedProduct);

  res.json(savedProduct);
});

// DELETE - Remove resource
app.delete('/api/products/:id', (req, res) => {
  const productId = req.params.id;

  const deleted = productService.deleteProduct(productId);
  if (!deleted) {
    return res.status(404).json({ error: 'Product not found' });
  }

  res.status(204).end();
});
```

## 📊 Status Codes

### Standard HTTP Status Codes

| Category          | Range   | Description                          |
| ----------------- | ------- | ------------------------------------ |
| **Informational** | 100-199 | Request received, continuing process |
| **Success**       | 200-299 | Request successfully processed       |
| **Redirection**   | 300-399 | Further action required              |
| **Client Error**  | 400-499 | Client error occurred                |
| **Server Error**  | 500-599 | Server error occurred                |

### Common Status Code Implementation

```javascript
// Success responses
const successResponse = (res, data, message = 'Success') => {
  res.status(200).json({
    success: true,
    message,
    data,
    timestamp: new Date().toISOString()
  });
};

const createdResponse = (res, data, message = 'Resource created') => {
  res.status(201).json({
    success: true,
    message,
    data,
    timestamp: new Date().toISOString()
  });
};

// Client error responses
const badRequestResponse = (res, message, errors = []) => {
  res.status(400).json({
    success: false,
    message,
    errors,
    timestamp: new Date().toISOString()
  });
};

const unauthorizedResponse = (res, message = 'Unauthorized') => {
  res.status(401).json({
    success: false,
    message,
    timestamp: new Date().toISOString()
  });
};

const forbiddenResponse = (res, message = 'Forbidden') => {
  res.status(403).json({
    success: false,
    message,
    timestamp: new Date().toISOString()
  });
};

const notFoundResponse = (res, resource) => {
  res.status(404).json({
    success: false,
    message: `${resource} not found`,
    timestamp: new Date().toISOString()
  });
};

// Server error responses
const internalServerErrorResponse = (res, message = 'Internal server error') => {
  res.status(500).json({
    success: false,
    message,
    timestamp: new Date().toISOString()
  });
};

// Usage in route handlers
app.get('/api/users/:id', (req, res) => {
  try {
    const user = userService.getUserById(req.params.id);
    if (!user) {
      return notFoundResponse(res, 'User');
    }
    successResponse(res, user);
  } catch (error) {
    internalServerErrorResponse(res, error.message);
  }
});

app.post('/api/users', (req, res) => {
  const validation = validateUserData(req.body);
  if (!validation.isValid) {
    return badRequestResponse(res, 'Validation failed', validation.errors);
  }

  try {
    const newUser = userService.createUser(req.body);
    createdResponse(res, newUser, 'User created successfully');
  } catch (error) {
    internalServerErrorResponse(res, error.message);
  }
});
```

## 🎨 API Design

### Resource Naming Conventions

```javascript
// Good practices for REST API endpoints
// Use nouns for resources, not verbs
// GET /api/users (not /api/getUsers)
// GET /api/products/{id} (not /api/getProductById)

// Use plural nouns for collections
// GET /api/users
// GET /api/products
// GET /api/orders

// Use specific names for nested resources
// GET /api/users/{userId}/orders
// GET /api/products/{productId}/reviews
// GET /api/orders/{orderId}/items

// Use consistent naming
// kebab-case for URL paths
// PascalCase for model names
// snake_case for database fields
```

### Request/Response Structure

```javascript
// Standard request structure for POST/PATCH
{
  "name": "Product Name",
  "description": "Product description",
  "price": 29.99,
  "category": "electronics",
  "inStock": true,
  "metadata": {
    "source": "web",
    "version": "1.0"
  }
}

// Standard response structure
{
  "success": true,
  "message": "Operation completed successfully",
  "data": {
    "id": "64a1b2c3d4e5f6789",
    "name": "Product Name",
    "description": "Product description",
    "price": 29.99,
    "category": "electronics",
    "inStock": true,
    "createdAt": "2023-07-15T10:30:00Z",
    "updatedAt": "2023-07-15T14:20:00Z"
  },
  "pagination": {
    "page": 1,
    "limit": 10,
    "total": 150,
    "hasNext": true,
    "hasPrev": false
  },
  "timestamp": "2023-07-15T14:25:00Z"
}
```

### Pagination and Filtering

```javascript
// Pagination implementation
app.get('/api/products', (req, res) => {
  const {
    page = 1,
    limit = 10,
    category,
    minPrice,
    maxPrice,
    sortBy = 'createdAt',
    order = 'desc'
  } = req.query;

  const filters = {};
  if (category) filters.category = category;
  if (minPrice) filters.price = { $gte: parseFloat(minPrice) };
  if (maxPrice) filters.price = { ...filters.price, $lte: parseFloat(maxPrice) };

  const options = {
    skip: (page - 1) * limit,
    limit: parseInt(limit),
    sort: { [sortBy]: order === 'desc' ? -1 : 1 }
  };

  try {
    const products = await Product.find(filters, null, options);
    const total = await Product.countDocuments(filters);

    const totalPages = Math.ceil(total / limit);
    const hasNext = page < totalPages;
    const hasPrev = page > 1;

    successResponse(res, {
      products,
      pagination: {
        page: parseInt(page),
        limit: parseInt(limit),
        total,
        totalPages,
        hasNext,
        hasPrev
      }
    });
  } catch (error) {
    internalServerErrorResponse(res, error.message);
  }
});

// Search functionality
app.get('/api/search', (req, res) => {
  const { q: query, type = 'all' } = req.query;

  if (!query) {
    return badRequestResponse(res, 'Search query is required');
  }

  let searchResults = [];

  switch (type) {
    case 'products':
      searchResults = await Product.find(
        {
          $or: [
            { name: { $regex: query, $options: 'i' } },
            { description: { $regex: query, $options: 'i' } },
            { category: { $regex: query, $options: 'i' } }
          ]
        },
        { name: 1, description: 1, category: 1, price: 1 }
      );
      break;

    case 'users':
      searchResults = await User.find(
        {
          $or: [
            { name: { $regex: query, $options: 'i' } },
            { email: { $regex: query, $options: 'i' } }
          ]
        },
        { name: 1, email: 1, profile: 1 }
      );
      break;

    default:
      searchResults = [...(await Product.find(...)), ...(await User.find(...))];
  }

  successResponse(res, { results: searchResults, query, type });
});
```

## 🎯 Best Practices

### API Versioning

```javascript
// URL versioning
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);

// Header versioning
app.use('/api', (req, res, next) => {
  const version = req.headers['api-version'] || 'v1';

  if (version === 'v1') {
    return v1Routes(req, res, next);
  } else if (version === 'v2') {
    return v2Routes(req, res, next);
  } else {
    res.status(400).json({ error: 'Unsupported API version' });
  }
});

// Content negotiation
app.get('/api/products', (req, res) => {
  const acceptHeader = req.headers.accept;

  if (acceptHeader.includes('application/vnd.api+json')) {
    // Return v2 format
    res.json(v2Format(products));
  } else if (acceptHeader.includes('application/json')) {
    // Return v1 format
    res.json(v1Format(products));
  } else {
    res.status(406).json({ error: 'Not acceptable' });
  }
});
```

### Error Handling

```javascript
// Global error handler middleware
function errorHandler(err, req, res, next) {
  const statusCode = err.statusCode || 500;
  const message = err.message || 'Internal server error';

  // Log error
  console.error(`Error ${statusCode}: ${message}`, err.stack);

  // Development vs Production error response
  const errorResponse = {
    success: false,
    message,
    timestamp: new Date().toISOString()
  };

  if (process.env.NODE_ENV === 'development') {
    errorResponse.stack = err.stack;
  }

  res.status(statusCode).json(errorResponse);
}

// Custom error classes
class ValidationError extends Error {
  constructor(message, errors = []) {
    super(message);
    this.name = 'ValidationError';
    this.statusCode = 400;
    this.errors = errors;
  }
}

class NotFoundError extends Error {
  constructor(resource) {
    super(`${resource} not found`);
    this.name = 'NotFoundError';
    this.statusCode = 404;
  }
}

class UnauthorizedError extends Error {
  constructor(message = 'Unauthorized') {
    super(message);
    this.name = 'UnauthorizedError';
    this.statusCode = 401;
  }
}

// Usage in route handlers
app.get('/api/users/:id', async (req, res, next) => {
  try {
    const user = await userService.getUserById(req.params.id);
    if (!user) {
      throw new NotFoundError('User');
    }
    successResponse(res, user);
  } catch (error) {
    next(error);
  }
});
```

### Security Best Practices

```javascript
// Rate limiting
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: {
    error: 'Too many requests from this IP, please try again after 15 minutes'
  },
  standardHeaders: true,
  legacyHeaders: false,
});

app.use('/api/', limiter);

// CORS configuration
const cors = require('cors');

app.use(cors({
  origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
  methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
  allowedHeaders: ['Content-Type', 'Authorization'],
  credentials: true
}));

// Input validation
const { body, validationResult } = require('express-validator');

app.post('/api/users',
  [
    body('name').notEmpty().isLength({ min: 2, max: 50 }),
    body('email').isEmail().normalizeEmail(),
    body('age').isInt({ min: 18, max: 120 }),
    body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)/)
  ],
  (req, res) => {
    const errors = validationResult(req);
    if (!errors.isEmpty()) {
      return badRequestResponse(res, 'Validation failed', errors.array());
    }

    // Process request
    const userData = req.body;
    const newUser = userService.createUser(userData);
    createdResponse(res, newUser, 'User created successfully');
  }
);

// Authentication middleware
const authenticateToken = (req, res, next) => {
  const authHeader = req.headers.authorization;
  const token = authHeader && authHeader.split(' ')[1];

  if (!token) {
    return unauthorizedResponse(res);
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (error) {
    return unauthorizedResponse(res);
  }
};

// Protect routes
app.use('/api/protected', authenticateToken);
```

## 🔧 Implementation

### Express.js Implementation

```javascript
const express = require('express');
const mongoose = require('mongoose');
const cors = require('cors');
const helmet = require('helmet');
const morgan = require('morgan');
const rateLimit = require('express-rate-limit');

const app = express();

// Middleware
app.use(helmet());
app.use(cors());
app.use(morgan('combined'));
app.use(express.json());
app.use(rateLimit({
  windowMs: 15 * 60 * 1000,
  max: 100
}));

// Database connection
mongoose.connect(process.env.MONGODB_URI, {
  useNewUrlParser: true,
  useUnifiedTopology: true
});

// Routes
app.use('/api/v1', require('./routes/v1'));
app.use('/api/v2', require('./routes/v2'));

// Error handling
app.use(errorHandler);

const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

### Node.js Native Implementation

```javascript
const http = require('http');
const url = require('url');

const server = http.createServer((req, res) => {
  const parsedUrl = url.parse(req.url, true);
  const path = parsedUrl.pathname;
  const method = req.method;

  // Route handling
  if (path === '/api/users' && method === 'GET') {
    // Handle GET /api/users
    res.writeHead(200, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify([
      { id: 1, name: 'John Doe', email: 'john@example.com' },
      { id: 2, name: 'Jane Smith', email: 'jane@example.com' }
    ]));
  } else if (path === '/api/users' && method === 'POST') {
    // Handle POST /api/users
    let body = '';
    req.on('data', chunk => {
      body += chunk.toString();
    });

    req.on('end', () => {
      try {
        const userData = JSON.parse(body);
        const newUser = {
          id: Date.now(),
          ...userData,
          createdAt: new Date().toISOString()
        };

        res.writeHead(201, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify(newUser));
      } catch (error) {
        res.writeHead(400, { 'Content-Type': 'application/json' });
        res.end(JSON.stringify({ error: 'Invalid JSON' }));
      }
    });
  } else {
    // Handle 404
    res.writeHead(404, { 'Content-Type': 'application/json' });
    res.end(JSON.stringify({ error: 'Not found' }));
  }
});

const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
  console.log(`Server running on port ${PORT}`);
});
```

## 🧪 Testing

### Unit Testing with Jest

```javascript
const request = require('supertest');
const app = require('../app');
const User = require('../models/User');

describe('User API', () => {
  beforeEach(async () => {
    await User.deleteMany({});
  });

  describe('GET /api/users', () => {
    it('should return empty users array', async () => {
      const response = await request(app)
        .get('/api/users')
        .expect(200)
        .expect('Content-Type', /json/);

      expect(response.body).toEqual([]);
    });

    it('should return users array with data', async () => {
      // Insert test data
      await User.create([
        { name: 'John Doe', email: 'john@example.com' },
        { name: 'Jane Smith', email: 'jane@example.com' }
      ]);

      const response = await request(app)
        .get('/api/users')
        .expect(200)
        .expect('Content-Type', /json/);

      expect(response.body).toHaveLength(2);
      expect(response.body[0].name).toBe('John Doe');
    });
  });

  describe('POST /api/users', () => {
    it('should create new user', async () => {
      const userData = {
        name: 'Test User',
        email: 'test@example.com',
        age: 25
      };

      const response = await request(app)
        .post('/api/users')
        .send(userData)
        .expect(201)
        .expect('Content-Type', /json/);

      expect(response.body.name).toBe('Test User');
      expect(response.body.email).toBe('test@example.com');
    });

    it('should return 400 for invalid data', async () => {
      const invalidData = {
        name: '', // Invalid: empty name
        email: 'invalid-email' // Invalid: invalid email format
      };

      const response = await request(app)
        .post('/api/users')
        .send(invalidData)
        .expect(400)
        .expect('Content-Type', /json/);

      expect(response.body.success).toBe(false);
      expect(response.body.errors).toBeDefined();
    });
  });

  describe('GET /api/users/:id', () => {
    let userId;

    beforeEach(async () => {
      const user = await User.create({
        name: 'Test User',
        email: 'test@example.com'
      });
      userId = user._id.toString();
    });

    it('should return user by ID', async () => {
      const response = await request(app)
        .get(`/api/users/${userId}`)
        .expect(200)
        .expect('Content-Type', /json/);

      expect(response.body.id).toBe(userId);
      expect(response.body.name).toBe('Test User');
    });

    it('should return 404 for non-existent user', async () => {
      const fakeId = '64a1b2c3d4e5f6789';

      const response = await request(app)
        .get(`/api/users/${fakeId}`)
        .expect(404)
        .expect('Content-Type', /json/);

      expect(response.body.success).toBe(false);
      expect(response.body.error).toBe('User not found');
    });
  });
});
```

### Integration Testing

```javascript
const request = require('supertest');
const app = require('../app');

describe('API Integration Tests', () => {
  describe('User Management Flow', () => {
    it('should handle complete user lifecycle', async () => {
      // Create user
      const createResponse = await request(app)
        .post('/api/users')
        .send({
          name: 'Integration User',
          email: 'integration@example.com',
          age: 30
        })
        .expect(201);

      const userId = createResponse.body.id;

      // Read user
      const getResponse = await request(app)
        .get(`/api/users/${userId}`)
        .expect(200);

      expect(getResponse.body.id).toBe(userId);
      expect(getResponse.body.name).toBe('Integration User');

      // Update user
      const updateResponse = await request(app)
        .patch(`/api/users/${userId}`)
        .send({
          age: 31
        })
        .expect(200);

      expect(updateResponse.body.age).toBe(31);

      // Delete user
      await request(app)
        .delete(`/api/users/${userId}`)
        .expect(204);
    });
  });

  describe('API Error Handling', () => {
    it('should handle malformed JSON gracefully', async () => {
      const response = await request(app)
        .post('/api/users')
        .set('Content-Type', 'application/json')
        .send('{"invalid": json}')
        .expect(400)
        .expect('Content-Type', /json/);

      expect(response.body.success).toBe(false);
    });

    it('should handle missing required fields', async () => {
      const response = await request(app)
        .post('/api/users')
        .send({
          name: 'Test User'
          // Missing required field: email
        })
        .expect(400)
        .expect('Content-Type', /json/);

      expect(response.body.errors).toBeDefined();
    });
  });
});
```

## 📚 References

### Official Documentation

* [RFC 7231](https://tools.ietf.org/html/rfc7231/) - HTTP/1.1
* [RFC 3986](https://tools.ietf.org/html/rfc3986/) - HTTP/1.0
* [REST API Design Guide](https://restfulapi.net/) - Comprehensive guide

### Books & Articles

* **"RESTful Web APIs"** by Leonard Richardson
* **"API Design Patterns** by Arnaud Lauret,
* **"Designing APIs"** by Brenda Wallace

### Tools & Frameworks

* **Postman**: API testing tool
* **Insomnia**: API client for REST
* **Swagger/OpenAPI**: API documentation
* **Apigee**: API management platform

## 🔗 Related Technologies

* [GraphQL](https://github.com/mahbubzulkarnain/catatan-seekor-the-series/blob/master/interface/catatan-seekor-graphql/README.md) - Query language for APIs
* [WebSocket](https://github.com/mahbubzulkarnain/catatan-seekor-the-series/blob/master/interface/catatan-seekor-websocket/README.md) - Real-time communication
* [gRPC](https://github.com/mahbubzulkarnain/catatan-seekor-the-series/blob/master/interface/catatan-seekor-grpc/README.md) - High-performance RPC
* [SOAP](https://github.com/mahbubzulkarnain/catatan-seekor-the-series/blob/master/interface/catatan-seekor-soap/README.md) - XML-based protocol

## 📝 Summary

### What We Covered

* ✅ **Overview**: REST principles dan architectural concepts
* ✅ **REST Principles**: Core principles dan constraints
* ✅ **HTTP Methods**: Standard methods dengan proper usage
* ✅ **Status Codes**: Comprehensive status code handling
* ✅ **API Design**: Best practices untuk REST API design
* ✅ **Best Practices**: Security, versioning, error handling
* ✅ **Implementation**: Multiple framework implementations
* ✅ **Testing**: Unit dan integration testing approaches

### Next Steps

1. **Practice**: Build REST APIs dengan different frameworks
2. **Explore**: GraphQL sebagai alternative to REST
3. **Master**: Advanced patterns seperti HATEOAS
4. **Specialize**: Focus pada specific domains

### Key Takeaways

* **Stateless**: Stateless design enables scalability
* **HTTP Semantics**: Proper use of HTTP methods and status codes
* **Uniform Interface**: Consistent API design across resources
* **Cacheable**: Proper caching strategies for performance

***

## 🤝 Need Help?

Jika Anda mengalami kesulitan atau memiliki pertanyaan:

* **Stack Overflow**: [REST Tag](https://stackoverflow.com/questions/tagged/rest)
* **Reddit**: [r/restapi](https://reddit.com/r/restapi)
* **Email**: \[<mahbub.zulkarnain@example.com>]

***

**💡 Pro Tip**: Design APIs dengan resource-oriented approach. Use proper HTTP methods for CRUD operations. Implement comprehensive error handling. Version your APIs for backward compatibility.

**⭐ Jika dokumentasi ini bermanfaat, jangan lupa berikan star di repository ini!**
