javascript-today

REST API Basics

REST (Representational State Transfer) is an architectural style for designing APIs. Most modern web APIs follow REST principles to create predictable, easy-to-use interfaces.

What is REST?

REST uses HTTP methods to perform operations on resources:

GET    /api/users       → Get all users
GET    /api/users/1     → Get user with ID 1
POST   /api/users       → Create new user
PUT    /api/users/1     → Update user 1
DELETE /api/users/1     → Delete user 1

Key concepts:

  • Resources (users, posts, products)
  • HTTP methods (GET, POST, PUT, DELETE)
  • Status codes (200, 404, 500)
  • Stateless (each request is independent)

HTTP Methods

GET - Retrieve Data

const express = require('express');
const app = express();

// Get all items
app.get('/api/products', (req, res) => {
  const products = [
    { id: 1, name: 'Laptop', price: 999 },
    { id: 2, name: 'Phone', price: 599 }
  ];
  
  res.json(products);
});

// Get single item
app.get('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const product = { id, name: 'Laptop', price: 999 };
  
  res.json(product);
});

POST - Create Data

app.use(express.json());

app.post('/api/products', (req, res) => {
  const { name, price } = req.body;
  
  const newProduct = {
    id: Date.now(), // Simple ID generation
    name,
    price
  };
  
  // Return 201 Created with new resource
  res.status(201).json(newProduct);
});

PUT - Update Data (Replace)

app.put('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { name, price } = req.body;
  
  const updatedProduct = {
    id,
    name,
    price
  };
  
  res.json(updatedProduct);
});

PATCH - Update Data (Partial)

app.patch('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  
  // Only update provided fields
  const updates = req.body;
  
  const product = {
    id,
    name: 'Laptop',
    price: 999,
    ...updates // Apply partial updates
  };
  
  res.json(product);
});

DELETE - Remove Data

app.delete('/api/products/:id', (req, res) => {
  const id = parseInt(req.params.id);
  
  // Return 204 No Content (successful deletion)
  res.status(204).send();
  
  // Or return deleted item with 200
  // res.json({ message: 'Deleted', id });
});

HTTP Status Codes

Use appropriate status codes:

// 200 OK - Successful GET, PUT, PATCH
res.status(200).json(data);

// 201 Created - Successful POST
res.status(201).json(newResource);

// 204 No Content - Successful DELETE
res.status(204).send();

// 400 Bad Request - Invalid input
res.status(400).json({ error: 'Invalid data' });

// 401 Unauthorized - Not authenticated
res.status(401).json({ error: 'Please login' });

// 403 Forbidden - Not authorized
res.status(403).json({ error: 'Access denied' });

// 404 Not Found - Resource doesn't exist
res.status(404).json({ error: 'Not found' });

// 500 Internal Server Error - Server error
res.status(500).json({ error: 'Server error' });

Common Status Codes

Code Meaning Use Case
200 OK Successful GET, PUT, PATCH
201 Created Successful POST
204 No Content Successful DELETE
400 Bad Request Invalid input
401 Unauthorized Not logged in
403 Forbidden Insufficient permissions
404 Not Found Resource doesn’t exist
500 Server Error Something went wrong

RESTful URL Design

Good RESTful URLs

✅ /api/users              → Collection
✅ /api/users/123          → Specific user
✅ /api/users/123/posts    → User's posts
✅ /api/posts/456/comments → Post's comments

Bad URLs

❌ /api/getUsers           → Don't use verbs
❌ /api/user              → Use plural
❌ /api/users/delete/123  → Use DELETE method
❌ /api/users?id=123      → Use /users/123

Resource Relationships

// User's posts
app.get('/api/users/:userId/posts', (req, res) => {
  const userId = req.params.userId;
  const posts = []; // Get user's posts
  res.json(posts);
});

// Post's comments
app.get('/api/posts/:postId/comments', (req, res) => {
  const postId = req.params.postId;
  const comments = []; // Get post's comments
  res.json(comments);
});

Building a Simple REST API

Complete example with in-memory data:

const express = require('express');
const app = express();

app.use(express.json());

// In-memory database
let users = [
  { id: 1, name: 'Alice', email: 'alice@example.com' },
  { id: 2, name: 'Bob', email: 'bob@example.com' }
];

let nextId = 3;

// GET /api/users - Get all users
app.get('/api/users', (req, res) => {
  res.json(users);
});

// GET /api/users/:id - Get user by ID
app.get('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const user = users.find(u => u.id === id);
  
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  res.json(user);
});

// POST /api/users - Create new user
app.post('/api/users', (req, res) => {
  const { name, email } = req.body;
  
  // Validation
  if (!name || !email) {
    return res.status(400).json({ 
      error: 'Name and email are required' 
    });
  }
  
  const newUser = {
    id: nextId++,
    name,
    email
  };
  
  users.push(newUser);
  
  res.status(201).json(newUser);
});

// PUT /api/users/:id - Update user
app.put('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const { name, email } = req.body;
  
  const userIndex = users.findIndex(u => u.id === id);
  
  if (userIndex === -1) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  // Validation
  if (!name || !email) {
    return res.status(400).json({ 
      error: 'Name and email are required' 
    });
  }
  
  const updatedUser = { id, name, email };
  users[userIndex] = updatedUser;
  
  res.json(updatedUser);
});

