CSE 341: Web Services

W02 Learning Activity: CRUD Operations

Overview

In Week 01, your API only read data. This week, you will add routes that create, update, and delete data. These operations use the HTTP methods POST, PUT, and DELETE.

You will also connect those HTTP methods to MongoDB operations such as insertOne, updateOne, and deleteOne.

Think of this activity as the bridge from your Week 01 read-only API to the Week 02 API, where clients can change the data stored in MongoDB.

Preparation Material

CRUD and HTTP methods

CRUD stands for Create, Read, Update, and Delete. These are the basic operations most data-driven applications need.

In REST-style APIs, HTTP methods help communicate which CRUD operation the client wants to perform.

POST creates a new resource

A POST request usually sends data in the request body. The server validates that data, inserts a new document into the database, and returns a response that tells the client what was created.

POST /products
Content-Type: application/json

{
  "id": "p4",
  "name": "Wireless Mouse",
  "category": "electronics",
  "price": 24.99,
  "inStock": true
}

In MongoDB, this often maps to insertOne.

const result = await getDb()
  .collection('products')
  .insertOne(newProduct);

The getDb() helper returns the database connection object. The collection('products') call chooses which collection to work with. The insertOne(newProduct) call adds one JavaScript object as a new MongoDB document.

MongoDB returns a result object after the insert. Your code can check that result, especially the insertedId, to confirm that the document was created.

PUT updates an existing resource

A PUT request usually includes an id in the URL and the updated data in the request body. The server finds the existing document and updates it.

PUT /products/p4
Content-Type: application/json

{
  "id": "p4",
  "name": "Wireless Mouse",
  "category": "electronics",
  "price": 19.99,
  "inStock": true
}

In MongoDB, this often maps to updateOne.

const result = await getDb()
  .collection('products')
  .updateOne(
    { id: productId },
    { $set: updatedProduct }
  );

The first argument is the filter that tells MongoDB which document to update. In this example, { id: productId } means "find the product whose id field matches productId."

The second argument tells MongoDB what change to make. The $set operator updates only the fields included in updatedProduct. MongoDB returns a result object with counts such as matchedCount and modifiedCount, which help your controller decide whether to return success or 404 Not Found.

Why use $set?

The $set operator tells MongoDB which fields to update. Without an update operator such as $set, you can accidentally replace more of the document than intended.

DELETE removes an existing resource

A DELETE request usually includes an id in the URL. The server finds that document and removes it from the database.

DELETE /products/p4

In MongoDB, this often maps to deleteOne.

const result = await getDb()
  .collection('products')
  .deleteOne({ id: productId });

Like updateOne, deleteOne receives a filter object that identifies which document should be removed. In this example, MongoDB deletes the product whose id field matches productId.

MongoDB returns a result object with deletedCount. If deletedCount is 1, a document was deleted. If it is 0, no matching product was found, so the API should usually return 404 Not Found.

Checking MongoDB results

MongoDB write operations return result objects. These result objects help your API decide which status code to send back.

For example, if updateOne returns matchedCount: 0, the requested document was not found. Your API should usually return a 404 response in that case.

Common status codes for write routes

Request bodies and validation

POST and PUT requests usually send JSON in the request body. Make sure your Express app includes app.use(express.json()) so Express can read that JSON and make it available as req.body.

Your controllers should validate required fields before calling MongoDB. If required fields are missing or invalid, return a safe 400 response instead of letting bad data reach the database.

Id strategy for Week 02

For the ecommerce examples in this activity, products use custom string ids such as p1, p2, and p3. In your Week 02 assignment, you will make a similar id decision for books and authors.

Example controller flow for POST /products

const createProduct = async (req, res) => {
  try {
    const { id, name, category, price, inStock } = req.body;

    if (!id || !name || !category || price === undefined || inStock === undefined) {
      return res.status(400).json({ message: 'id, name, category, price, and inStock are required' });
    }

    const newProduct = { id, name, category, price, inStock };
    await productModel.createProduct(newProduct);

    return res.status(201).json(newProduct);
  } catch (error) {
    return res.status(500).json({ message: 'Unable to create product' });
  }
};

Duplicate ids

Decide how your API should handle a create request that uses an id that already exists. Many APIs return 409 Conflict for duplicate data, but for this course a clear 400 response is also acceptable if your spec documents that choice.

Activity Instructions

Trace one write route before coding

Before implementing your Week 02 assignment, think through one ecommerce route, such as POST /products. Use these questions to practice tracing the request from the client to MongoDB and back to the client.

  1. What method and URL will the client send?
    Example answer (click to expand)

    The client sends a POST request to /products. The POST method tells the API that the client wants to create a new product.

  2. What request body fields are required?
    Example answer (click to expand)

    For this product example, the request body should include fields such as id, name, category, price, and inStock. Your assignment routes will use different fields, but the same idea applies: decide which fields are required before accepting the request.

  3. What validation should happen before calling MongoDB?
    Example answer (click to expand)

    The controller should check that each required field exists and has a reasonable value. For example, price should be present, and inStock should not be missing. If required data is missing, the controller should return 400 Bad Request before calling MongoDB.

  4. Which MongoDB method should the model call?
    Example answer (click to expand)

    The model should call insertOne because the route creates one new product document in the products collection.

  5. What result value should the controller check?
    Example answer (click to expand)

    After insertOne, MongoDB returns a result object. The controller or model can check insertedId to confirm that MongoDB created the document.

  6. What status code and JSON response should be returned?
    Example answer (click to expand)

    If the product is created successfully, the API should usually return 201 Created. The JSON response could return the created product or a short confirmation message. If required fields are missing, return 400. If something unexpected fails, return 500 with a safe error message.

Connect HTTP verbs to MongoDB operations

Use the following table as a guide while writing your Week 02 spec and implementation:

API action HTTP method MongoDB operation
Create a product POST insertOne
Read products GET find or findOne
Update a product PUT updateOne
Delete a product DELETE deleteOne

Submission

After you have completed all the learning activities for this week, return to Canvas to take the W02 Learning Activities quiz.

Other Links: