<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Aniruddha Bhattacharya</title>
    <description>The latest articles on DEV Community by Aniruddha Bhattacharya (@aniruddha_bhattacharya).</description>
    <link>https://dev.to/aniruddha_bhattacharya</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3154579%2F99baec0f-44ff-4909-bd42-aa03b9cf28ea.jpeg</url>
      <title>DEV Community: Aniruddha Bhattacharya</title>
      <link>https://dev.to/aniruddha_bhattacharya</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/aniruddha_bhattacharya"/>
    <language>en</language>
    <item>
      <title>Optimize Your Node.js MongoDB Code: Replace $lookup with Mongoose ref - [Anni]</title>
      <dc:creator>Aniruddha Bhattacharya</dc:creator>
      <pubDate>Wed, 14 May 2025 03:42:43 +0000</pubDate>
      <link>https://dev.to/aniruddha_bhattacharya/optimize-your-nodejs-mongodb-code-replace-lookup-with-mongoose-ref-anni-2hmn</link>
      <guid>https://dev.to/aniruddha_bhattacharya/optimize-your-nodejs-mongodb-code-replace-lookup-with-mongoose-ref-anni-2hmn</guid>
      <description>&lt;h2&gt;
  
  
  Introduction:
&lt;/h2&gt;

&lt;p&gt;In MongoDB, especially when working with &lt;strong&gt;Mongoose in Node.js&lt;/strong&gt;, there are two main ways to fetch related data:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Using MongoDB's native &lt;code&gt;$lookup&lt;/code&gt; in aggregation pipelines&lt;/li&gt;
&lt;li&gt;Using Mongoose’s built-in &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;populate()&lt;/code&gt; functionality&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While &lt;code&gt;$lookup&lt;/code&gt; gives fine-grained control and works directly at the database level, Mongoose's ref-based population simplifies your code, makes your data models more maintainable, and leads to better developer experience for most use cases.&lt;/p&gt;

&lt;p&gt;Let’s explore this using a real-world &lt;strong&gt;project management&lt;/strong&gt; example involving &lt;strong&gt;Sprints&lt;/strong&gt; and &lt;strong&gt;Tasks&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Schema Design: Tasks and Sprints
&lt;/h2&gt;

&lt;p&gt;Here's how we model the relationship between &lt;strong&gt;Tasks&lt;/strong&gt; and &lt;strong&gt;Sprints&lt;/strong&gt; in Mongoose using &lt;code&gt;ref&lt;/code&gt; and &lt;code&gt;populate&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const taskSchema = new mongoose.Schema({
  sprintId: {
    type: mongoose.Types.ObjectId,
    ref: 'sprints'
  },
  projectId: {
    type: mongoose.Types.ObjectId,
    ref: 'projects',
    required: true
  },
  title: { type: String, required: true, minlength: 3 },
  description: { type: String, default: '' },
  type: {
    type: String,
    enum: ['Story', 'Bug', 'Task', 'Epic', 'Sub-task'],
    default: 'Task'
  },
  status: {
    type: String,
    enum: ['To Do', 'In Progress', 'In Review', 'Done', 'Blocked'],
    default: 'To Do'
  },
  priority: {
    type: String,
    enum: ['Low', 'Medium', 'High', 'Critical'],
    default: 'Medium'
  },
  assignee: {
    _id: mongoose.Types.ObjectId,
    email: String,
    displayName: String
  },
  reporter: {
    _id: mongoose.Types.ObjectId,
    email: String,
    displayName: String
  },
  estimate: { type: Number, default: 0 },
  createdBy: {
    _id: mongoose.Types.ObjectId,
    email: String,
    displayName: String
  },
  createdAt: { type: Date, default: Date.now },
  dueDate: Date,
  lastModified: Date
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Task Creation Logic
&lt;/h2&gt;

