Using Mongoose in Express Routes
The moment it all connects — a real Express API backed by a real MongoDB database. This is MERN’s back-end, complete.
What you will learn
- Wire Mongoose models into Express routes
- Build a persistent CRUD API
- Handle async and errors properly
A persistent CRUD API
Remember the Express CRUD API that used an array? Now we replace the array with real database calls through a Mongoose model. The routes look almost the same — but the data is permanent.
const express = require("express");
const Task = require("./models/Task"); // a Mongoose model
const router = express.Router();
// READ all
router.get("/", async (req, res, next) => {
try {
const tasks = await Task.find();
res.json(tasks);
} catch (err) { next(err); }
});
// CREATE
router.post("/", async (req, res, next) => {
try {
const task = await Task.create({ text: req.body.text });
res.status(201).json(task);
} catch (err) { next(err); }
});
// DELETE
router.delete("/:id", async (req, res, next) => {
try {
await Task.findByIdAndDelete(req.params.id);
res.json({ message: "Deleted" });
} catch (err) { next(err); }
});
module.exports = router;Each route is an async function so it can await the database. The GET / handler calls await Task.find() to load every task and sends them back as JSON. The POST / handler reads req.body.text (what the client sent), await Task.create({...}) saves a new task, and replies with status 201 (“Created”) plus the new task. The DELETE /:id handler reads the id from the URL (req.params.id), removes that task, and replies with a confirmation message. Every handler is wrapped in try/catch, and on any error it calls next(err) to hand the problem to the central error handler.
Note: Output — a GET / request returns JSON like:
[
{ "id": "65f...a1", "text": "Buy milk", "_v": 0 },
{ "id": "65f...b2", "text": "Walk dog", "_v": 0 }
]
A POST / with body { "text": "Read book" } replies with status 201 and the single new task. Because the data now lives in MongoDB, it is still there after you restart the server.
Here is the full request → response flow for one of these routes, end to end:
- The client (browser or React app) sends an HTTP request, e.g.
POST /with a JSON body. - Express matches the URL and method to the right route handler.
express.json()middleware has already parsed the body, soreq.body.textis ready to use.- The handler
awaits a Mongoose call (Task.create(...)) which writes to MongoDB. - MongoDB stores the document permanently and returns it (with its new
_id). - The handler sends a response —
res.status(201).json(task)— back to the client. - If anything throws,
try/catchpasses the error tonext(err)and the error handler replies instead.
Note: The structure is identical to your earlier in-memory API — only the data source changed from an array to await Task.find(). Everything you learned transfers directly.
Tip: Wrap every async route in try/catch and pass errors to next(err) so your central error handler (from the Express course) deals with them. This keeps routes clean and robust.
Q. What changed when moving from the in-memory API to MongoDB?
✍️ Practice
- Convert your in-memory Express CRUD API to use a Mongoose model.
- Restart the server and confirm the data is still there (it persists!).
🏠 Homework
- Add the UPDATE (PUT) route using
findByIdAndUpdatewith validation.