> ## Documentation Index
> Fetch the complete documentation index at: https://resources.devweekends.com/llms.txt
> Use this file to discover all available pages before exploring further.

# 09. Building a REST API

> Learn how to build a complete RESTful API with Express.js.

# Building a REST API

APIs (Application Programming Interfaces) are the backbone of modern web development. They allow different applications to communicate with each other--whether it's your frontend talking to your backend, or third-party services integrating with your app.

Think of a REST API like a restaurant menu. The menu (API documentation) tells you what dishes (resources) are available and how to order them (HTTP methods). You do not need to know how the kitchen works internally--you just place an order (request) and receive your dish (response). The waiter (HTTP) carries messages back and forth using a standardized process (the REST constraints).

## What is REST?

**REST** (Representational State Transfer) is an architectural style for designing networked applications. It's not a protocol or standard, but a set of constraints that, when followed, create scalable and maintainable APIs.

### Core Principles of REST

| Principle             | Description                                                                                                             |
| --------------------- | ----------------------------------------------------------------------------------------------------------------------- |
| **Stateless**         | Each request contains all the information needed to process it. The server doesn't store client state between requests. |
| **Client-Server**     | The client and server are separate, allowing them to evolve independently.                                              |
| **Uniform Interface** | Resources are accessed using standard HTTP methods (GET, POST, PUT, DELETE).                                            |
| **Resource-Based**    | Everything is a resource (users, products, orders) identified by URLs.                                                  |

### HTTP Methods and Their Meanings

| Method   | Purpose                        | Example                       |
| -------- | ------------------------------ | ----------------------------- |
| `GET`    | Retrieve data                  | Get all users, get user by ID |
| `POST`   | Create new data                | Create a new user             |
| `PUT`    | Update existing data (replace) | Update a user's information   |
| `PATCH`  | Partial update                 | Update only user's email      |
| `DELETE` | Remove data                    | Delete a user                 |

### Why REST?

* **Universal**: Works with any programming language that can make HTTP requests
* **Scalable**: Statelessness allows easy horizontal scaling
* **Cacheable**: GET requests can be cached for better performance
* **Simple**: Uses familiar HTTP concepts developers already know

In this chapter, we will build a simple REST API to manage a list of items, demonstrating these principles in action.

## Setup

Create a new file `server.js` and install express if you haven't.

```javascript theme={null}
const express = require('express');
const app = express();

app.use(express.json()); // Middleware to parse JSON bodies

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

// Mock Database -- In a real application, this would be a database like
// PostgreSQL or MongoDB. Using an in-memory array means all data is lost
// when the server restarts. This is fine for learning, but never for production.
let items = [
  { id: 1, name: 'Item One' },
  { id: 2, name: 'Item Two' }
];

app.listen(PORT, () => console.log(`Server started on port ${PORT}`));
```

## GET: Read Data

```javascript theme={null}
// Get all items
app.get('/api/items', (req, res) => {
  res.json(items);
});

// Get single item
app.get('/api/items/:id', (req, res) => {
  const found = items.some(item => item.id === parseInt(req.params.id));

  if (found) {
    res.json(items.filter(item => item.id === parseInt(req.params.id)));
  } else {
    res.status(404).json({ msg: `No item with the id of ${req.params.id}` });
  }
});
```

## POST: Create Data

```javascript theme={null}
app.post('/api/items', (req, res) => {
  const newItem = {
    id: items.length + 1, // Simple ID generation -- WARNING: this breaks after deletions!
                           // If you delete item 2 from a 3-item list and add a new one,
                           // you get id=3 which collides with the existing item 3.
                           // In production, use UUIDs or database auto-increment.
    name: req.body.name
  };

  if (!newItem.name) {
    return res.status(400).json({ msg: 'Please include a name' });
  }

  items.push(newItem);
  res.status(201).json(newItem); // Return the created item with 201 status
});
```

## PUT: Update Data

