Day 06 - MERN: Project Architecture, Controllers, and Promises
Welcome to Day 6! As our Node.js projects grow, keeping all our logic inside a single file becomes unmanageable. Today, we will organize our application using an MVC-like structure and master asynchronous database operations using Promises.
1. The Request Flow and Project Structure
To keep code clean, we separate our application into distinct folders based on their responsibility. This is known as Separation of Concerns.
- Models: Defines the database schema and handles direct database operations (the Kitchen).
- Controllers: Contains the business logic and decides what happens to the data (the Chef).
- Routes: Defines the API URLs and directs incoming requests to the correct controller (the Waiter).
- index.js: The main entry point that starts Express and connects everything together.
When a client sends a request, the router intercepts it, hands it off to the controller, which then talks to the model to interact with the database. The router never touches the database directly!
2. Controllers in Action
Instead of writing all your database logic directly inside your router file, you extract it into a dedicated Controller function. This keeps your router incredibly clean.
// controllers/studentController.js
import Student from "../models/student.js";
export const createStudent = (req, res) => {
const newStudent = new Student({
name: req.body.name,
age: req.body.age
});
newStudent.save().then(() => {
res.json({ message: "Successfully saved to database" });
});
};
Now, your router simply imports the function and assigns it to a route:
// routes/studentRouter.js
import express from "express";
import { createStudent } from "../controllers/studentController.js";
const router = express.Router();
router.post("/", createStudent); // Clean and readable!
3. Exporting Modules: Default vs. Named
When splitting code across files, you need to share functions using exports.
- Default Export: Use this when a file has one primary purpose. You do not need curly braces when importing, and you can name the import whatever you want. ```javascript export default Student; import Student from “./student.js”; // No curly braces
* **Named Export:** Use this when exporting multiple functions from a single file (like a controller). You *must* use curly braces when importing, and the name must exactly match the export.
```javascript
export const createStudent = () => {};
export const getStudents = () => {};
import { createStudent, getStudents } from "./studentController.js";
4. Understanding Promises
Operations like saving a document to MongoDB (newStudent.save()) take time because the server has to connect over the network. JavaScript refuses to freeze your entire application while waiting, so it returns a Promise instead.
A Promise is a placeholder for a future value. It operates in three states:
- Pending: The database is currently processing the request.
- Fulfilled (Resolved): The data was successfully saved. We handle this outcome using
.then(). - Rejected: Something went wrong (e.g., network failure). We handle the error using
.catch().
newStudent.save()
.then(() => {
// Runs if the Promise is Fulfilled
res.json({ message: "Saved successfully" });
})
.catch((err) => {
// Runs if the Promise is Rejected
res.status(500).json({ message: err.message });
});
Comments
Questions, corrections, and practical takeaways are welcome here.