Learnwizy Technologies Logo

Learnwizy Technologies

Class 25: Building a RESTful API with Express (CRUD - Create & Update)

Continuing our journey of building a RESTful API with Express.js, today we'll implement the "Create" (POST) and "Update" (PUT/PATCH) operations. These operations involve receiving data from the client in the request body, processing it, and updating our in-memory data store.

Remember, we are still using a JavaScript array to simulate our database.


Implementing POST Requests (Create)

The POST method is used to create new resources. When a client sends a POST request to a collection endpoint, it typically includes the data for the new resource in the request body.

// server.js
const express = require('express');
const app = express();
const port = 3000;

let books = [
  { id: '1', title: 'The Hitchhiker\'s Guide to the Galaxy', author: 'Douglas Adams', year: 1979 },
  { id: '2', title: '1984', author: 'George Orwell', year: 1949 },
  { id: '3', title: 'To Kill a Mockingbird', author: 'Harper Lee', year: 1960 }
];

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

// GET all books (from previous class)
app.get('/api/books', (req, res) => {
  res.status(200).json(books);
});

// GET single book by ID (from previous class)
app.get('/api/books/:id', (req, res) => {
  const book = books.find(b => b.id === req.params.id);
  if (book) {
    res.status(200).json(book);
  } else {
    res.status(404).json({ message: `Book with ID ${req.params.id} not found.` });
  }
});

// POST /api/books - Create a new book
app.post('/api/books', (req, res) => {
  const { title, author, year } = req.body; // Destructure data from request body

  // Basic validation
  if (!title || !author || !year) {
    // Send 400 Bad Request if required fields are missing
    return res.status(400).json({ message: 'Title, author, and year are required.' });
  }

  // Generate a simple unique ID (for in-memory data)
  const newId = (books.length > 0 ? Math.max(...books.map(b => parseInt(b.id))) + 1 : 1).toString();

  const newBook = {
    id: newId,
    title,
    author,
    year: parseInt(year) // Ensure year is a number
  };

  books.push(newBook); // Add the new book to our array

  // Respond with the newly created resource and 201 Created status
  res.status(201).json(newBook);
});

app.listen(port, () => {
  console.log(`Server running at http://localhost:${port}`);
});

Test it with Postman/Insomnia:

  1. Method: POST
  2. URL: http://localhost:3000/api/books
  3. Headers: Set Content-Type: application/json
  4. Body: Select "raw" and "JSON" and enter:
    {
      "title": "The Great Gatsby",
      "author": "F. Scott Fitzgerald",
      "year": 1925
    }
  5. Send the request. You should get a 201 Created response with the new book object, including its generated ID.
  6. Try sending a request with missing fields (e.g., no title) to see the 400 Bad Request response.

Implementing PUT Requests (Full Update)

The PUT method is used to fully update or replace an existing resource. The client sends the complete new representation of the resource in the request body.

// server.js (continued)

// PUT /api/books/:id - Fully update a book
app.put('/api/books/:id', (req, res) => {
  const bookId = req.params.id;
  const { title, author, year } = req.body;

  // Basic validation for full update
  if (!title || !author || !year) {
    return res.status(400).json({ message: 'Title, author, and year are required for a full update.' });
  }

  const bookIndex = books.findIndex(b => b.id === bookId);

  if (bookIndex !== -1) {
    // Update the existing book with new data
    books[bookIndex] = {
      id: bookId, // Ensure ID remains the same
      title,
      author,
      year: parseInt(year)
    };
    res.status(200).json(books[bookIndex]); // Respond with the updated book
  } else {
    // If book not found, return 404
    res.status(404).json({ message: `Book with ID ${bookId} not found for update.` });
  }
});

// ... app.listen() remains the same

Test it with Postman/Insomnia:

  1. First, make sure you have a book with ID '1' (or create a new one with POST).
  2. Method: PUT
  3. URL: http://localhost:3000/api/books/1
  4. Headers: Set Content-Type: application/json
  5. Body: Select "raw" and "JSON" and enter a complete new representation for the book:
    {
      "title": "The Updated Hitchhiker's Guide",
      "author": "Douglas Adams (Revised)",
      "year": 2020
    }
  6. Send the request. You should get a 200 OK response with the fully updated book.
  7. Try to update a non-existent ID (e.g., 99) to see the 404 Not Found response.

Implementing PATCH Requests (Partial Update)

The PATCH method is used to apply partial modifications to a resource. The client sends only the fields they want to update in the request body.

// server.js (continued)

// PATCH /api/books/:id - Partially update a book
app.patch('/api/books/:id', (req, res) => {
  const bookId = req.params.id;
  const updates = req.body; // Get the partial updates from the request body

  const bookIndex = books.findIndex(b => b.id === bookId);

  if (bookIndex !== -1) {
    // Merge updates into the existing book object
    // Object.assign creates a new object, preventing direct mutation of original
    books[bookIndex] = { ...books[bookIndex], ...updates };

    // Ensure year is parsed if it's part of the update
    if (updates.year) {
        books[bookIndex].year = parseInt(updates.year);
    }

    res.status(200).json(books[bookIndex]); // Respond with the partially updated book
  } else {
    res.status(404).json({ message: `Book with ID ${bookId} not found for partial update.` });
  }
});

// ... app.listen() remains the same

Difference between PUT and PATCH semantics:

Test it with Postman/Insomnia:

  1. First, make sure you have a book with ID '1'.
  2. Method: PATCH
  3. URL: http://localhost:3000/api/books/1
  4. Headers: Set Content-Type: application/json
  5. Body: Select "raw" and "JSON" and enter:
    {
      "title": "The Ultimate Guide",
      "year": 2025
    }
  6. Send the request. You should get a 200 OK response. Notice that only the title and year are updated, while author remains unchanged.

Validation and Error Handling (Intermediate)

We've already implemented basic validation for missing required fields. It's crucial to provide meaningful error messages to the client when something goes wrong.

// Example of improved validation and error response for POST
app.post('/api/books', (req, res) => {
  const { title, author, year } = req.body;

  const errors = [];
  if (!title) errors.push('Title is required.');
  if (!author) errors.push('Author is required.');
  if (!year) errors.push('Year is required.');
  if (year && isNaN(parseInt(year))) errors.push('Year must be a number.');

  if (errors.length > 0) {
    return res.status(400).json({ message: 'Validation failed', errors });
  }

  // ... rest of the creation logic ...
});

This approach provides more specific feedback to the client, making it easier for them to correct their requests.

You've now successfully implemented the Create and Update operations for your RESTful API. In the next class, we'll complete the CRUD cycle by adding the Delete operation and explore how to organize our routes using Express Router.