Day 07 - MERN: Async/Await, Error Handling, and Database Modeling

Welcome to Day 7! Today, we tackle how JavaScript handles time-consuming tasks without freezing, how to catch unexpected errors gracefully, and how to translate project requirements into Mongoose database models.

1. Asynchronous JavaScript & async/await

JavaScript is fundamentally non-blocking. When your server asks the database for information, it does not freeze the entire application waiting for a response; it moves on to other tasks. To manage these delayed responses cleanly, we use async and await.

  • async: Adding this keyword to a function guarantees that it will return a Promise.
  • await: This keyword can only be used inside an async function. It pauses the execution of that specific function until the Promise settles, making asynchronous code read like simple, top-to-bottom synchronous code.
// A standard asynchronous controller function
export async function getUsers(req, res) {
    // Execution pauses here until the database returns the users
    const users = await User.find(); 
    res.json(users);
}

2. Bulletproofing with try…catch

When dealing with external databases or APIs, things will inevitably fail. Wrapping your logic in a try...catch block ensures that if an error occurs, your server handles it gracefully instead of crashing the entire backend. Combining this with async/await is the standard pattern for Express routes.

async function createUser(req, res) {
    try {
        // Try to execute this risky database operation
        const user = await User.create(req.body);
        res.status(201).json(user);
    } catch (err) {
        // If it fails, catch the error and send a clean failure response
        res.status(500).json({ message: "Failed to create user", error: err.message });
    }
}

3. Planning and Domain Analysis

Before writing code, robust applications start with a Software Requirements Specification (SRS) to define exactly what the system must do.

  • Functional Requirements: What the system actively does (e.g., “Users can create and delete books”).
  • Non-Functional Requirements: The constraints and qualities of the system (e.g., “Passwords must be securely hashed,” “API must respond in under 200ms”).

To figure out your database structure, look at your functional requirements and extract the nouns (e.g., User, Book). These nouns represent the core entities that will become your database models.

4. Building Mongoose Models

Once you have identified your entities, you translate them into Mongoose Schemas (the blueprint) and Models (the interface used to query the database).

Mongoose schemas allow you to enforce strict rules on your NoSQL database:

  • type: Forces the data into a specific format (String, Number, Boolean, Date).
  • required: true: Rejects the document if this field is missing.
  • unique: true: Creates a database index to prevent duplicate entries (like two users registering with the same email).
import mongoose from "mongoose";

// 1. Define the Schema (Blueprint)
const userSchema = new mongoose.Schema({
    email: { 
        type: String, 
        required: true, 
        unique: true 
    },
    firstName: { 
        type: String, 
        required: true 
    }
});

// 2. Create and export the Model
const User = mongoose.model("User", userSchema);
export default User;

Share: X / Twitter LinkedIn Reddit WhatsApp

Comments

Questions, corrections, and practical takeaways are welcome here.