```javascript theme={null}
app.put('/api/items/:id', (req, res) => {
  const found = items.some(item => item.id === parseInt(req.params.id));

  if (found) {
    const updItem = req.body;
    items.forEach(item => {
      if (item.id === parseInt(req.params.id)) {
        item.name = updItem.name ? updItem.name : item.name;
        res.json({ msg: 'Item updated', item });
      }
    });
  } else {
    res.status(404).json({ msg: `No item with the id of ${req.params.id}` });
  }
});
```

## DELETE: Remove Data

```javascript theme={null}
app.delete('/api/items/:id', (req, res) => {
  const found = items.some(item => item.id === parseInt(req.params.id));

  if (found) {
    items = items.filter(item => item.id !== parseInt(req.params.id));
    res.json({ msg: 'Item deleted', items });
  } else {
    res.status(404).json({ msg: `No item with the id of ${req.params.id}` });
  }
});
```

## Testing with Postman / Insomnia

Since we don't have a frontend yet, use tools like **Postman** or **Insomnia** to test your API.

1. **GET** `http://localhost:5000/api/items` -> Should return list.
2. **POST** `http://localhost:5000/api/items` with JSON body `{"name": "New Item"}` -> Should add item.
3. **PUT** `http://localhost:5000/api/items/1` with JSON body `{"name": "Updated Item"}` -> Should update item 1.
4. **DELETE** `http://localhost:5000/api/items/1` -> Should delete item 1.

## Summary

* **REST APIs** use standard HTTP methods for CRUD operations
* **GET** for retrieving data (should never modify server state)
* **POST** for creating data (return 201, not 200)
* **PUT** for full replacement, **PATCH** for partial update
* **DELETE** for removing data (return 204 No Content for success)
* Always validate input and handle errors--never trust data from the client
* Use proper HTTP status codes: they are part of the API contract, not decoration

## Request Validation with Joi

Never trust client input! Validate all requests:

```bash theme={null}
npm install joi
```

```javascript theme={null}
const Joi = require('joi');

// Define schema
const itemSchema = Joi.object({
  name: Joi.string().min(3).max(50).required(),
  description: Joi.string().max(500),
  price: Joi.number().positive().precision(2),
  category: Joi.string().valid('electronics', 'clothing', 'food'),
  tags: Joi.array().items(Joi.string()),
  inStock: Joi.boolean().default(true)
});

// Validation middleware
const validate = (schema) => (req, res, next) => {
  const { error, value } = schema.validate(req.body, { abortEarly: false });
  
  if (error) {
    const errors = error.details.map(detail => ({
      field: detail.path.join('.'),
      message: detail.message
    }));
    return res.status(400).json({ success: false, errors });
  }
  
  req.body = value; // Use validated/sanitized values
  next();
};

// Usage
app.post('/api/items', validate(itemSchema), (req, res) => {
  // req.body is now validated and sanitized
  res.status(201).json(req.body);
});
```

## API Response Standards

Maintain consistent response formats:

```javascript theme={null}
// Success responses
const sendSuccess = (res, data, statusCode = 200) => {
  res.status(statusCode).json({
    success: true,
    data
  });
};

// Paginated responses
const sendPaginated = (res, data, page, limit, total) => {
  res.json({
    success: true,
    data,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit),
      hasNext: page * limit < total,
      hasPrev: page > 1
    }
  });
};

// Error responses
const sendError = (res, message, statusCode = 400) => {
  res.status(statusCode).json({
    success: false,
    error: message
  });
};
```

## Pagination

```javascript theme={null}
app.get('/api/items', async (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const skip = (page - 1) * limit;
  
  // Database query with pagination
  const items = await Item.find()
    .skip(skip)
    .limit(limit)
    .sort({ createdAt: -1 });
  
  const total = await Item.countDocuments();
  
  res.json({
    success: true,
    data: items,
    pagination: {
      page,
      limit,
      total,
      pages: Math.ceil(total / limit)
    }
  });
});
```

## Filtering and Sorting

