# REST API Design Made Simple with Express.js

APIs are how applications communicate. A REST API is a structured way to request and receive data over HTTP. Building REST APIs is one of the most common uses for Express.

## What is an API?

An API (Application Programming Interface) is a contract between client and server. It says: "Send me this format, and I'll respond with that format."

For example, a social media API might say: "POST to `/posts` with a message, and I'll create a post."

## What is REST?

REST (Representational State Transfer) is a style of designing APIs. It uses HTTP methods (GET, POST, PUT, DELETE) and URLs to represent operations on resources.

A resource is something you can create, read, update, or delete. Examples: users, posts, comments, products.

Instead of having endpoints like:
- `/createUser`
- `/getUserData`
- `/deleteUser`

REST uses the resource URL with HTTP methods:
- `GET /users` - Get all users
- `POST /users` - Create a user
- `GET /users/123` - Get user 123
- `PUT /users/123` - Update user 123
- `DELETE /users/123` - Delete user 123

This is cleaner and more logical.

## HTTP Methods for CRUD

REST maps HTTP methods to database operations:

| HTTP Method | Operation | Meaning |
|---|---|---|
| **GET** | Read | Retrieve data |
| **POST** | Create | Add new data |
| **PUT** | Update | Modify existing data |
| **DELETE** | Delete | Remove data |

These four methods handle all basic operations (CRUD: Create, Read, Update, Delete).

## Basic REST API Example

Here's a simple REST API for managing blog posts:

```javascript
import express from 'express';

const app = express();
app.use(express.json());

// Sample data
let posts = [
  { id: 1, title: 'First Post', content: 'Hello world' },
  { id: 2, title: 'Second Post', content: 'Another post' }
];

// GET all posts
app.get('/posts', (req, res) => {
  res.json(posts);
});

// GET a single post
app.get('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) {
    return res.status(404).json({ error: 'Post not found' });
  }
  res.json(post);
});

// CREATE a new post
app.post('/posts', (req, res) => {
  const newPost = {
    id: posts.length + 1,
    title: req.body.title,
    content: req.body.content
  };
  posts.push(newPost);
  res.status(201).json(newPost);
});

// UPDATE a post
app.put('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) {
    return res.status(404).json({ error: 'Post not found' });
  }
  post.title = req.body.title || post.title;
  post.content = req.body.content || post.content;
  res.json(post);
});

// DELETE a post
app.delete('/posts/:id', (req, res) => {
  const index = posts.findIndex(p => p.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: 'Post not found' });
  }
  const deleted = posts.splice(index, 1);
  res.json({ message: 'Post deleted', post: deleted[0] });
});

app.listen(3000);
```

This API covers all CRUD operations for posts.

## Naming Conventions

Follow these conventions for clean URLs:

- **Use plural nouns for resources**: `/posts`, `/users`, `/products` (not `/post`, `/user`)
- **Use lowercase**: `/posts` (not `/Posts`)
- **Use hyphens for multi-word resources**: `/blog-posts` (not `/blogposts` or `/blog_posts`)
- **Avoid verbs in URLs**: Use HTTP methods instead
  - Wrong: `GET /posts/getAll`
  - Right: `GET /posts`

## Status Codes

Use appropriate HTTP status codes:

| Code | Meaning | Use When |
|---|---|---|
| **200** | OK | Request succeeded |
| **201** | Created | Resource created successfully |
| **400** | Bad Request | Client error (invalid data) |
| **401** | Unauthorized | Authentication required |
| **403** | Forbidden | Authenticated but not allowed |
| **404** | Not Found | Resource doesn't exist |
| **500** | Server Error | Server-side error |

```javascript
app.get('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) {
    return res.status(404).json({ error: 'Post not found' });
  }
  res.status(200).json(post); // 200 is default, can omit
});

app.post('/posts', (req, res) => {
  const newPost = { id: posts.length + 1, ...req.body };
  posts.push(newPost);
  res.status(201).json(newPost);
});
```

## Handling Query Parameters

Use query parameters for filtering and sorting:

```javascript
app.get('/posts', (req, res) => {
  let results = posts;

  // Filter by status
  if (req.query.status) {
    results = results.filter(p => p.status === req.query.status);
  }

  // Sort by date
  if (req.query.sort === 'date') {
    results.sort((a, b) => new Date(b.date) - new Date(a.date));
  }

  // Pagination
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const start = (page - 1) * limit;
  results = results.slice(start, start + limit);

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

Usage:
- `GET /posts?status=published`
- `GET /posts?sort=date`
- `GET /posts?page=2&limit=5`

## Error Handling

Consistent error responses help clients:

```javascript
app.get('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) {
    return res.status(404).json({
      error: {
        code: 'POST_NOT_FOUND',
        message: 'The requested post does not exist',
        details: `Post with ID ${req.params.id} not found`
      }
    });
  }
  res.json(post);
});
```

Structured error responses let clients handle errors properly.

## Request Validation

Validate data before processing:

```javascript
app.post('/posts', (req, res) => {
  const { title, content } = req.body;

  // Validation
  if (!title || !content) {
    return res.status(400).json({
      error: 'Title and content are required'
    });
  }

  if (title.length < 3) {
    return res.status(400).json({
      error: 'Title must be at least 3 characters'
    });
  }

  const newPost = { id: posts.length + 1, title, content };
  posts.push(newPost);
  res.status(201).json(newPost);
});
```

## API Versioning

As your API grows, you might need to change it. Versioning helps:

```javascript
app.get('/v1/posts', (req, res) => {
  // Version 1 implementation
  res.json(posts);
});

app.get('/v2/posts', (req, res) => {
  // Version 2 implementation (different format, more data, etc.)
  res.json(posts.map(p => ({
    id: p.id,
    title: p.title,
    description: p.content,
    createdAt: new Date()
  })));
});
```

This lets old clients continue using v1 while new clients use v2.

## Authentication in REST APIs

Protect routes with authentication:

```javascript
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }
  // Verify token (simplified)
  req.user = { id: 1 };
  next();
}

app.post('/posts', authenticate, (req, res) => {
  // Create post as authenticated user
  const newPost = {
    id: posts.length + 1,
    ...req.body,
    userId: req.user.id
  };
  posts.push(newPost);
  res.status(201).json(newPost);
});

app.delete('/posts/:id', authenticate, (req, res) => {
  // Delete logic
});
```

Only authenticated users can create or delete.

## CORS for Cross-Origin Requests

If your API is called from a different domain, enable CORS:

```bash
npm install cors
```

```javascript
import cors from 'cors';

app.use(cors());
// or restrict to specific domains
app.use(cors({
  origin: 'https://example.com'
}));
```

## Complete REST API Example

Here's a realistic REST API with authentication, validation, and proper status codes:

```javascript
import express from 'express';

const app = express();
app.use(express.json());

let posts = [];
let nextId = 1;

// Middleware: log requests
app.use((req, res, next) => {
  console.log(`${req.method} ${req.path}`);
  next();
});

// Middleware: authenticate
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (token === 'valid-token') {
    req.user = { id: 1 };
    next();
  } else {
    res.status(401).json({ error: 'Unauthorized' });
  }
}

// GET all posts
app.get('/posts', (req, res) => {
  res.json(posts);
});

// GET single post
app.get('/posts/:id', (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) return res.status(404).json({ error: 'Not found' });
  res.json(post);
});

// POST create post (requires auth)
app.post('/posts', authenticate, (req, res) => {
  const { title, content } = req.body;

  if (!title || !content) {
    return res.status(400).json({ error: 'Title and content required' });
  }

  const newPost = { id: nextId++, title, content, userId: req.user.id };
  posts.push(newPost);
  res.status(201).json(newPost);
});

// PUT update post
app.put('/posts/:id', authenticate, (req, res) => {
  const post = posts.find(p => p.id === parseInt(req.params.id));
  if (!post) return res.status(404).json({ error: 'Not found' });

  post.title = req.body.title || post.title;
  post.content = req.body.content || post.content;
  res.json(post);
});

// DELETE post
app.delete('/posts/:id', authenticate, (req, res) => {
  const index = posts.findIndex(p => p.id === parseInt(req.params.id));
  if (index === -1) return res.status(404).json({ error: 'Not found' });

  posts.splice(index, 1);
  res.json({ message: 'Deleted' });
});

app.listen(3000);
```

## Key Takeaways

- REST uses HTTP methods (GET, POST, PUT, DELETE) to represent operations
- Resources are nouns, not verbs: `/posts` not `/getPosts`
- Status codes indicate operation success: 200, 201, 400, 404, 500
- Query parameters filter and sort results
- Validation catches bad data before processing
- Authentication protects sensitive operations
- Consistent error responses help clients handle errors
- Versioning lets you evolve your API without breaking clients

Build REST APIs following these principles, and you'll create clean, intuitive, maintainable APIs that developers love to use.

