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:
- We define an
asynchandler forPOSTrequests to the root path (/). Remember, this will map to/api/itemsbecause of how we mounted the router inapp.js. - We use a
try...catchblock to handle potential errors during database operations. - We pull
nameandquantityfromreq.body. - We perform a simple check to ensure the required fields were sent. If not, we immediately return a
400 Bad Request. await Item.create()does the heavy lifting: it creates a new document based on our Mongoose schema and saves it to the database.- If successful, we send back a
201 Createdstatus code along with thenewItemobject, which now includes the_idand timestamps generated by MongoDB and Mongoose. - If
Item.create()fails (e.g., if you send a string forquantity), Mongoose throws a validation error, which is caught. We respond with a400 Bad Requestand 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.
-
Make sure your server is running (
npm run dev). -
Test the Success Case (201 Created):
- Method:
POST - URL:
http://localhost:5001/api/items - Headers: Set
Content-Typetoapplication/json. - Body: Select "raw" and "JSON", then enter:
{ "name": "Keyboard", "quantity": 50 } - Send the request.
- Expected Result: You should receive a
201 Createdstatus code. The response body should be the JSON object for your new item, complete with an_id.
- Method:
-
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 Requeststatus code, with a message like"Name and quantity are required fields.".
- Method:
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
POSTmethod is used to create new resources. - Incoming data is accessed via
req.body, thanks to theexpress.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 Createdstatus. - Input validation errors (missing fields, wrong data types) should result in a
400 Bad Requeststatus.
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.