// PATCH /api/users/:id - Partial update
app.patch('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  const updates = req.body;
  
  const user = users.find(u => u.id === id);
  
  if (!user) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  Object.assign(user, updates);
  
  res.json(user);
});

// DELETE /api/users/:id - Delete user
app.delete('/api/users/:id', (req, res) => {
  const id = parseInt(req.params.id);
  
  const userIndex = users.findIndex(u => u.id === id);
  
  if (userIndex === -1) {
    return res.status(404).json({ error: 'User not found' });
  }
  
  users.splice(userIndex, 1);
  
  res.status(204).send();
});

app.listen(3000, () => {
  console.log('API running on http://localhost:3000');
});

Testing the API

Using curl

# Get all users
curl http://localhost:3000/api/users

# Get user by ID
curl http://localhost:3000/api/users/1

# Create user
curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name":"Charlie","email":"charlie@example.com"}'

# Update user
curl -X PUT http://localhost:3000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice Smith","email":"alice.smith@example.com"}'

# Partial update
curl -X PATCH http://localhost:3000/api/users/1 \
  -H "Content-Type: application/json" \
  -d '{"name":"Alice Johnson"}'

# Delete user
curl -X DELETE http://localhost:3000/api/users/1

Using JavaScript fetch

// Get all users
fetch('http://localhost:3000/api/users')
  .then(res => res.json())
  .then(users => console.log(users));

// Get user by ID
fetch('http://localhost:3000/api/users/1')
  .then(res => res.json())
  .then(user => console.log(user));

// Create user
fetch('http://localhost:3000/api/users', {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Charlie',
    email: 'charlie@example.com'
  })
})
  .then(res => res.json())
  .then(user => console.log('Created:', user));

// Update user
fetch('http://localhost:3000/api/users/1', {
  method: 'PUT',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({
    name: 'Alice Smith',
    email: 'alice.smith@example.com'
  })
})
  .then(res => res.json())
  .then(user => console.log('Updated:', user));

// Delete user
fetch('http://localhost:3000/api/users/1', {
  method: 'DELETE'
})
  .then(res => console.log('Deleted:', res.status));

Query Parameters

Filter and paginate with query strings:

// GET /api/users?role=admin
app.get('/api/users', (req, res) => {
  let result = users;
  
  // Filter by role
  if (req.query.role) {
    result = result.filter(u => u.role === req.query.role);
  }
  
  // Search by name
  if (req.query.search) {
    result = result.filter(u => 
      u.name.toLowerCase().includes(req.query.search.toLowerCase())
    );
  }
  
  res.json(result);
});

// GET /api/users?page=2&limit=10
app.get('/api/users', (req, res) => {
  const page = parseInt(req.query.page) || 1;
  const limit = parseInt(req.query.limit) || 10;
  const skip = (page - 1) * limit;
  
  const paginatedUsers = users.slice(skip, skip + limit);
  
  res.json({
    data: paginatedUsers,
    page,
    limit,
    total: users.length
  });
});

Response Formats

Standard Success Response

app.get('/api/users', (req, res) => {
  res.json({
    success: true,
    data: users,
    count: users.length
  });
});

Standard Error Response

app.get('/api/users/:id', (req, res) => {
  const user = users.find(u => u.id === parseInt(req.params.id));
  
  if (!user) {
    return res.status(404).json({
      success: false,
      error: {
        message: 'User not found',
        code: 'USER_NOT_FOUND'
      }
    });
  }
  
  res.json({
    success: true,
    data: user
  });
});

Organizing Routes

Use Express Router:

// routes/users.js
const express = require('express');
const router = express.Router();

router.get('/', (req, res) => {
  res.json(users);
});

router.get('/:id', (req, res) => {
  // Get user by ID
});

router.post('/', (req, res) => {
  // Create user
});

router.put('/:id', (req, res) => {
  // Update user
});

router.delete('/:id', (req, res) => {
  // Delete user
});

module.exports = router;
// server.js
const express = require('express');
const usersRouter = require('./routes/users');

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

app.use('/api/users', usersRouter);

app.listen(3000);

REST API Best Practices

DO:

  • Use nouns for resources (/users, /posts)
  • Use plural names (/users, not /user)
  • Use HTTP methods correctly
  • Return appropriate status codes
  • Version your API (/api/v1/users)
  • Validate input
  • Handle errors gracefully

DON’T:

  • Use verbs in URLs (/getUser, /deletePost)
  • Return 200 for errors
  • Ignore status codes
  • Expose internal errors to clients
  • Change URLs without versioning

Summary

Method URL Purpose Status
GET /api/users Get all users 200
GET /api/users/:id Get one user 200 or 404
POST /api/users Create user 201
PUT /api/users/:id Update user (full) 200 or 404
PATCH /api/users/:id Update user (partial) 200 or 404
DELETE /api/users/:id Delete user 204 or 404

Key Principles:

  • Resources are nouns
  • HTTP methods are verbs
  • Status codes communicate results
  • Each request is independent (stateless)