Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 12

Creating Resources with POST and `Model.create()`

Recommended resources

Introduction

In the previous lessons, you successfully implemented the "Read" operations of CRUD by building endpoints to get all items (GET /api/items) and a single item by its ID (GET /api/items/:id). You've mastered fetching data with Model.find() and Model.findById(), and you're already handling different response codes like 200, 400, and 404.

Now, we move to the "C" in CRUD: Create. This lesson is laser-focused on implementing a POST route to add a new resource to your database. You will learn how to process incoming data from the request body and use the Mongoose Model.create() method. Upon successful creation, your API will correctly signal this by returning a 201 Created status code, a standard for production-ready REST APIs.


1. Handling Incoming Data: req.body

When a client wants to create a new resource, it sends the data for that resource in the body of a POST request, typically in JSON format. The express.json() middleware, which you configured in the initial setup, is responsible for parsing this JSON payload. Once parsed, Express makes the data available to your route handler as a JavaScript object on req.body.

For our Item model, which requires a name and quantity, a client would send a request body like this:

{
    "name": "Laptop",
    "quantity": 15
}

Inside your route handler, you can access these values with req.body.name and req.body.quantity.


2. Creating Documents: new Model().save() vs. Model.create()

Mongoose offers two primary patterns for creating and saving a new document. It's valuable to recognize both, but for exam speed and code clarity, one is generally superior.

The video below demonstrates the first pattern: instantiating a new model object and then calling the .save() method on it.

Build A REST API With Node.js, Express, & MongoDB - Quick

This clip from Web Dev Simplified's 'Build A REST API With Node.js, Express, & MongoDB - Quick' provides a fast-paced walkthrough of building a POST route. Pay close attention to how he constructs a new object, calls .save() on it, and handles both success (201) and validation errors (400).

Watch the segment from 16:08 to 18:28. Focus on the try...catch block, the use of new subscriber(...), the await subscriber.save(), and the explicit res.status(201) and res.status(400) calls.

While the .save() pattern works perfectly well, Mongoose provides a more concise and often preferred alternative: Model.create(). This single static method takes an object, validates it, creates the document, and saves it to the database all in one asynchronous step.

For a direct, text-based example of this cleaner pattern, review the following resource.

Defining Mongoose Schemas and Models

The article from CodeSignal Learn, 'Defining Mongoose Schemas and Models', shows a very clean implementation of a POST route using Todo.create(). This is the exact pattern we'll be using.

Find the section titled 'Step 4: Creating a Sample Todo Item via API Route'. Examine the code for the app.post('/todos', ...) handler. Notice how Todo.create() is used to create and save the document in a single await call.

For your exam, the Model.create() pattern is highly recommended. It's faster to write and reduces the chance of errors, such as forgetting to await the .save() call.


3. Your Turn: Implement the POST Route

Now it's time to add the creation logic to your application. You will add a new route handler to src/routes/item.routes.js that listens for POST requests.

Action: Open src/routes/item.routes.js and add the following code block. A good place for it is right after the GET /api/items/:id route, before the module.exports line.

// src/routes/item.routes.js

// ... (keep all your existing code)

// @route   POST /api/items
// @desc    Create a new item
// @access  Public
router.post('/', async (req, res) => {
    try {
        // Destructure name and quantity from the request body
        const { name, quantity } = req.body;

        // Basic validation: Check if required fields are present
        if (!name || quantity === undefined) {
            return res.status(400).json({ message: 'Name and quantity are required fields.' });
        }

        // Use Item.create() to create and save the new item in one step
        const newItem = await Item.create({
            name,
            quantity
        });

        // Respond with a 201 status and the created item
        res.status(201).json(newItem);

    } catch (error) {
        // Mongoose validation errors will be caught here
        // e.g., if quantity is not a number
        res.status(400).json({ message: 'Error creating item', error: error.message });
    }
});

module.exports = router;

Code Breakdown:

  1. We define an async handler for POST requests to the root path (/). Remember, this will map to /api/items because of how we mounted the router in app.js.
  2. We use a try...catch block to handle potential errors during database operations.
  3. We pull name and quantity from req.body.
  4. We perform a simple check to ensure the required fields were sent. If not, we immediately return a 400 Bad Request.
  5. await Item.create() does the heavy lifting: it creates a new document based on our Mongoose schema and saves it to the database.
  6. If successful, we send back a 201 Created status code along with the newItem object, which now includes the _id and timestamps generated by MongoDB and Mongoose.
  7. If Item.create() fails (e.g., if you send a string for quantity), Mongoose throws a validation error, which is caught. We respond with a 400 Bad Request and the error message.

4. Testing Your Endpoint

Verifying your implementation is critical. Use your API client (like Postman) to test both the success and failure scenarios.

  1. Make sure your server is running (npm run dev).

  2. Test the Success Case (201 Created):

    • Method: POST
    • URL: http://localhost:5001/api/items
    • Headers: Set Content-Type to application/json.
    • Body: Select "raw" and "JSON", then enter:
      {
          "name": "Keyboard",
          "quantity": 50
      }
      
    • Send the request.
    • Expected Result: You should receive a 201 Created status code. The response body should be the JSON object for your new item, complete with an _id.
  3. Test the Validation Failure Case (400 Bad Request):

    • Method: POST
    • URL: http://localhost:5001/api/items
    • Body: Send a request that is missing a required field:
      {
          "quantity": 25
      }
      
    • Send the request.
    • Expected Result: You should receive a 400 Bad Request status code, with a message like "Name and quantity are required fields.".

Conclusion

Excellent work. You have now implemented the "Create" functionality, a cornerstone of any API. You have a working POST endpoint that correctly validates input, creates new database records, and uses the appropriate HTTP status codes.

Key Takeaways:

  • The POST method is used to create new resources.
  • Incoming data is accessed via req.body, thanks to the express.json() middleware.
  • Model.create(doc) is the most efficient pattern for creating and saving a new document.
  • A successful creation should always be signaled with a 201 Created status.
  • Input validation errors (missing fields, wrong data types) should result in a 400 Bad Request status.

Next Up:
You've covered Create and Read. The next lesson will focus on "Update", where you'll implement a PUT or PATCH route to modify existing resources in your database.