```javascript theme={null}
app.get('/api/items', async (req, res) => {
  // Build filter object
  const filter = {};
  
  if (req.query.category) {
    filter.category = req.query.category;
  }
  if (req.query.minPrice) {
    filter.price = { ...filter.price, $gte: parseFloat(req.query.minPrice) };
  }
  if (req.query.maxPrice) {
    filter.price = { ...filter.price, $lte: parseFloat(req.query.maxPrice) };
  }
  if (req.query.search) {
    filter.name = { $regex: req.query.search, $options: 'i' };
  }
  
  // Build sort object
  const sort = {};
  if (req.query.sortBy) {
    const order = req.query.order === 'desc' ? -1 : 1;
    sort[req.query.sortBy] = order;
  }
  
  const items = await Item.find(filter).sort(sort);
  res.json({ success: true, data: items });
});

// Example: GET /api/items?category=electronics&minPrice=100&sortBy=price&order=desc
```

## HTTP Status Codes Reference

| Code    | Name                  | When to Use                        |
| ------- | --------------------- | ---------------------------------- |
| **200** | OK                    | Successful GET, PUT, PATCH         |
| **201** | Created               | Successful POST (resource created) |
| **204** | No Content            | Successful DELETE                  |
| **400** | Bad Request           | Invalid input, validation error    |
| **401** | Unauthorized          | Missing or invalid authentication  |
| **403** | Forbidden             | Authenticated but not authorized   |
| **404** | Not Found             | Resource doesn't exist             |
| **409** | Conflict              | Duplicate entry, version conflict  |
| **422** | Unprocessable Entity  | Valid JSON but semantic errors     |
| **429** | Too Many Requests     | Rate limit exceeded                |
| **500** | Internal Server Error | Server-side error                  |

## API Versioning

```javascript theme={null}
const v1Router = require('./routes/v1');
const v2Router = require('./routes/v2');

app.use('/api/v1', v1Router);
app.use('/api/v2', v2Router);

// Or using headers
app.use('/api', (req, res, next) => {
  const version = req.headers['api-version'] || 'v1';
  req.apiVersion = version;
  next();
});
```

## Complete REST Controller Example

```javascript theme={null}
// controllers/itemController.js
class ItemController {
  // GET /api/items
  async getAll(req, res, next) {
    try {
      const { page = 1, limit = 10, sort = '-createdAt' } = req.query;
      
      const items = await Item.find()
        .sort(sort)
        .skip((page - 1) * limit)
        .limit(parseInt(limit));
        
      const total = await Item.countDocuments();
      
      res.json({
        success: true,
        count: items.length,
        data: items,
        pagination: { page: parseInt(page), limit: parseInt(limit), total }
      });
    } catch (error) {
      next(error);
    }
  }

  // GET /api/items/:id
  async getById(req, res, next) {
    try {
      const item = await Item.findById(req.params.id);
      
      if (!item) {
        return res.status(404).json({
          success: false,
          error: 'Item not found'
        });
      }
      
      res.json({ success: true, data: item });
    } catch (error) {
      next(error);
    }
  }

  // POST /api/items
  async create(req, res, next) {
    try {
      const item = await Item.create(req.body);
      res.status(201).json({ success: true, data: item });
    } catch (error) {
      next(error);
    }
  }

  // PUT /api/items/:id
  async update(req, res, next) {
    try {
      const item = await Item.findByIdAndUpdate(
        req.params.id,
        req.body,
        { new: true, runValidators: true }
      );
      
      if (!item) {
        return res.status(404).json({
          success: false,
          error: 'Item not found'
        });
      }
      
      res.json({ success: true, data: item });
    } catch (error) {
      next(error);
    }
  }

  // DELETE /api/items/:id
  async delete(req, res, next) {
    try {
      const item = await Item.findByIdAndDelete(req.params.id);
      
      if (!item) {
        return res.status(404).json({
          success: false,
          error: 'Item not found'
        });
      }
      
      res.status(204).send();
    } catch (error) {
      next(error);
    }
  }
}

module.exports = new ItemController();
```