&lt;p&gt;When creating a task, we also push its ID into the corresponding Sprint:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const taskModel = {
  createTask: async (sprintId, projectId, title, description, type, status, priority, assignee, reporter, estimate, createdBy, createdAt, dueDate, lastModified) =&amp;gt; {
    const task = new Task({
      sprintId: sprintId ? new mongoose.Types.ObjectId(sprintId) : undefined,
      projectId: new mongoose.Types.ObjectId(projectId),
      title,
      description,
      type,
      status,
      priority,
      assignee,
      reporter,
      estimate,
      createdBy,
      createdAt,
      dueDate,
      lastModified
    });

    const savedTask = await task.save();

    if (sprintId) {
      const Sprint = mongoose.model('sprints');
      await Sprint.findByIdAndUpdate(
        sprintId,
        { $push: { tasks: task._id } },
        { new: true }
      );
    }

    return savedTask;
  }
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Sprint Schema with &lt;code&gt;tasks&lt;/code&gt; as Array of &lt;code&gt;ref&lt;/code&gt;
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const sprintSchema = new mongoose.Schema({
  projectId: {
    type: mongoose.Types.ObjectId,
    ref: 'projects',
    required: true
  },
  sprintNum: { type: String, required: true },
  name: { type: String, required: true },
  description: { type: String, default: '' },
  duration: { type: String, enum: ['1 Week', '2 Weeks', '3 Weeks'] },
  startDate: { type: Date, required: true },
  endDate: { type: Date, required: true },
  tasks: [{
    type: mongoose.Types.ObjectId,
    ref: 'tasks'
  }],
  createdBy: {
    _id: mongoose.Types.ObjectId,
    email: String,
    displayName: String
  }
}, { timestamps: true });
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Populating Tasks from Sprints
&lt;/h2&gt;

&lt;p&gt;Instead of writing a complex &lt;code&gt;$lookup&lt;/code&gt; query, we simply do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const getAllSprints = async () =&amp;gt; {
  return await Sprint.find({}).populate('tasks').exec();
};
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This automatically pulls in all related task documents for each sprint!&lt;/p&gt;

&lt;h2&gt;
  
  
  Some plus factors to use ref over lookup
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;The code is very readable compared to the lookup.&lt;/li&gt;
&lt;li&gt;The integration of mongoose is seamless. Only thing you just have too push the ids into the array and ref and populate will do the job automatically.&lt;/li&gt;
&lt;li&gt;Note: This will be easier for small one-level joins. If you have multi-level joins or complex joins then aggregate is the best to do so.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;If you're using Mongoose, stop reaching for $lookup unless you have a really complex use case. For most apps, especially CRUD-based systems like task managers, ref and populate() are more than enough.&lt;/p&gt;

&lt;p&gt;Not only is the syntax cleaner, but it also keeps your code more expressive and schema-driven. Let MongoDB handle relationships behind the scenes—just like a good ORM should.&lt;/p&gt;

</description>
      <category>node</category>
      <category>mongodb</category>
      <category>mongoose</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Secure Your Node.js API: Implementing Robust User Login with JWT - [Anni]</title>
      <dc:creator>Aniruddha Bhattacharya</dc:creator>
      <pubDate>Mon, 12 May 2025 15:48:02 +0000</pubDate>
      <link>https://dev.to/aniruddha_bhattacharya/secure-your-nodejs-api-implementing-robust-user-login-with-jwt-anni-4emm</link>
      <guid>https://dev.to/aniruddha_bhattacharya/secure-your-nodejs-api-implementing-robust-user-login-with-jwt-anni-4emm</guid>
      <description>&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;In today's web development landscape, securing your APIs is paramount. User authentication is a critical first step in ensuring only authorized users can access your application's resources. In this post, we'll dive into a practical implementation of a secure user login system using Node.js, Express, and JSON Web Tokens (JWT). We'll explore input validation, database interaction, and the generation of access and refresh tokens.&lt;/p&gt;

&lt;h2&gt;
  
  
  Project Structure
&lt;/h2&gt;

&lt;p&gt;Before we delve into the code, let's take a quick look at the project structure.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;userops/
├── app/
│   ├── config/
│   ├── controller/
│   │   └── appController.js
│   │   └── middlewareControl.js
│   ├── model/
│   │   └── userModel.js
│   ├── routes/
│   │   └── appRoutes.js
│   └── service/
│       ├── mailService.js
│       └── service.js
├── node_modules/
├── test/
├── .env
├── app.js
├── Dockerfile
├── package-lock.json
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Better visualizing it in form of image.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuppmi4kktj71gexo0fi.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnuppmi4kktj71gexo0fi.png" alt="Image description" width="233" height="471"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Defining the Login Route
&lt;/h2&gt;

&lt;p&gt;The entry point for our login functionality is defined in our appRoutes.js file.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;router.route("/api/v1/userops/login")
  .post(middleware.validateReqBody("login"), controller.login);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here, we define a POST route at /api/v1/userops/login. Notice that we're using a middleware (validateReqBody) to handle input validation before passing the request to our login controller function.&lt;/p&gt;

&lt;h2&gt;
  
  
  Input Validation Middleware
&lt;/h2&gt;

&lt;p&gt;The validateReqBody middleware (likely in middlewareControl.js) is responsible for ensuring the incoming request body adheres to our defined rules. For the "login" case (Image 6), we're validating the email and password fields.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const express = require("express");
const { check } = require("express-validator");
const middleware = {
    validateReqBody: (prop) =&amp;gt; {
        switch (prop) {

            case "login": {
                return [
                    check("email")
                        .isEmail().withMessage("Valid email required.")
                        .trim().normalizeEmail(),

                    check("password")
                        .isLength({ min: 6 }).withMessage("Password must be at least 6 characters."),
                ]
            }
        }
    }
}

module.exports = middleware;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This ensures the email is in a valid format and the password has a minimum length, preventing basic errors from reaching our core logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Login Controller
&lt;/h2&gt;

&lt;p&gt;The login function within our appController.js handles the main login process.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login: (req, res, next) =&amp;gt; {
  try {
    const validationCheck = validationResult(req);
    if (!validationCheck.isEmpty()) {
      return res.status(422).send({
        status: "error",
        errorType: "validation",
        response: validationCheck.errors.map((item) =&amp;gt; item.msg).join(" | "),
      });
    }

    return service.login(req, res);
  } catch (error) {
    return res.status(500).send({
      status: "error",
      response: "Internal server error",
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;First, it checks for any validation errors. If errors exist, it returns a 422 status code with the validation messages. Otherwise, it calls the login service function to handle the core authentication logic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Login Service
&lt;/h2&gt;

&lt;p&gt;The login function in our service.js (Image 4) performs the crucial steps of verifying user credentials and generating JWTs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login: async (req, res) =&amp;gt; {
  try {
    const { email, password } = req.body;
    const emailExists = await userModel.login(email, password);

    if (emailExists &amp;amp;&amp;amp; Object.keys(emailExists).length &amp;gt; 0) {
      if (emailExists.isVerified === true) {
        const authToken = await jwt.sign(
          {
            userDetails: emailExists,
            purpose: "user_login",
          },
          JWT_SECRET_KEY,
          { expiresIn: "45m" }
        );

        const refreshToken = await jwt.sign(
          {
            userDetails: emailExists,
            purpose: "user_login",
          },
          JWT_SECRET_KEY,
          { expiresIn: "7d" }
        );

        return res.status(200).send({
          status: "success",
          response: "User logged in successfully.",
          authToken,
          refreshToken,
        });
      } else {
        return res.status(403).send({
          status: "error",
          response: "Your account is not verified. Please verify your email before logging in.",
        });
      }
    } else {
      return res.status(404).send({
        status: "error",
        response: "User not found. Please check your email and password.",
      });
    }
  } catch (error) {
    return res.status(500).send({
      status: "error",
      response: "Internal server error. " + error.message,
    });
  }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's a breakdown:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;It extracts the email and password from the request body.&lt;/li&gt;
&lt;li&gt;It calls the userModel.login function (Image 5) to find a user with the provided credentials.&lt;/li&gt;
&lt;li&gt;If a user is found:&lt;/li&gt;
&lt;li&gt;It checks if the user's email is verified. If not, it returns a 403 status.&lt;/li&gt;
&lt;li&gt;If verified, it generates both an authToken (short-lived) and a refreshToken (longer-lived) using JWT. These tokens are signed using a secret key (JWT_SECRET_KEY) and include user details and an expiration time.&lt;/li&gt;
&lt;li&gt;It returns a 200 status with a success message and the generated tokens.&lt;/li&gt;
&lt;li&gt;If no user is found, it returns a 404 status.&lt;/li&gt;
&lt;li&gt;Any errors during this process result in a 500 status.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The User Model
&lt;/h2&gt;

&lt;p&gt;The userModel.js likely interacts with your database (e.g., MongoDB using Mongoose) to find a user by email and password.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;login: async (emailAddress, password) =&amp;gt; {
  return await dbCon.findOne({ email: emailAddress, password: password }, { password: 0 }).exec();
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This function queries the database for a user matching the provided email and password. It uses findOne and excludes the actual password from the returned document for security reasons.&lt;/p&gt;

&lt;p&gt;This example demonstrates a fundamental yet crucial aspect of API security: user authentication. By implementing input validation, securely verifying credentials against a database, and issuing JWTs for authorized access, you can protect your application's resources effectively. Remember to handle your JWT_SECRET_KEY securely and consider implementing token refresh mechanisms in a production environment for a better user experience.&lt;/p&gt;

</description>
      <category>node</category>
      <category>express</category>
      <category>authentication</category>
      <category>jwt</category>
    </item>
  </channel>
</rss>
