Day 05 - MERN: Mongoose Models, Routers, and Project Structure

Welcome to Day 5! As our backend applications grow, putting all our code into a single file quickly becomes unmanageable. Today, we will organize our project structure and learn how Express communicates with MongoDB using schemas, models, and modular routers.

1. Mongoose: Schemas and Models

While we can talk to MongoDB directly, most developers use Mongoose, an Object Data Modeling (ODM) library. It creates a bridge between your JavaScript objects and your MongoDB documents, saving you from writing complicated database queries.

To use Mongoose, you need two things:

  • Schema (The Blueprint): Defines the exact structure of your documents (the fields, data types, and rules).
  • Model (The Interface): Wraps the schema and provides the actual commands (like .find() or .save()) to interact with the database collection.
import mongoose from "mongoose";

// 1. The Schema (Blueprint)
const studentSchema = new mongoose.Schema({
    name: String,
    age: Number,
    city: String
});

// 2. The Model (Database Interface)
// Mongoose automatically converts "Student" to the plural "students" collection
const Student = mongoose.model("Student", studentSchema);

// Export it so other files can use it
export default Student;

2. Saving Data and Handling Promises

Database operations take time. Because Node.js does not want to freeze your server while waiting for the database, these operations return a Promise—a placeholder for a future result that will eventually succeed or fail.

Creating a new document using your model does not instantly save it to MongoDB. You must explicitly call .save(). We handle this asynchronous process using async/await.

app.post("/", async (req, res) => {
    try {
        // Create a JS object from the incoming client data
        const newStudent = new Student(req.body);
        
        // Wait for the database to actually store the document
        await newStudent.save(); 
        
        res.json({ message: "Successfully saved to database" });
    } catch (error) {
        // Catch and respond to any database errors
        res.status(500).json({ message: "Failed to save", error: error.message });
    }
});

3. Express Router and Clean Architecture

Writing every single API inside index.js is a bad practice. Instead, we follow the Single Responsibility Principle by separating our code into folders: a models folder for database structures, and a routers folder for API endpoints.

express.Router() creates a mini Express application. You group related routes (like everything related to students) in one file, and plug that router into your main application.

// routers/studentRouter.js
import express from "express";
import Student from "../models/student.js";

const studentRouter = express.Router();

studentRouter.get("/", async (req, res) => {
    const students = await Student.find(); // Retrieves all students
    res.json(students);
});

export default studentRouter;

// index.js (Main Application)
import express from "express";
import studentRouter from "./routers/studentRouter.js";

const app = express();

// Automatically prefixes all routes in the router with "/students"
app.use("/students", studentRouter);

4. The Magic of ObjectId

When you successfully save a document, MongoDB automatically generates a unique identifier for it called _id.

  • 12-Byte Format: This is not a standard UUID. It is a specific 12-byte ObjectId generated by MongoDB.
  • Built-in Timestamp: The first part of the ObjectId contains a timestamp of exactly when the document was created, meaning documents naturally sort chronologically without needing a separate “created at” date field.
  • Primary Key: You will use this _id constantly in the future to find, update, or delete specific documents (e.g., Student.findById(id)).
Share: X / Twitter LinkedIn Reddit WhatsApp

Comments

Questions, corrections, and practical takeaways are welcome here.