Mongoose & the Full StackCore· 50 min read

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.

Express routes backed by MongoDB (persistent!)
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:

  1. The client (browser or React app) sends an HTTP request, e.g. POST / with a JSON body.
  2. Express matches the URL and method to the right route handler.
  3. express.json() middleware has already parsed the body, so req.body.text is ready to use.
  4. The handler awaits a Mongoose call (Task.create(...)) which writes to MongoDB.
  5. MongoDB stores the document permanently and returns it (with its new _id).
  6. The handler sends a response — res.status(201).json(task) — back to the client.
  7. If anything throws, try/catch passes the error to next(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?

Answer: The route structure stayed the same; only the data layer changed (array → await Model.find()), and now data persists across restarts.

✍️ Practice

  1. Convert your in-memory Express CRUD API to use a Mongoose model.
  2. Restart the server and confirm the data is still there (it persists!).

🏠 Homework

  1. Add the UPDATE (PUT) route using findByIdAndUpdate with validation.
Want to learn this with a mentor?

CodingClave runs guided, project-based training (28-day, 45-day & 6-month batches).

Explore Training →