<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[what i learnt from building my project 'casejeeto']]></title><description><![CDATA[what i learnt from building my project 'casejeeto']]></description><link>https://publiclawyercontrollerlearnings.hashnode.dev</link><generator>RSS for Node</generator><lastBuildDate>Sat, 07 Nov 2026 13:54:51 GMT</lastBuildDate><atom:link href="https://publiclawyercontrollerlearnings.hashnode.dev/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Express Server Notes]]></title><description><![CDATA[What problem does each part solve, and how does a request travel through the backend? When a request arrives: Express → creates the backend/server…]]></description><link>https://publiclawyercontrollerlearnings.hashnode.dev/express-server-notes</link><guid isPermaLink="true">https://publiclawyercontrollerlearnings.hashnode.dev/express-server-notes</guid><dc:creator><![CDATA[Jyoti Tiwari]]></dc:creator><pubDate>Sat, 12 Sep 2026 15:10:39 GMT</pubDate><content:encoded><![CDATA[<p><strong>What problem does each part solve, and how does a request travel through the backend?</strong></p>
<h2>1. Server setup:</h2>
<pre><code class="language-text">Import packages
     ↓
Create Express app
     ↓
Add middleware
     ↓
Connect database
     ↓
Create auth/helpers
     ↓
Create routes/controllers
     ↓
Start server
</code></pre>
<p>When a request arrives:</p>
<pre><code class="language-text">CLIENT
  ↓
REQUEST
  ↓
MIDDLEWARE
  ↓
CONTROLLER
  ↓
DATABASE
  ↓
RESPONSE
</code></pre>
<hr />
<h1>2. Importing Packages</h1>
<pre><code class="language-js">const express = require('express');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
</code></pre>
<ul>
<li><p><strong>Express</strong> → creates the backend/server.</p>
</li>
<li><p><strong>Mongoose</strong> → communicates with MongoDB.</p>
</li>
<li><p><strong>jsonwebtoken</strong> → creates and verifies JWTs.</p>
</li>
</ul>
<pre><code class="language-text">Node.js → Express → Server
Node.js → Mongoose → MongoDB
Node.js → JWT → Authentication
</code></pre>
<hr />
<h1>3. Importing Our Own Models</h1>
<pre><code class="language-js">const User = require('./User');
const Client = require('./Client');
</code></pre>
<p><code>./User</code> means:</p>
<blockquote>
<p>Go to <code>User.js</code> and give me what that file exports.</p>
</blockquote>
<p>The models define how our MongoDB data is structured.</p>
<hr />
<h1>4. Creating the Express App</h1>
<pre><code class="language-js">const app = express();
</code></pre>
<p><code>express()</code> creates an Express application.</p>
<p>We store it in <code>app</code>, which is why we can use:</p>
<pre><code class="language-js">app.get(...)
app.post(...)
app.use(...)
app.listen(...)
</code></pre>
<p>Think:</p>
<pre><code class="language-text">express()
   ↓
Express application
   ↓
app
</code></pre>
<hr />
<h1>5. JSON Middleware</h1>
<pre><code class="language-js">app.use(express.json());
</code></pre>
<p>If Postman sends:</p>
<pre><code class="language-json">{
    "bio": "I am learning backend"
}
</code></pre>
<p><code>express.json()</code> parses it so we can access:</p>
<pre><code class="language-js">req.body
</code></pre>
<p>Therefore:</p>
<pre><code class="language-text">JSON request
    ↓
express.json()
    ↓
req.body
</code></pre>
<hr />
<h1>6. Connecting MongoDB</h1>
<pre><code class="language-js">await mongoose.connect(
    'mongodb://127.0.0.1:27017/backendPractice'
);
</code></pre>
<p>Meaning:</p>
<pre><code class="language-text">127.0.0.1 → this computer
27017      → MongoDB port
backendPractice → database name
</code></pre>
<p>A clean startup flow is:</p>
<pre><code class="language-text">startServer()
     ↓
connect MongoDB
     ↓
successful?
   ↙       ↘
 yes       no
 ↓         ↓
listen    error
</code></pre>
<p>So the server starts accepting requests only after the database connection succeeds.</p>
<hr />
<h1>7. <code>.then()</code> and <code>.catch()</code></h1>
<p>For a promise:</p>
<pre><code class="language-js">mongoose.connect(...)
    .then(() =&gt; {
        console.log('Connected');
    })
    .catch((error) =&gt; {
        console.log(error);
    });
</code></pre>
<p>Think:</p>
<pre><code class="language-text">Try operation
   ↓
 ┌───────┴───────┐
success         failure
   ↓               ↓
 then            catch
</code></pre>
<hr />
<h1>8. Middleware</h1>
<p>Middleware sits between the request and the controller.</p>
<pre><code class="language-text">REQUEST
   ↓
middleware
   ↓
controller
   ↓
response
</code></pre>
<p>It receives:</p>
<pre><code class="language-js">(req, res, next)
</code></pre>
<ul>
<li><p><code>req</code> → incoming request</p>
</li>
<li><p><code>res</code> → response</p>
</li>
<li><p><code>next</code> → continue to the next step</p>
</li>
</ul>
<p>Middleware can:</p>
<ul>
<li><p>authenticate</p>
</li>
<li><p>validate</p>
</li>
<li><p>log</p>
</li>
<li><p>modify <code>req</code></p>
</li>
<li><p>block requests</p>
</li>
</ul>
<hr />
<h1>9. <code>req.params</code>, <code>req.body</code>, <code>req.headers</code></h1>
<p>These are three important sources of request information.</p>
<h3><code>req.params</code></h3>
<p>For:</p>
<pre><code class="language-js">app.get('/generate-token/:userId', ...)
</code></pre>
<p>Request:</p>
<pre><code class="language-text">/generate-token/ABC123
</code></pre>
<p>Then:</p>
<pre><code class="language-js">req.params.userId
</code></pre>
<p>is:</p>
<pre><code class="language-text">ABC123
</code></pre>
<p>Used for values inside the URL.</p>
<h3><code>req.body</code></h3>
<p>For:</p>
<pre><code class="language-json">{
    "bio": "Hello"
}
</code></pre>
<p>we get:</p>
<pre><code class="language-js">req.body.bio
</code></pre>
<p>Used for data sent inside the request body.</p>
<h3><code>req.headers</code></h3>
<p>For:</p>
<pre><code class="language-text">Authorization: Bearer XYZ...
</code></pre>
<p>we can access:</p>
<pre><code class="language-js">req.headers.authorization
</code></pre>
<p>Used for request metadata such as authentication information.</p>
<hr />
<h1>10. Getting the JWT</h1>
<pre><code class="language-js">const token =
    req.headers.authorization?.split(' ')[1];
</code></pre>
<p>Suppose:</p>
<pre><code class="language-text">Bearer eyJhb...
</code></pre>
<p>After:</p>
<pre><code class="language-js">.split(' ')
</code></pre>
<p>we get:</p>
<pre><code class="language-js">["Bearer", "eyJhb..."]
</code></pre>
<p><code>[1]</code> gives:</p>
<pre><code class="language-text">eyJhb...
</code></pre>
<p>So the flow is:</p>
<pre><code class="language-text">Authorization header
       ↓
"Bearer TOKEN"
       ↓
split(' ')
       ↓
["Bearer", "TOKEN"]
       ↓
[1]
       ↓
TOKEN
</code></pre>
<hr />
<h1>11. Optional Chaining <code>?.</code></h1>
<p>Without:</p>
<pre><code class="language-js">authorization.split(' ')
</code></pre>
<p>if <code>authorization</code> doesn't exist, JavaScript can crash.</p>
<p>With:</p>
<pre><code class="language-js">authorization?.split(' ')
</code></pre>
<p>JavaScript says:</p>
<blockquote>
<p>If it exists, continue. Otherwise give <code>undefined</code>.</p>
</blockquote>
<p>Use <code>?.</code> when something <strong>may legitimately be missing</strong>.</p>
<p>It is different from <code>try/catch</code>:</p>
<pre><code class="language-text">?.         → safely access missing value
try/catch → handle an operation that throws
</code></pre>
<hr />
<h1>12. Authentication Middleware</h1>
<p>A simple authentication flow:</p>
<pre><code class="language-js">const auth = (req, res, next) =&gt; {

    const token =
        req.headers.authorization?.split(' ')[1];

    if (!token) {
        return res.status(401).json({
            message: 'No token provided'
        });
    }

    try {
        const decoded = jwt.verify(
            token,
            'mysecretkey'
        );

        req.user = decoded;

        next();

    } catch (error) {
        return res.status(401).json({
            message: 'Invalid token'
        });
    }
};
</code></pre>
<p>The mental model:</p>
<pre><code class="language-text">Request
   ↓
Get token
   ↓
Token exists?
   ↓
Verify JWT
   ↓
Valid?
 ↙     ↘
NO      YES
↓        ↓
401    req.user
         ↓
       next()
</code></pre>
<hr />
<h1>13. Why <code>req.user = decoded</code>?</h1>
<p>Suppose the JWT contains:</p>
<pre><code class="language-js">{
    userId: "12345"
}
</code></pre>
<p>After verification:</p>
<pre><code class="language-js">req.user = decoded;
</code></pre>
<p>Now later code can use:</p>
<pre><code class="language-js">req.user.userId
</code></pre>
<p>The flow is:</p>
<pre><code class="language-text">JWT
 ↓
verify
 ↓
decoded data
 ↓
req.user
 ↓
controller
</code></pre>
<hr />
<h1>14. Why <code>next()</code>?</h1>
<p><code>next()</code> means:</p>
<blockquote>
<p><strong>Authentication succeeded. Continue the request pipeline.</strong></p>
</blockquote>
<p>If authentication fails:</p>
<pre><code class="language-js">return res.status(401).json(...)
</code></pre>
<p>The request stops.</p>
<p>If authentication succeeds:</p>
<pre><code class="language-js">next();
</code></pre>
<p>The request continues.</p>
<p>Think of middleware as a security checkpoint:</p>
<pre><code class="language-text">Request
   ↓
Security check
   ↓
 ┌───────┴───────┐
fail            success
 ↓                 ↓
STOP              next()
</code></pre>
<hr />
<h1>15. Helper Functions</h1>
<p>Example:</p>
<pre><code class="language-js">const formatClient = (client) =&gt; {
    return {
        savedLawyers: client.savedLawyers,
        blockedLawyers: client.blockedLawyers,
        bio: client.bio
    };
};
</code></pre>
<p>A helper takes existing data and creates the format we want to send.</p>
<pre><code class="language-text">MongoDB document
      ↓
formatClient()
      ↓
clean response
</code></pre>
<p>This prevents us from sending unnecessary fields.</p>
<hr />
<h1>16. Controller</h1>
<p>Example:</p>
<pre><code class="language-js">app.get('/api/client/profile', auth, async (req, res) =&gt; {
    ...
});
</code></pre>
<p>There are three parts:</p>
<pre><code class="language-text">/api/client/profile → URL
auth                → middleware
async (req, res)    → controller logic
</code></pre>
<p>The controller answers:</p>
<blockquote>
<p><strong>What should happen when this endpoint is called?</strong></p>
</blockquote>
<p>For a profile request:</p>
<pre><code class="language-text">Get user ID
    ↓
Find User
    ↓
Find Client
    ↓
Format data
    ↓
Send response
</code></pre>
<hr />
<h1>17. Finding User and Client</h1>
<pre><code class="language-js">const id = req.user.userId;

const user = await User.findOne({
    _id: id
});

const client = await Client.findOne({
    userId: id
});
</code></pre>
<p>The User has:</p>
<pre><code class="language-text">_id = 12345
</code></pre>
<p>The Client has:</p>
<pre><code class="language-text">userId = 12345
</code></pre>
<p>So they are connected:</p>
<pre><code class="language-text">USER
_id = 12345
    ↑
    │
CLIENT
userId = 12345
</code></pre>
<p>If either doesn't exist:</p>
<pre><code class="language-js">if (!user) {
    return res.status(404).json({
        message: 'User not found'
    });
}
</code></pre>
<hr />
<h1>18. Sending Response</h1>
<pre><code class="language-js">return res.status(200).json({
    userProfile: formatUser(user),
    clientProfile: formatClient(client)
});
</code></pre>
<p><code>200</code> means the request succeeded.</p>
<p>The response is sent back to Postman/frontend.</p>
<hr />
<h1>19. <code>try/catch</code> in Controllers</h1>
<p>Database operations can throw errors:</p>
<pre><code class="language-js">try {
    const user = await User.findOne(...);
}
catch (error) {
    res.status(500).json({
        message: 'Server error'
    });
}
</code></pre>
<p>Think:</p>
<pre><code class="language-text">Expected problem
     ↓
if check
     ↓
401 / 404 / etc.

Unexpected thrown error
     ↓
try/catch
     ↓
500
</code></pre>
<hr />
<h1>20. Updating Profile</h1>
<pre><code class="language-js">app.put('/api/client/profile', auth, async (req, res) =&gt; {
    ...
});
</code></pre>
<p>Remember:</p>
<pre><code class="language-text">GET → retrieve
PUT → update
</code></pre>
<p>Example:</p>
<pre><code class="language-js">const client = await Client.findOneAndUpdate(
    { userId: id },
    { bio: req.body.bio },
    { new: true }
);
</code></pre>
<p>Three objects:</p>
<h3>1. What to find</h3>
<pre><code class="language-js">{ userId: id }
</code></pre>
<h3>2. What to change</h3>
<pre><code class="language-js">{ bio: req.body.bio }
</code></pre>
<h3>3. What to return</h3>
<pre><code class="language-js">{ new: true }
</code></pre>
<p><code>new: true</code> means:</p>
<blockquote>
<p>Return the updated document instead of the old one.</p>
</blockquote>
<hr />
<h1>21. JWT Creation</h1>
<pre><code class="language-js">const token = jwt.sign(
    { userId: req.params.userId },
    'mysecretkey',
    { expiresIn: '1h' }
);
</code></pre>
<p>This creates a JWT containing:</p>
<pre><code class="language-js">{
    userId: "ABC123"
}
</code></pre>
<p>with:</p>
<pre><code class="language-text">secret → mysecretkey
expiry → 1 hour
</code></pre>
<p>Then:</p>
<pre><code class="language-js">res.json({
    token: token
});
</code></pre>
<p>sends it to the client.</p>
<p>The client can later send:</p>
<pre><code class="language-text">Authorization: Bearer TOKEN
</code></pre>
<hr />
<h1>22. Middleware vs Controller</h1>
<p>This is the important distinction.</p>
<p>Ask:</p>
<blockquote>
<p><strong>Is this checking/preparing something before the actual operation?</strong></p>
</blockquote>
<p>If yes → <strong>middleware</strong></p>
<p>Ask:</p>
<blockquote>
<p><strong>Is this actually performing what the endpoint was requested to do?</strong></p>
</blockquote>
<p>If yes → <strong>controller</strong></p>
<h3>Middleware</h3>
<pre><code class="language-text">Check authentication
Check permissions
Validate
Prepare request
   ↓
next()
</code></pre>
<h3>Controller</h3>
<pre><code class="language-text">Perform endpoint's job
   ↓
Database
   ↓
Business logic
   ↓
Response
</code></pre>
<p>For example:</p>
<pre><code class="language-text">GET /profile
     ↓
    auth          ← middleware
     ↓
getProfile        ← controller
     ↓
 MongoDB
     ↓
 response
</code></pre>
<hr />
<h1>23. Complete Mental Model</h1>
<p>Whenever you read Express code, ask:</p>
<pre><code class="language-text">1. Where did the information come from?
   ↓
params / body / query / headers

2. What needs to be checked first?
   ↓
middleware

3. What is this endpoint supposed to do?
   ↓
controller

4. Where does the data come from/go?
   ↓
database

5. What do we send back?
   ↓
response
</code></pre>
<p>The whole backend can therefore be understood as:</p>
<pre><code class="language-text">REQUEST
   ↓
Where is the data?
(params/body/headers)
   ↓
MIDDLEWARE
(check/prepare/block)
   ↓
CONTROLLER
(actual endpoint work)
   ↓
DATABASE
   ↓
RESPONSE
</code></pre>
<p><strong>Express backend code is essentially a request entering the system, passing through checkpoints, performing an operation, talking to the database, and finally returning a response.</strong></p>
<h1><strong>HOW TO STRUCTURE THE SAME FOR A LARGE PROJECT</strong></h1>
<pre><code class="language-text">server/
│
├── server.js
├── package.json
├── .env
│
├── config/
│   ├── db.js
│   └── env.js
│
├── models/
│   ├── User.js
│   └── Client.js
│
├── controllers/
│   ├── auth.controller.js
│   └── client.controller.js
│
├── routes/
│   ├── auth.routes.js
│   └── client.routes.js
│
├── middleware/
│   ├── auth.middleware.js
│   ├── error.middleware.js
│   └── validation.middleware.js
│
├── services/
│   ├── auth.service.js
│   └── client.service.js
│
├── utils/
│   ├── jwt.js
│   ├── formatters.js
│   └── asyncHandler.js
│
└── validators/
    ├── auth.validator.js
    └── client.validator.js
</code></pre>
<h3><code>server.js</code></h3>
<pre><code class="language-js">const express = require('express');

const connectDB = require('./config/db');

const authRoutes = require('./routes/auth.routes');
const clientRoutes = require('./routes/client.routes');

const errorHandler = require('./middleware/error.middleware');

const app = express();

app.use(express.json());

connectDB();

app.use('/api/auth', authRoutes);
app.use('/api/client', clientRoutes);

app.get('/', (req, res) =&gt; {
    res.json({
        message: 'Server is running'
    });
});

app.use(errorHandler);

app.listen(5000, () =&gt; {
    console.log('Server running on http://localhost:5000');
});
</code></pre>
<h3><code>config/db.js</code></h3>
<pre><code class="language-js">const mongoose = require('mongoose');

const connectDB = async () =&gt; {
    try {
        await mongoose.connect(
            'mongodb://127.0.0.1:27017/backendPractice'
        );

        console.log('MongoDB connected');
    } catch (error) {
        console.log('MongoDB connection failed');
        console.log(error);
    }
};

module.exports = connectDB;
</code></pre>
<h3><code>models/User.js</code></h3>
<pre><code class="language-js">const mongoose = require('mongoose');

const userSchema = new mongoose.Schema({
    name: String,
    email: String,
    phone: String,
    role: String,
    isEmailVerified: Boolean,
    isPhoneVerified: Boolean,
    isActive: Boolean
});

module.exports = mongoose.model('User', userSchema);
</code></pre>
<h3><code>models/Client.js</code></h3>
<pre><code class="language-js">const mongoose = require('mongoose');

const clientSchema = new mongoose.Schema({
    userId: {
        type: mongoose.Schema.Types.ObjectId,
        ref: 'User'
    },

    savedLawyers: [
        {
            type: mongoose.Schema.Types.ObjectId
        }
    ],

    blockedLawyers: [
        {
            type: mongoose.Schema.Types.ObjectId
        }
    ],

    bio: String
});

module.exports = mongoose.model('Client', clientSchema);
</code></pre>
<h3><code>middleware/auth.middleware.js</code></h3>
<pre><code class="language-js">const jwt = require('jsonwebtoken');

const auth = (req, res, next) =&gt; {
    const token = req.headers.authorization?.split(' ')[1];

    if (!token) {
        return res.status(401).json({
            message: 'No token provided'
        });
    }

    try {
        const decoded = jwt.verify(
            token,
            'mysecretkey'
        );

        req.user = decoded;

        next();

    } catch (error) {
        return res.status(401).json({
            message: 'Invalid token'
        });
    }
};

module.exports = auth;
</code></pre>
<h3><code>middleware/error.middleware.js</code></h3>
<pre><code class="language-js">const errorHandler = (error, req, res, next) =&gt; {
    console.log(error);

    res.status(500).json({
        message: 'Server error'
    });
};

module.exports = errorHandler;
</code></pre>
<h3><code>utils/formatters.js</code></h3>
<pre><code class="language-js">const formatClient = (client) =&gt; {
    return {
        savedLawyers: client.savedLawyers,
        blockedLawyers: client.blockedLawyers,
        bio: client.bio
    };
};

const formatUser = (user) =&gt; {
    return {
        name: user.name,
        email: user.email,
        phone: user.phone,
        role: user.role,
        isEmailVerified: user.isEmailVerified,
        isPhoneVerified: user.isPhoneVerified,
        isActive: user.isActive
    };
};

module.exports = {
    formatClient,
    formatUser
};
</code></pre>
<h3><code>utils/jwt.js</code></h3>
<pre><code class="language-js">const jwt = require('jsonwebtoken');

const generateToken = (userId) =&gt; {
    return jwt.sign(
        {
            userId: userId
        },
        'mysecretkey',
        {
            expiresIn: '1h'
        }
    );
};

module.exports = {
    generateToken
};
</code></pre>
<h3><code>utils/asyncHandler.js</code></h3>
<pre><code class="language-js">const asyncHandler = (fn) =&gt; {
    return (req, res, next) =&gt; {
        Promise
            .resolve(fn(req, res, next))
            .catch(next);
    };
};

module.exports = asyncHandler;
</code></pre>
<h3><code>controllers/client.controller.js</code></h3>
<pre><code class="language-js">const User = require('../models/User');
const Client = require('../models/Client');

const {
    formatClient,
    formatUser
} = require('../utils/formatters');

const asyncHandler = require('../utils/asyncHandler');

const getMyProfile = asyncHandler(async (req, res) =&gt; {

    const id = req.user.userId;

    const user = await User.findOne({
        _id: id
    });

    const client = await Client.findOne({
        userId: id
    });

    if (!user) {
        return res.status(404).json({
            message: 'User not found'
        });
    }

    if (!client) {
        return res.status(404).json({
            message: 'Client not found'
        });
    }

    const formattedUser = formatUser(user);
    const formattedClient = formatClient(client);

    return res.status(200).json({
        userProfile: formattedUser,
        clientProfile: formattedClient
    });
});

const updateProfile = asyncHandler(async (req, res) =&gt; {

    const id = req.user.userId;

    const client = await Client.findOneAndUpdate(
        {
            userId: id
        },
        {
            bio: req.body.bio
        },
        {
            new: true
        }
    );

    if (!client) {
        return res.status(404).json({
            message: 'Client not found'
        });
    }

    return res.status(200).json({
        message: 'Profile updated successfully',
        clientProfile: formatClient(client)
    });
});

module.exports = {
    getMyProfile,
    updateProfile
};
</code></pre>
<h3><code>controllers/auth.controller.js</code></h3>
<pre><code class="language-js">const User = require('../models/User');
const Client = require('../models/Client');

const {
    generateToken
} = require('../utils/jwt');

const createTestUser = async (req, res) =&gt; {

    const user = await User.create({
        name: 'Jyoti',
        email: 'jyoti@test.com',
        phone: '9999999999',
        role: 'client',
        isEmailVerified: true,
        isPhoneVerified: true,
        isActive: true
    });

    const client = await Client.create({
        userId: user._id,
        savedLawyers: [],
        blockedLawyers: [],
        bio: 'I am learning backend development.'
    });

    res.json({
        message: 'Test user created',
        userId: user._id,
        user,
        client
    });
};

const getToken = (req, res) =&gt; {

    const token = generateToken(
        req.params.userId
    );

    res.json({
        token
    });
};

module.exports = {
    createTestUser,
    getToken
};
</code></pre>
<h3><code>routes/client.routes.js</code></h3>
<pre><code class="language-js">const express = require('express');

const auth = require('../middleware/auth.middleware');

const {
    getMyProfile,
    updateProfile
} = require('../controllers/client.controller');

const router = express.Router();

router.get(
    '/profile',
    auth,
    getMyProfile
);

router.put(
    '/profile',
    auth,
    updateProfile
);

module.exports = router;
</code></pre>
<h3><code>routes/auth.routes.js</code></h3>
<pre><code class="language-js">const express = require('express');

const {
    createTestUser,
    getToken
} = require('../controllers/auth.controller');

const router = express.Router();

router.get(
    '/create-test-user',
    createTestUser
);

router.get(
    '/generate-token/:userId',
    getToken
);

module.exports = router;
</code></pre>
<h3>Final structure</h3>
<pre><code class="language-text">server/
│
├── server.js
├── package.json
├── .env
│
├── config/
│   └── db.js
│
├── models/
│   ├── User.js
│   └── Client.js
│
├── controllers/
│   ├── auth.controller.js
│   └── client.controller.js
│
├── routes/
│   ├── auth.routes.js
│   └── client.routes.js
│
├── middleware/
│   ├── auth.middleware.js
│   └── error.middleware.js
│
└── utils/
    ├── jwt.js
    ├── formatters.js
    └── asyncHandler.js
</code></pre>
<p>The basic role of each file was already explained. The important next step is understanding <strong>how the files communicate</strong> and <strong>why large projects separate responsibilities</strong>.</p>
<hr />
<h2>1. How files communicate</h2>
<p>Node files communicate mainly through:</p>
<pre><code class="language-js">module.exports
</code></pre>
<p>and:</p>
<pre><code class="language-js">require()
</code></pre>
<p>For example:</p>
<pre><code class="language-js">// utils/jwt.js
module.exports = {
    generateToken
};
</code></pre>
<p>Another file can use it:</p>
<pre><code class="language-js">const { generateToken } = require('../utils/jwt');
</code></pre>
<p>Think:</p>
<pre><code class="language-text">jwt.js
   │
   │ exports
   ▼
generateToken
   │
   │ require
   ▼
auth.controller.js
</code></pre>
<h3><code>./</code> vs <code>../</code></h3>
<pre><code class="language-js">require('./file')
</code></pre>
<p>→ current folder</p>
<pre><code class="language-js">require('../file')
</code></pre>
<p>→ go one folder up first</p>
<hr />
<h1>2. <code>express.Router()</code></h1>
<p>Putting every route inside <code>server.js</code> becomes messy:</p>
<pre><code class="language-js">app.get(...)
app.post(...)
app.put(...)
app.delete(...)
</code></pre>
<p>So we create separate routers:</p>
<pre><code class="language-js">const router = express.Router();
</code></pre>
<p>For example:</p>
<pre><code class="language-text">routes/
├── auth.routes.js
├── client.routes.js
├── lawyer.routes.js
└── admin.routes.js
</code></pre>
<p>Each router manages one group of endpoints.</p>
<hr />
<h1>3. How route paths combine</h1>
<p>In <code>server.js</code>:</p>
<pre><code class="language-js">app.use('/api/client', clientRoutes);
</code></pre>
<p>In <code>client.routes.js</code>:</p>
<pre><code class="language-js">router.get('/profile', auth, getMyProfile);
</code></pre>
<p>Express combines them:</p>
<pre><code class="language-text">/api/client
      +
/profile
      =
/api/client/profile
</code></pre>
<p>So the actual request is:</p>
<pre><code class="language-text">GET /api/client/profile
</code></pre>
<p>This lets <code>server.js</code> handle the <strong>main prefixes</strong>, while route files handle the individual endpoints.</p>
<hr />
<h1>4. Why controllers are passed without <code>()</code></h1>
<p>Route:</p>
<pre><code class="language-js">router.get('/profile', auth, getMyProfile);
</code></pre>
<p>We pass:</p>
<pre><code class="language-js">getMyProfile
</code></pre>
<p>not:</p>
<pre><code class="language-js">getMyProfile()
</code></pre>
<p>Because:</p>
<pre><code class="language-js">getMyProfile
</code></pre>
<p>means:</p>
<blockquote>
<p>"Here is the function. Run it when the request arrives."</p>
</blockquote>
<p>Whereas:</p>
<pre><code class="language-js">getMyProfile()
</code></pre>
<p>means:</p>
<blockquote>
<p>"Run it right now."</p>
</blockquote>
<hr />
<h1>5. Middleware and controllers form a pipeline</h1>
<p>For:</p>
<pre><code class="language-text">GET /api/client/profile
</code></pre>
<p>the flow is:</p>
<pre><code class="language-text">Request
   ↓
server.js
   ↓
client router
   ↓
auth middleware
   ↓
next()
   ↓
getMyProfile controller
   ↓
User/Client model
   ↓
MongoDB
   ↓
res.json()
   ↓
Response
</code></pre>
<p>The files are separate, but the <strong>request is still one continuous pipeline</strong>.</p>
<hr />
<h1>6. <code>asyncHandler</code></h1>
<p>Without <code>asyncHandler</code>, every async controller may need:</p>
<pre><code class="language-js">try {
    ...
}
catch(error) {
    next(error);
}
</code></pre>
<p>That becomes repetitive across hundreds of controllers.</p>
<p>So we can create:</p>
<pre><code class="language-js">const asyncHandler = (fn) =&gt; {
    return (req, res, next) =&gt; {
        Promise
            .resolve(fn(req, res, next))
            .catch(next);
    };
};
</code></pre>
<p>Then:</p>
<pre><code class="language-js">const getMyProfile = asyncHandler(async (req, res) =&gt; {
    const user = await User.findOne(...);
});
</code></pre>
<p>Conceptually:</p>
<pre><code class="language-text">Controller
    ↓
asyncHandler
    ↓
success → continue
error   → next(error)
</code></pre>
<hr />
<h1>7. Error middleware</h1>
<p>Normal middleware:</p>
<pre><code class="language-js">(req, res, next)
</code></pre>
<p>Error middleware:</p>
<pre><code class="language-js">(error, req, res, next)
</code></pre>
<p>Example:</p>
<pre><code class="language-js">const errorHandler = (error, req, res, next) =&gt; {
    res.status(500).json({
        message: 'Server error'
    });
};
</code></pre>
<p>Then:</p>
<pre><code class="language-js">app.use(errorHandler);
</code></pre>
<p>So an error can travel:</p>
<pre><code class="language-text">Controller
   ↓
asyncHandler
   ↓
next(error)
   ↓
errorHandler
   ↓
500 response
</code></pre>
<hr />
<h1>8. Route vs Controller</h1>
<p>This distinction is extremely important.</p>
<h3>Route asks:</h3>
<blockquote>
<p><strong>Which request should trigger something?</strong></p>
</blockquote>
<pre><code class="language-js">router.get('/profile', auth, getMyProfile);
</code></pre>
<h3>Controller asks:</h3>
<blockquote>
<p><strong>What should happen?</strong></p>
</blockquote>
<pre><code class="language-js">const getMyProfile = asyncHandler(async (req, res) =&gt; {
    // actual request logic
});
</code></pre>
<p>Think:</p>
<pre><code class="language-text">ROUTE       → WHERE?
CONTROLLER  → WHAT?
</code></pre>
<hr />
<h1>9. Why do we need services?</h1>
<p>A controller can become huge if it contains:</p>
<pre><code class="language-text">database logic
business rules
calculations
permissions
notifications
formatting
...
</code></pre>
<p>So complicated reusable <strong>business logic</strong> can move into a service.</p>
<pre><code class="language-text">Route
  ↓
Controller
  ↓
Service
  ↓
Model
  ↓
Database
</code></pre>
<p>Example:</p>
<pre><code class="language-js">// client.service.js

const getClientProfile = async (userId) =&gt; {
    const user = await User.findOne({ _id: userId });
    const client = await Client.findOne({ userId });

    return { user, client };
};
</code></pre>
<p>Then the controller stays simple:</p>
<pre><code class="language-js">const getMyProfile = asyncHandler(async (req, res) =&gt; {
    const data = await getClientProfile(req.user.userId);

    res.json(data);
});
</code></pre>
<p>The service is especially useful when the same logic is needed by multiple controllers.</p>
<hr />
<h1>10. <code>.env</code></h1>
<p>Instead of hardcoding:</p>
<pre><code class="language-js">'mongodb://...'
'mysecretkey'
</code></pre>
<p>we can store configuration in:</p>
<pre><code class="language-text">.env
</code></pre>
<p>Example:</p>
<pre><code class="language-env">PORT=5000
MONGO_URI=...
JWT_SECRET=...
</code></pre>
<p>Then:</p>
<pre><code class="language-js">process.env.MONGO_URI
process.env.JWT_SECRET
</code></pre>
<p>This allows the same code to work with different environments such as:</p>
<pre><code class="language-text">development
testing
production
</code></pre>
<p>without changing the JavaScript code.</p>
<hr />
<h1>11. Model vs Database</h1>
<p>This is another important distinction.</p>
<pre><code class="language-js">const User = require('../models/User');
</code></pre>
<p><code>User</code> is a <strong>Mongoose model</strong>, not the database itself.</p>
<p>The flow is:</p>
<pre><code class="language-text">Controller
    ↓
User model
    ↓
Mongoose
    ↓
MongoDB
</code></pre>
<p>For example:</p>
<pre><code class="language-js">User.findOne(...)
</code></pre>
<p>means:</p>
<blockquote>
<p>Ask the User model to perform a database operation.</p>
</blockquote>
<hr />
<h1>12. Complete architecture</h1>
<p>A large Express backend can therefore look like:</p>
<pre><code class="language-text">CLIENT
   ↓
SERVER
   ↓
ROUTES
   ↓
MIDDLEWARE
   ↓
CONTROLLER
   ↓
SERVICE
   ↓
MODEL
   ↓
MONGOOSE
   ↓
DATABASE
</code></pre>
<p>Errors have their own path:</p>
<pre><code class="language-text">Any layer
   ↓
next(error)
   ↓
Error middleware
   ↓
Response
</code></pre>
<hr />
<h1>13. The mental model to remember</h1>
<p>Don't memorize the folder names.</p>
<p>Ask these questions:</p>
<pre><code class="language-text">Which URL?
     ↓
ROUTE

Does something need checking first?
     ↓
MIDDLEWARE

What should happen?
     ↓
CONTROLLER

Is there reusable/complex business logic?
     ↓
SERVICE

How do we access stored data?
     ↓
MODEL

Where is the data actually stored?
     ↓
DATABASE
</code></pre>
<h3>The core idea</h3>
<p>A large project doesn't separate files because the request itself is different.</p>
<p>It separates them because <strong>different responsibilities are easier to manage when they live in different places</strong>.</p>
<p>So instead of:</p>
<pre><code class="language-text">10,000 lines → one server.js
</code></pre>
<p>we get:</p>
<pre><code class="language-text">10,000 lines
      ↓
many small responsibilities
      ↓
many focused files
      ↓
one connected request pipeline
</code></pre>
<p>That is what makes a large backend manageable.</p>
]]></content:encoded></item><item><title><![CDATA[Python Fundamentals]]></title><description><![CDATA[When you write x = 5, Python does two things: it creates an integer object 5 somewhere in memory, and it creates a name x that points at it. If you then…]]></description><link>https://publiclawyercontrollerlearnings.hashnode.dev/python-fundamentals</link><guid isPermaLink="true">https://publiclawyercontrollerlearnings.hashnode.dev/python-fundamentals</guid><dc:creator><![CDATA[Jyoti Tiwari]]></dc:creator><pubDate>Wed, 09 Sep 2026 09:58:34 GMT</pubDate><content:encoded><![CDATA[<hr />
<h2>Part 1: Variables and Memory</h2>
<h3>How Python actually stores things</h3>
<p>When you write <code>x = 5</code>, Python does two things: it creates an integer object <code>5</code> somewhere in memory, and it creates a name <code>x</code> that points at it. If you then write <code>y = x</code>, Python does <strong>not</strong> copy the number — it just makes <code>y</code> point at the same object <code>x</code> already points at.</p>
<pre><code class="language-python">a = [1, 2, 3]
b = a              # b now points at the SAME list object as a

print(id(a))       # some memory address, e.g. 140234099283776
print(id(b))       # the exact same address
print(a is b)      # True — they are literally the same object
</code></pre>
<p>Now watch what happens if you change the list through one name:</p>
<pre><code class="language-python">b.append(4)
print(a)            # [1, 2, 3, 4]
print(b)            # [1, 2, 3, 4]
</code></pre>
<p>Both changed, because there was only ever <strong>one list</strong> — <code>a</code> and <code>b</code> are just two labels on it.</p>
<p>Compare this to making an actual copy:</p>
<pre><code class="language-python">c = a.copy()         # creates a genuinely NEW list with the same contents
c.append(5)
print(a)              # [1, 2, 3, 4]     — unaffected
print(c)              # [1, 2, 3, 4, 5]
print(a is c)          # False — different objects now
</code></pre>
<p><code>==</code> and <code>is</code> ask different questions:</p>
<ul>
<li><p><code>a == c</code> → "do these look the same (same contents)?"</p>
</li>
<li><p><code>a is c</code> → "are these literally the same object in memory?"</p>
</li>
</ul>
<h3>Mutable vs Immutable — the most important distinction in the language</h3>
<p><strong>Immutable</strong> (cannot be changed after creation — any "change" makes a brand new object): <code>int</code>, <code>float</code>, <code>str</code>, <code>bool</code>, <code>tuple</code></p>
<p><strong>Mutable</strong> (can be changed in place, without creating a new object): <code>list</code>, <code>dict</code>, <code>set</code>, and custom class instances by default</p>
<p>Strings feel like they should be changeable, but they are not:</p>
<pre><code class="language-python">s = "hello"
s[0] = "H"          # TypeError: 'str' object does not support item assignment
</code></pre>
<p>Any string "modification" actually builds a new string and reassigns the name to it:</p>
<pre><code class="language-python">s = "hello"
s = s.upper()        # a NEW string "HELLO" is created; s now points to it
                       # the original "hello" string still exists in memory
                       # briefly, until nothing points to it and it's cleaned up
</code></pre>
<h3>Why this matters for function arguments</h3>
<pre><code class="language-python">def try_change_string(text):
    text = text.upper()     # this just repoints the LOCAL name "text"
    return text

original = "hello"
result = try_change_string(original)
print(original)    # "hello"  — the caller's variable never changed
print(result)       # "HELLO" — you have to explicitly return the new value
</code></pre>
<pre><code class="language-python">def try_change_list(items):
    items.append("new")     # this MUTATES the actual list object in place

original = ["a", "b"]
try_change_list(original)
print(original)    # ["a", "b", "new"]  — changed, with no return needed!
</code></pre>
<p>The rule: <strong>reassigning a parameter inside a function never affects the caller's variable. Mutating a mutable object that was passed in does affect the caller, because there's only one object and both the caller and the function are looking at it.</strong></p>
<h3>The classic trap this causes: mutable default arguments</h3>
<pre><code class="language-python">def add_item(item, my_list=[]):    # DANGEROUS
    my_list.append(item)
    return my_list

print(add_item(1))    # [1]
print(add_item(2))    # [1, 2]   &lt;-- surprising! you'd expect [2]
</code></pre>
<p>Why: the empty list <code>[]</code> in the function signature is created <strong>exactly once</strong>, at the moment the function is <em>defined</em> — not each time it's called. Every call that doesn't supply its own list reuses that same original list object, and it keeps growing across calls.</p>
<p>The fix — use <code>None</code> as a sentinel, and create a fresh list inside the function body each call:</p>
<pre><code class="language-python">def add_item(item, my_list=None):
    if my_list is None:
        my_list = []          # fresh list, created fresh on THIS call
    my_list.append(item)
    return my_list
</code></pre>
<h3>Nested mutability — shallow vs deep copies</h3>
<pre><code class="language-python">matrix = [[0, 0], [0, 0]]
shallow = matrix.copy()
shallow[0][0] = 99
print(matrix)     # [[99, 0], [0, 0]]  &lt;- original changed too!
</code></pre>
<p><code>.copy()</code> only copies the <em>outer</em> list. The inner lists are still the same shared objects. To fully separate everything, nested levels included:</p>
<pre><code class="language-python">import copy
deep = copy.deepcopy(matrix)
deep[0][0] = 55
print(matrix)      # unaffected this time
</code></pre>
<p>Dictionaries behave identically:</p>
<pre><code class="language-python">config = {"debug": True, "settings": {"retries": 3}}
shallow_copy = config.copy()

shallow_copy["debug"] = False
print(config["debug"])                      # True — outer level is independent

shallow_copy["settings"]["retries"] = 99
print(config["settings"]["retries"])        # 99 — inner dict is still SHARED
</code></pre>
<hr />
<h2>Part 2: Functions</h2>
<h3>Positional and keyword arguments</h3>
<pre><code class="language-python">def describe(name, age, city="Unknown"):
    print(f"{name}, {age}, from {city}")

describe("Alice", 30)                       # positional
describe(name="Alice", age=30)              # keyword — order irrelevant
describe(age=30, name="Alice")              # same result as above
describe("Alice", city="NYC", age=30)       # mixed — positional args must come first
</code></pre>
<p>You can force some parameters to only be passable by keyword, using a bare <code>*</code>:</p>
<pre><code class="language-python">def create_user(name, *, email, is_admin=False):
    print(name, email, is_admin)

create_user("Bob", email="bob@example.com")   # fine
create_user("Bob", "bob@example.com")           # TypeError — email must be named
</code></pre>
<p>This prevents accidental mix-ups when a function has several parameters of the same type.</p>
<h3>Variable-length arguments: <code>*args</code> and <code>**kwargs</code></h3>
<pre><code class="language-python">def show_args(*args, **kwargs):
    print("positional extras:", args)
    print("keyword extras:", kwargs)

show_args(1, 2, 3, name="Alice", age=30)
# positional extras: (1, 2, 3)                &lt;- collected into a tuple
# keyword extras: {'name': 'Alice', 'age': 30}  &lt;- collected into a dict
</code></pre>
<p>The reverse — unpacking a collection into separate arguments:</p>
<pre><code class="language-python">def add(a, b, c):
    return a + b + c

nums = [1, 2, 3]
print(add(*nums))              # * unpacks the list -&gt; add(1, 2, 3)

kwargs = {"a": 1, "b": 2, "c": 3}
print(add(**kwargs))            # ** unpacks the dict -&gt; add(a=1, b=2, c=3)
</code></pre>
<h3>Scope — how Python decides which variable you mean</h3>
<p>Python looks up names using the order: <strong>Local → Enclosing → Global → Built-in</strong> (often called the LEGB rule).</p>
<pre><code class="language-python">x = "global"

def outer():
    x = "outer"
    def inner():
        print(x)      # not found locally, found in enclosing scope -&gt; "outer"
    inner()
    print(x)           # "outer"

outer()
print(x)                # "global" — never touched
</code></pre>
<p>A common trap — if you assign to a name <strong>anywhere</strong> inside a function, Python treats that name as local for the <em>entire</em> function body, even on lines before the assignment:</p>
<pre><code class="language-python">counter = 0

def broken_increment():
    counter = counter + 1
    # Python sees "counter = ..." in this function and decides
    # counter is LOCAL throughout the whole function.
    # So the "counter" being read on the right side is the
    # not-yet-assigned local one -&gt; UnboundLocalError
</code></pre>
<p>Two ways to fix it:</p>
<pre><code class="language-python">def fixed_with_global():
    global counter
    counter = counter + 1     # explicitly refers to the global variable

def fixed_with_return():
    return counter + 1         # cleaner — no mutation of outer state at all

counter = fixed_with_return()   # caller decides what to do with the new value
</code></pre>
<p>In practice, prefer the <code>return</code>-based style. Reaching into outer scope with <code>global</code> makes code much harder to reason about as programs grow.</p>
<h3>Closures — functions that remember their environment</h3>
<pre><code class="language-python">def make_counter():
    count = 0
    def increment():
        nonlocal count       # like "global" but for the enclosing (not global) scope
        count += 1
        return count
    return increment

counter1 = make_counter()
counter2 = make_counter()

print(counter1())    # 1
print(counter1())    # 2
print(counter1())    # 3
print(counter2())    # 1  &lt;- completely separate state from counter1
</code></pre>
<p>Each call to <code>make_counter()</code> creates a fresh <code>count</code>, and the returned <code>increment</code> function "closes over" (remembers) that specific <code>count</code> forever — even though <code>make_counter()</code> itself already finished running. This is how you get private, persistent state without writing a class.</p>
<hr />
<h2>Part 3: Classes</h2>
<h3>What <code>self</code> really is</h3>
<pre><code class="language-python">class Dog:
    def __init__(self, name):
        self.name = name

    def bark(self):
        return f"{self.name} says woof"

rex = Dog("Rex")
print(rex.bark())         # normal syntax
print(Dog.bark(rex))      # produces the EXACT same result
</code></pre>
<p><code>rex.bark()</code> is just convenient syntax for <code>Dog.bark(rex)</code> — Python automatically passes the object before the dot as the first argument. <code>self</code> is simply the name given to that first parameter; there's no hidden magic.</p>
<h3><code>__init__</code> initializes, it doesn't construct</h3>
<p>The object already exists by the time <code>__init__</code> runs — <code>__init__</code> just fills in its starting attribute values.</p>
<pre><code class="language-python">class Dog:
    species = "Canis familiaris"     # CLASS attribute — one copy, shared by all instances

    def __init__(self, name, age):
        self.name = name              # INSTANCE attribute — unique to each object
        self.age = age
</code></pre>
<p>Think of the class as a template stored once, and each instance as having its own private dictionary of overrides. Accessing <code>rex.species</code> checks <code>rex</code>'s own storage first; not finding it there, Python falls back to the class. Accessing <code>rex.name</code> finds it directly on the instance.</p>
<pre><code class="language-python">rex = Dog("Rex", 3)
buddy = Dog("Buddy", 5)

Dog.species = "Canis lupus familiaris"    # change at the class level
print(rex.species)     # updated — both instances see the change, since neither has its own override

rex.species = "Special"                    # this creates an INSTANCE-level override on rex only
print(rex.species)      # "Special"
print(buddy.species)     # unaffected — still the class-level value
</code></pre>
<h3>The same mutable-default trap, in class form</h3>
<pre><code class="language-python">class ShoppingCart:
    items = []          # DANGER: this is a class attribute, shared by every instance

    def add(self, item):
        self.items.append(item)

cart1 = ShoppingCart()
cart2 = ShoppingCart()
cart1.add("apple")
print(cart2.items)      # ["apple"]  &lt;- leaked in from cart1!
</code></pre>
<p><code>self.items.append(...)</code> doesn't create a new instance attribute — it mutates the one shared list, because no instance-level <code>items</code> was ever separately created. Fix:</p>
<pre><code class="language-python">class ShoppingCart:
    def __init__(self):
        self.items = []        # a fresh, independent list per instance

    def add(self, item):
        self.items.append(item)
</code></pre>
<h3>Dunder methods — how your objects plug into Python's built-in syntax</h3>
<p>None of Python's operators are hardcoded to work on custom objects. <code>+</code>, <code>==</code>, <code>&lt;</code>, <code>len()</code>, printing, truthiness in <code>if</code> — all of these work because your class defines the matching "dunder" (double underscore) method.</p>
<pre><code class="language-python">class Money:
    def __init__(self, amount):
        self.amount = amount

    def __add__(self, other):        # enables: money1 + money2
        return Money(self.amount + other.amount)

    def __eq__(self, other):          # enables: money1 == money2
        return self.amount == other.amount

    def __lt__(self, other):          # enables: money1 &lt; money2
        return self.amount &lt; other.amount

    def __repr__(self):               # controls what print(money) shows
        return f"Money(${self.amount})"

    def __bool__(self):               # controls behavior in "if money:"
        return self.amount != 0

m1 = Money(10)
m2 = Money(20)
print(m1 + m2)            # Money($30)
print(m1 &lt; m2)             # True
print(m1 == Money(10))     # True
if not Money(0):
    print("no money")       # this prints, since __bool__ returns False for Money(0)
</code></pre>
<h3>Three decorators you'll see constantly</h3>
<pre><code class="language-python">class Circle:
    def __init__(self, radius):
        self.radius = radius

    @property
    def area(self):                     # accessed like a plain attribute, no parentheses
        return 3.14159 * self.radius ** 2

    @classmethod
    def from_diameter(cls, diameter):   # alternate way to construct a Circle
        return cls(diameter / 2)

    @staticmethod
    def is_valid_radius(r):             # doesn't need self or cls — just grouped here logically
        return r &gt; 0

c = Circle(5)
print(c.area)                       # 78.53975   -- notice: no parentheses used
c2 = Circle.from_diameter(10)
print(c2.radius)                     # 5.0
print(Circle.is_valid_radius(-1))    # False
</code></pre>
<ul>
<li><p><code>@property</code> — call a method without <code>()</code>, as if it were a plain attribute (useful for computed values)</p>
</li>
<li><p><code>@classmethod</code> — receives the class itself (<code>cls</code>) instead of an instance; typically used for alternate constructors</p>
</li>
<li><p><code>@staticmethod</code> — receives neither <code>self</code> nor <code>cls</code>; it's a plain function grouped inside the class for organization</p>
</li>
</ul>
<h3>Inheritance and <code>super()</code></h3>
<pre><code class="language-python">class Puppy(Dog):
    def __init__(self, name):
        super().__init__(name, age=0)   # runs Dog's __init__ first
        self.is_puppy = True
</code></pre>
<p><code>super()</code> gives you a handle to the parent class, letting you build on top of its behavior instead of rewriting it from scratch.</p>
<hr />
<h2>Part 4: Type Hints</h2>
<p>Type hints look like they should enforce types, but by default, <strong>plain Python ignores them completely at runtime</strong>:</p>
<pre><code class="language-python">def add(a: int, b: int) -&gt; int:
    return a + b

print(add("hello", "world"))    # runs fine, prints "helloworld" — no error!
</code></pre>
<p>So what are they for?</p>
<ul>
<li><p>Editor/IDE autocomplete and inline warnings</p>
</li>
<li><p>Static analysis tools (like <code>mypy</code>) that check your code <em>before</em> running it</p>
</li>
<li><p>Documentation that stays accurate, since it lives in the code itself</p>
</li>
</ul>
<pre><code class="language-python">from typing import Optional, List, Dict, Union

def get_name() -&gt; Optional[str]:     # means: returns a str, OR returns None
    return None

def get_scores() -&gt; List[int]:        # a list of ints
    return [90, 85, 77]

def get_user() -&gt; Dict[str, str]:     # a dict with string keys and string values
    return {"name": "Alice"}

def process(value: Union[int, str]) -&gt; str:   # accepts either type
    return str(value)

# Python 3.10+ shorthand:
def get_name_new() -&gt; str | None:
    return None
</code></pre>
<p>Some tools <strong>do</strong> enforce type hints at runtime — most notably Pydantic, which reads them and validates incoming data against them:</p>
<pre><code class="language-python">from pydantic import BaseModel
from typing import List, Optional

class Address(BaseModel):
    street: str
    city: str

class User(BaseModel):
    name: str
    age: int
    tags: List[str] = []
    address: Optional[Address] = None
</code></pre>
<p>Constructing a <code>User(age="not a number")</code> here would immediately raise a validation error — unlike plain Python functions, which silently let mismatched types through.</p>
<hr />
<h2>Part 5: async/await</h2>
<p>The core idea: <strong>async is about not sitting idle while waiting, not about doing multiple things at the literal same instant.</strong> Only one line of Python code ever executes at a time — there's no multi-core magic here. The win comes purely from not blocking during waits (like network requests or database calls).</p>
<h3>Synchronous version — everything waits in line</h3>
<pre><code class="language-python">import time

def fetch_user():
    time.sleep(2)          # simulates a 2-second network call — freezes the whole program
    return "user data"

def fetch_orders():
    time.sleep(2)
    return "orders data"

start = time.time()
fetch_user()
fetch_orders()
print(f"Took {time.time() - start:.1f}s")    # Took 4.0s
</code></pre>
<h3>Async version — waits overlap</h3>
<pre><code class="language-python">import asyncio
import time

async def fetch_user():
    await asyncio.sleep(2)
    return "user data"

async def fetch_orders():
    await asyncio.sleep(2)
    return "orders data"

async def main():
    start = time.time()
    results = await asyncio.gather(fetch_user(), fetch_orders())
    print(f"Took {time.time() - start:.1f}s")    # Took 2.0s
    print(results)

asyncio.run(main())
</code></pre>
<p>Step by step:</p>
<ol>
<li><p><code>fetch_user()</code> runs until it hits <code>await asyncio.sleep(2)</code>, then <strong>pauses</strong>, handing control back to the event loop (a scheduler managing all this).</p>
</li>
<li><p>The event loop, now free, starts <code>fetch_orders()</code>, which also pauses at its own <code>await</code>.</p>
</li>
<li><p>Both are now "parked" waiting on their timers simultaneously.</p>
</li>
<li><p>After 2 seconds (not 4), both resume, return their values, and <code>gather</code> collects them into a list.</p>
</li>
</ol>
<h3>The most common async bug: forgetting <code>await</code></h3>
<pre><code class="language-python">async def get_data():
    await asyncio.sleep(1)
    return {"status": "ok"}

async def main():
    result = get_data()          # forgot await!
    print(result)                  # &lt;coroutine object get_data at 0x7f...&gt;
    print(result["status"])        # TypeError — this isn't the actual data yet

    result = await get_data()     # correct — actually runs and waits for the result
    print(result["status"])         # "ok"
</code></pre>
<p>Calling an <code>async def</code> function without <code>await</code> doesn't run it — it just hands you back an unexecuted coroutine object.</p>
<h3><code>async for</code> and <code>async with</code></h3>
<pre><code class="language-python">async def list_users(db):
    async with db.client.start_session() as session:   # async context manager
        cursor = db.users.find({})
        async for user in cursor:                        # async iteration
            print(user)
</code></pre>
<p>Same shapes as regular <code>with</code> and <code>for</code>, but built for operations where each step might need to wait on I/O — such as fetching the next result from a database query over the network.</p>
<hr />
<h2>Part 6: Virtual Environments</h2>
<p>By default, Python installs packages into one <strong>global</strong> location shared by everything on your machine. This becomes a problem the moment two different projects need different versions of the same library.</p>
<p>A virtual environment ("venv") is a self-contained, isolated copy of Python's package directory for a single project.</p>
<pre><code class="language-bash">mkdir my_project &amp;&amp; cd my_project

python -m venv venv
# Creates a venv/ folder: its own python, pip, and package storage

which python
# /usr/bin/python3        &lt;- still the SYSTEM python, venv isn't active yet

source venv/bin/activate
# Shell prompt now shows (venv)

which python
# /Users/you/my_project/venv/bin/python     &lt;- now rewired to the venv's copy

pip install fastapi uvicorn
# Installs ONLY inside venv/ — completely invisible to other projects

pip freeze &gt; requirements.txt
# Records exact installed versions:
#   fastapi==0.115.0
#   uvicorn==0.30.1

deactivate
# Prompt returns to normal; "python" points back to the system version
</code></pre>
<p>On another machine (or a deployment server), nobody copies the <code>venv/</code> folder itself — it gets rebuilt from the requirements file:</p>
<pre><code class="language-bash">python -m venv venv
source venv/bin/activate
pip install -r requirements.txt    # reproduces the exact same set of packages
</code></pre>
<p><code>requirements.txt</code> is the one artifact that travels with your project (usually committed to git); <code>venv/</code> itself is added to <code>.gitignore</code>, since it's just a large, easily-regeneratable, machine-specific folder.</p>
]]></content:encoded></item><item><title><![CDATA[authController.js]]></title><description><![CDATA[Think about what a web server actually receives when a request comes in: a URL, some headers (little key-value pairs like Content-Type: application/json)…]]></description><link>https://publiclawyercontrollerlearnings.hashnode.dev/authcontroller-js</link><guid isPermaLink="true">https://publiclawyercontrollerlearnings.hashnode.dev/authcontroller-js</guid><dc:creator><![CDATA[Jyoti Tiwari]]></dc:creator><pubDate>Mon, 07 Sep 2026 10:11:55 GMT</pubDate><content:encoded><![CDATA[<hr />
<h2>0. Why this file exists at all</h2>
<p>Think about what a web server actually receives when a request comes in: a URL, some headers (little key-value pairs like <code>Content-Type: application/json</code>), and maybe a body. That's it. There is no little tag attached to the request that says "this is Priya, and she's logged in." The server has to be <em>convinced</em>.</p>
<p>Every other file in this app — the one that lets lawyers post replies, the one that lets clients book appointments — eventually writes a line like:</p>
<pre><code class="language-js">if (req.user.role === 'lawyer') { ... }
</code></pre>
<p>That line trusts <code>req.user</code> completely. It never asks "but how do we know this is really true?" <strong>This file,</strong> <code>authController.js</code><strong>, is the only place in the entire app responsible for answering that question.</strong> Everywhere else just trusts the answer.</p>
<p>Why keep all of that logic in one file instead of spreading it around? Because if you ever need to change <em>how</em> identity is proven — swap JWTs for session cookies, add two-factor authentication, whatever — you want that to be a change to <em>one</em> file, not a hunt through fifty files that each independently decided to trust something. This idea has a name: <strong>separation of concerns</strong>. Each part of the app has exactly one job, and only one part of the app is allowed to decide "who is this person."</p>
<hr />
<h2>1. The imports</h2>
<pre><code class="language-js">const bcrypt = require('bcryptjs');
const { createHash, randomUUID, timingSafeEqual } = require('crypto');
const jwt = require('jsonwebtoken');
</code></pre>
<p>Before touching what these <em>do</em>, slow down on the mechanics of <code>require</code>. In Node.js, <code>require('some-package')</code> runs that package's code and hands you back whatever it exported — usually one big object bundling together everything that package offers.</p>
<p><strong>Line 1.</strong> <code>require('bcryptjs')</code> returns an object. That whole object gets stored in a variable named <code>bcrypt</code>. Later in the file you'll see <code>bcrypt.hash(...)</code> and <code>bcrypt.compare(...)</code> — those are just methods living on that object, accessed the normal way, with a dot.</p>
<p><strong>Line 2 is the one worth actually stopping on.</strong> <code>require('crypto')</code> is Node's built-in cryptography toolbox, and it returns a genuinely huge object — dozens of functions for hashing, encryption, random number generation, and more. If you wrote:</p>
<pre><code class="language-js">const crypto = require('crypto');
</code></pre>
<p>...you'd get the <em>whole</em> toolbox under the name <code>crypto</code>, and you'd write <code>crypto.createHash(...)</code> every time you needed it. Instead, the file does this:</p>
<pre><code class="language-js">const { createHash, randomUUID, timingSafeEqual } = require('crypto');
</code></pre>
<p>The curly braces are <strong>object destructuring</strong>. Read it as: "Node, give me the whole crypto object, but then immediately reach inside it, pull out just these three named things, and give each one its own standalone variable name, matching what it's called inside the object." It is <em>exactly</em> equivalent to writing all of this:</p>
<pre><code class="language-js">const crypto = require('crypto');
const createHash = crypto.createHash;
const randomUUID = crypto.randomUUID;
const timingSafeEqual = crypto.timingSafeEqual;
</code></pre>
<p>Four lines of manual unpacking, collapsed into one line of shorthand. The practical payoff: from this point in the file onward, you write <code>createHash(...)</code> directly — no <code>crypto.</code> prefix needed, because <code>createHash</code> is now its own independent variable holding that one function.</p>
<p><strong>Line 3.</strong> Same simple case as line 1 — <code>require('jsonwebtoken')</code> returns one object, stored as <code>jwt</code>, giving you <code>jwt.sign(...)</code> (build a token) and <code>jwt.verify(...)</code> (check a token) later in the file.</p>
<p><strong>Why exactly these three from</strong> <code>crypto</code><strong>, and nothing else?</strong> Every single import in this file maps to one specific job later on, with zero waste:</p>
<table>
<thead>
<tr>
<th>Import</th>
<th>Job in this file</th>
</tr>
</thead>
<tbody><tr>
<td><code>bcrypt</code></td>
<td>hashing and checking <em>passwords</em></td>
</tr>
<tr>
<td><code>createHash</code></td>
<td>hashing <em>refresh tokens</em> (a different job from passwords — explained in section 5)</td>
</tr>
<tr>
<td><code>randomUUID</code></td>
<td>generating a unique random ID for each refresh token</td>
</tr>
<tr>
<td><code>timingSafeEqual</code></td>
<td>safely comparing two hashes without leaking timing information</td>
</tr>
<tr>
<td><code>jwt</code></td>
<td>building and checking JSON Web Tokens</td>
</tr>
</tbody></table>
<p>Nothing here is unused. That's a small but real signal, when you're reading someone else's code, that the file was written deliberately rather than copy-pasted from a dozen tutorials.</p>
<p><em>(This block only sets up tools — it has no inputs or outputs of its own to demonstrate. The functions below are what actually use these tools.)</em></p>
<hr />
<h2>2. The constants</h2>
<pre><code class="language-js">const ACCESS_TOKEN_TTL = '15m';
const REFRESH_TOKEN_TTL = '30d';
const MAX_REFRESH_SESSIONS = 10;
</code></pre>
<p>First, the plain English: these three lines just create three named values sitting at the top of the file, available to every function below them. <code>'15m'</code> means "15 minutes," <code>'30d'</code> means "30 days," and <code>10</code> is just the number ten.</p>
<p><strong>Why bother naming them, instead of just typing</strong> <code>'15m'</code> <strong>directly inside the function that needs it, three sections down?</strong> Imagine you <em>didn't</em> name it — you typed <code>'15m'</code> directly inline. Six months later, your team decides access tokens should last 30 minutes instead of 15. You'd now have to go find every single place in the codebase that happens to type the literal string <code>'15m'</code>, and hope you found all of them, and hope none of them meant something unrelated that just happened to also be <code>'15m'</code>. This gets error-prone fast. By instead writing <code>ACCESS_TOKEN_TTL</code> once at the top and referencing that name everywhere else, changing the policy becomes a single-line edit, and every place that uses it picks up the new value automatically, guaranteed. This principle — say a value once, refer to it by name everywhere else — is called <strong>DRY: Don't Repeat Yourself</strong>. Here it's being applied not to <em>logic</em> but to a <em>policy decision</em> (how long should things last), which is just as important to keep in one place.</p>
<p><strong>What actually <em>is</em></strong> <code>'15m'</code><strong>, mechanically?</strong> It isn't a number of seconds you calculate yourself. It's a small duration string that gets handed to the <code>jsonwebtoken</code> library later (in <code>signAccessToken</code>, section 3), and that library has its own tiny internal parser that understands strings like <code>'15m'</code>, <code>'2h'</code>, <code>'30d'</code>, <code>'7 days'</code>, and converts them into a plain count of seconds behind the scenes. You will never see that conversion happen directly in this file — it's hidden inside <code>jwt.sign</code> — but it's worth knowing it's happening, because the <em>next</em> thing it does with that number is the actual mechanism behind "token expiry," which is just:</p>
<blockquote>
<p>take the current time, add the duration, store that future moment as a plain number (seconds since January 1st, 1970 — this is called a "Unix timestamp"), and stamp it into the token as a field named <code>exp</code>.</p>
</blockquote>
<p>Later, checking whether a token has expired is nothing magical — it's just: "is <code>exp</code> (a number) less than <code>Date.now()</code> (another number)?" You'll meet that check being <em>performed</em> automatically inside <code>jwt.verify()</code> in section 10.</p>
<p><strong>Why is</strong> <code>MAX_REFRESH_SESSIONS</code> <strong>set to exactly</strong> <code>10</code><strong>, and why does that connect to MongoDB specifically?</strong> Here's a fact about MongoDB that has nothing to do with this app specifically: every single document stored in MongoDB — no matter what's in it — has a hard ceiling of <strong>16 megabytes</strong>. You will basically never hit that limit with normal fields like a name or an email address. But this app's design stores a list called <code>refreshTokens</code> <em>directly inside</em> each user's document — one entry every time that person logs in, from any device. If nothing ever removed old entries, a user who logged in daily for ten years, across multiple browsers and phones, would have a document that just kept growing, forever, with no upper bound. <code>10</code> is this file's answer to the question "how many devices does one real person plausibly use at once?" — a login limit that also, as a side effect, guarantees this array can never come close to threatening that 16MB ceiling. The section-by-section mechanism that actually <em>enforces</em> this number lives in <code>addRefreshSession</code> (section 7) — this constant is just the policy; that function is the enforcement.</p>
<p><em>(Again, no direct input/output here — these are just labelled values other functions will use.)</em></p>
<hr />
<h2>3. <code>signAccessToken</code> — turning a user into a short-lived proof of identity</h2>
<pre><code class="language-js">function signAccessToken(user) {
  return jwt.sign(
    { userId: user._id, role: user.role },
    process.env.JWT_ACCESS_SECRET,
    { expiresIn: ACCESS_TOKEN_TTL }
  );
}
</code></pre>
<p><strong>What is this function's job, in one sentence?</strong> Given a user object (something already fetched from the database), produce a short string — the "access token" — that this user's browser can hand back on every future request as proof of "I already logged in, here's my receipt."</p>
<p><strong>Reading</strong> <code>jwt.sign</code><strong>'s three arguments slowly:</strong></p>
<ol>
<li><p><strong>The payload</strong> — <code>{ userId: user._id, role: user.role }</code>. This is the actual data you want carried inside the token. Just two fields: which user this is, and what role they have (<code>'client'</code> or <code>'lawyer'</code>).</p>
</li>
<li><p><strong>The secret</strong> — <code>process.env.JWT_ACCESS_SECRET</code>. This is a string pulled from an <strong>environment variable</strong>, not typed directly into the code. Why does that distinction matter? If the secret were typed directly as a string in this file, it would get committed to source control (git) the moment someone ran <code>git push</code> — visible to anyone with repo access, forever, in the history, even if you later remove it. Environment variables live only on the actual running server, configured separately for development, staging, and production, and are never written into any file that gets committed. This secret is the single most important piece of information in this entire authentication system — anyone who obtains it can forge tokens for any user, including an admin.</p>
</li>
<li><p><strong>Options</strong> — <code>{ expiresIn: ACCESS_TOKEN_TTL }</code>. This is where <code>'15m'</code> from section 2 finally gets used, and where it becomes that <code>exp</code> timestamp described a moment ago.</p>
</li>
</ol>
<p><strong>What comes back out</strong> is a JWT: a string made of three parts separated by dots, each part encoded in a text-safe format called base64url — a header, the payload, and a signature.</p>
<p><strong>The single most important security idea in this whole function, and the one that trips up nearly everyone reading JWT code for the first time:</strong> <em>the payload is signed, not encrypted.</em> Signing and encrypting sound similar but do opposite things. Encrypting hides content so only someone with the right key can read it. Signing does <em>not</em> hide anything — it just attaches a tamper-proof seal. Anyone at all — a browser extension sitting on the user's machine, a proxy server somewhere on the network, literally anyone who gets a copy of this string — can decode the middle chunk with nothing more than a plain base64 decoder (no secret required) and read the payload in plain text: <code>{"userId": "64f...", "role": "client", "exp": 1735689600}</code>. The secret's <em>only</em> job is making the third chunk — the signature — impossible to forge. If an attacker tried editing the decoded payload to say <code>"role": "admin"</code> and then re-encoded it, the signature would no longer match what <code>jwt.verify</code> recomputes on the way back in (section 10), and the tampered token would be rejected outright. <strong>The practical rule this teaches you: never put a password, a credit card number, or anything genuinely secret inside a JWT payload.</strong> Think of it as a sealed glass envelope — sealed against tampering, but transparent to anyone who wants to look.</p>
<p><strong>Why include</strong> <code>role</code> <strong>in this particular token, given that principle?</strong> This is a deliberate trade-off, not an oversight. Because <code>role</code> sits right there in the token, some other file's middleware — say, a check like <code>requireRole('lawyer')</code> guarding a lawyer-only page — can make its decision just by reading the token, without needing to ask the database "what is this person's role?" on every single request. That's a real performance win. The cost: if an admin changes someone's role while they're still logged in, their <em>existing</em> token keeps claiming the <em>old</em> role until it naturally expires — which, thanks to <code>ACCESS_TOKEN_TTL</code>, is a maximum of 15 minutes later. That bounded delay is the price paid for not hitting the database on every request.</p>
<h3>Worked example</h3>
<p><strong>Input</strong> (a user document already loaded from MongoDB):</p>
<pre><code class="language-js">const user = {
  _id: "64f1a2b3c4d5e6f7a8b9c0d1",
  role: "lawyer",
  name: "Amara Chen"
};

signAccessToken(user);
</code></pre>
<p><strong>Output</strong> (a single string — line breaks added here only for readability; the real thing is one continuous line):</p>
<pre><code class="language-plaintext">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiI2NGYxYTJiM2M0ZDVlNmY3YThiOWMwZDEiLCJyb2xlIjoibGF3eWVyIiwiaWF0IjoxNzM2MjAwMDAwLCJleHAiOjE3MzYyMDA5MDB9.
Xk3pQz7...(signature bytes)...
</code></pre>
<p>If you took the middle chunk and ran it through any online base64 decoder, you would see, in plain readable text — no secret needed:</p>
<pre><code class="language-json">{ "userId": "64f1a2b3c4d5e6f7a8b9c0d1", "role": "lawyer", "iat": 1736200000, "exp": 1736200900 }
</code></pre>
<p><code>exp</code> here is <code>1736200900</code> — 900 seconds (15 minutes) after <code>iat</code> (the "issued at" timestamp), which is exactly what <code>ACCESS_TOKEN_TTL = '15m'</code> produced.</p>
<hr />
<h2>4. <code>signRefreshToken</code> — a longer-lived, lower-power proof</h2>
<pre><code class="language-js">function signRefreshToken(user) {
  return jwt.sign(
    { userId: user._id },
    process.env.JWT_REFRESH_SECRET,
    { expiresIn: REFRESH_TOKEN_TTL, jwtid: randomUUID() }
  );
}
</code></pre>
<p>This function looks almost identical to the last one — same three arguments to <code>jwt.sign</code> — which is exactly why it's worth reading it <em>against</em> <code>signAccessToken</code>, difference by difference, because every single difference here was a deliberate decision.</p>
<p><strong>Difference 1 — the payload only has</strong> <code>userId</code><strong>, no</strong> <code>role</code><strong>.</strong> Think about what a refresh token is <em>for</em>: its one and only job, later, is to be traded in for a brand-new access token (you'll see this in section 10). It should never, by itself, be usable to make an authorization decision like "is this person allowed to see this lawyer-only page?" Giving it the absolute minimum information it needs to do its one job, and nothing more, is called the <strong>principle of least privilege</strong> — a token that leaks should leak as little useful information as possible.</p>
<p><strong>Difference 2 — a completely different secret,</strong> <code>JWT_REFRESH_SECRET</code><strong>, not</strong> <code>JWT_ACCESS_SECRET</code><strong>.</strong> Why maintain two separate secrets instead of reusing one everywhere? Picture the worst case: somehow, one of these two secrets leaks (a misconfigured server, a leaked <code>.env</code> file, whatever). If both token types used the same secret, an attacker who obtained it could now forge <em>both</em> access tokens and refresh tokens for any user. With two separate secrets, a leak of one only compromises that one token type — the blast radius is cut in half. This is called <strong>token type separation</strong>.</p>
<p><strong>Difference 3 — the expiry is</strong> <code>'30d'</code><strong>, not</strong> <code>'15m'</code><strong>.</strong> This is the whole reason refresh tokens exist at all: nobody wants to type their password again every 15 minutes. The access token is short-lived on purpose (so a stolen one goes stale fast); the refresh token is long-lived on purpose (so a real user stays logged in for weeks without re-entering credentials), and it's used sparingly — only to mint new access tokens, not on every single request.</p>
<p><strong>Difference 4 — a brand new option that didn't exist before:</strong> <code>jwtid: randomUUID()</code><strong>.</strong> This one deserves the slowest read of the whole function.</p>
<p><code>jwtid</code> sets a field inside the token called <code>jti</code> (JWT ID) — a random, unique identifier baked into <em>this one specific act of signing a token</em>. <code>randomUUID()</code> comes from Node's built-in <code>crypto</code> module and produces a string that looks like <code>f47ac10b-58cc-4372-a567-0e02b2c3d479</code> — a <strong>cryptographically random</strong> value, meaning it isn't just "unlikely to repeat," it's astronomically unlikely to ever repeat, by design.</p>
<p><strong>Why is this necessary at all?</strong> Picture a genuinely realistic scenario: one user logs into the app from their laptop and their phone within the same second — not an attack, just an ordinary person doing an ordinary thing. Both logins call <code>signRefreshToken(user)</code> with the <em>same</em> <code>user</code> object, meaning the exact same payload (<code>{ userId: ... }</code>), the exact same secret, and possibly the exact same second for <code>iat</code>/<code>exp</code>. Without anything else varying between them, it becomes at least theoretically possible for two signed token strings to end up identical or predictably related to one another. That would quietly break an assumption the rest of this file leans on hard, starting two functions from now: <em>that each stored hashed token uniquely identifies exactly one session.</em> Adding <code>randomUUID()</code> into the mix guarantees every single call to <code>signRefreshToken</code>, no matter the user, no matter the timing, produces a token that has never existed before, anywhere, and never will again.</p>
<h3>Worked example</h3>
<p><strong>Input:</strong></p>
<pre><code class="language-js">const user = { _id: "64f1a2b3c4d5e6f7a8b9c0d1" };

signRefreshToken(user);
</code></pre>
<p><strong>Output</strong> (a JWT string, structurally like before but different payload/secret):</p>
<pre><code class="language-plaintext">eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJ1c2VySWQiOiI2NGYxYTJiM2M0ZDVlNmY3YThiOWMwZDEiLCJqdGkiOiJmNDdhYzEwYi01OGNjLTQzNzItYTU2Ny0wZTAyYjJjM2Q0NzkiLCJpYXQiOjE3MzYyMDAwMDAsImV4cCI6MTczODc5MjAwMH0.
9zLm2q...(different signature bytes)...
</code></pre>
<p>Decoding the middle chunk shows:</p>
<pre><code class="language-json">{
  "userId": "64f1a2b3c4d5e6f7a8b9c0d1",
  "jti": "f47ac10b-58cc-4372-a567-0e02b2c3d479",
  "iat": 1736200000,
  "exp": 1738792000
}
</code></pre>
<p>Notice: no <code>role</code> field at all (Difference 1), and <code>exp</code> is roughly 30 days after <code>iat</code>, not 15 minutes (Difference 3). Call this function again for the <em>same</em> user a second later, and <code>jti</code> will be a completely different random string, even though <code>userId</code> is identical — that's <code>randomUUID()</code> doing its job.</p>
<hr />
<h2>5. <code>digestRefreshToken</code> — turning a long token into a fixed, lookup-friendly fingerprint</h2>
<pre><code class="language-js">function digestRefreshToken(refreshToken) {
  return createHash('sha256').update(refreshToken, 'utf8').digest('hex');
}
</code></pre>
<p>Before anything else: why hash the refresh token <em>at all</em> instead of just storing the raw token string in the database? Think about what the database contains if you store the raw token: if that database were ever stolen or leaked (a genuine, common category of real-world breach), the attacker would have every user's live refresh token in hand, ready to use immediately, with zero extra work. Storing a <em>hash</em> of the token instead means the database leak alone isn't enough — you'd need to reverse a one-way hash, which is (by design) computationally infeasible.</p>
<p><strong>Reading the three chained method calls one at a time:</strong></p>
<ol>
<li><p><code>createHash('sha256')</code> — asks Node's crypto module for a fresh, empty "hash object," specifically configured to use the SHA-256 algorithm. Nothing has been hashed yet at this point; this just prepares the tool.</p>
</li>
<li><p><code>.update(refreshToken, 'utf8')</code> — feeds the actual token string into that hash object, telling it to interpret the string as UTF-8 encoded bytes (the standard way JavaScript strings are turned into raw bytes).</p>
</li>
<li><p><code>.digest('hex')</code> — tells the hash object "you're done receiving input, now give me the final result," and asks for that result formatted as a hexadecimal string (characters <code>0-9</code> and <code>a-f</code>). SHA-256 always produces exactly 256 bits of output, and 256 bits written in hex is always exactly <strong>64 characters</strong> — that specific number, 64, becomes important again in the very next function.</p>
</li>
</ol>
<p><strong>The core property to understand: this hash is deterministic.</strong> Feed the exact same string in a thousand times, get the exact same 64-character output a thousand times, no exceptions, no randomness involved anywhere.</p>
<p><strong>Now contrast that directly with</strong> <code>bcrypt.hash</code><strong>,</strong> which you'll meet properly in <code>register</code> (section 8), because the contrast is the entire point of this section. <code>bcrypt.hash</code> is deliberately <strong>non-deterministic</strong> — it generates a random "salt" internally every single time it's called, so hashing the identical password twice produces two different-looking results both times.</p>
<p><strong>Why does a password need randomness while a refresh token specifically must <em>not</em> have it?</strong> It comes down to what each hash needs to be used <em>for</em>, later:</p>
<ul>
<li><p>A password hash needs to resist an attacker who has stolen the <em>entire</em> database and now has unlimited offline time to try to crack it, possibly using <strong>rainbow tables</strong> — huge, precomputed lookup tables mapping common passwords to their hashes. If every user's password were hashed the same deterministic way, two users who happened to share the password <code>"password123"</code> would have the <em>identical</em> stored hash — and cracking one instantly reveals the other. Randomizing per-user defeats that.</p>
</li>
<li><p>A refresh token, by contrast, needs <em>fast, exact</em> lookup: "does any currently active session for this user match the token that was just presented?" To answer that efficiently, the stored value needs to be reproducible — hash the incoming token the same way every time, then simply compare it against what's stored. If refresh tokens used bcrypt instead, checking one incoming token against, say, ten stored sessions would require calling the (deliberately slow) <code>bcrypt.compare()</code> up to ten separate times, on every single refresh request from every user. The file's own design notes cite this specific performance problem as the actual reason an earlier version of this code, which did use bcrypt here, was abandoned.</p>
</li>
</ul>
<p><strong>There's also a sharper, genuinely-a-correctness-bug reason bcrypt would be actively wrong here, not merely slower:</strong> bcrypt silently truncates any input longer than 72 bytes — anything past that length is simply ignored during hashing. A JWT can easily exceed 72 bytes. That means two <em>genuinely different</em> long refresh tokens that happen to share the same first 72 bytes could be hashed by bcrypt into <em>matching</em> results — a false positive that would let the wrong token pass as valid. SHA-256 has no truncation limit like this at all; it processes an input of any length in full, so this entire failure mode simply cannot happen with it.</p>
<h3>Worked example</h3>
<p><strong>Input:</strong></p>
<pre><code class="language-js">digestRefreshToken("eyJhbGciOiJIUzI1NiIs...signRefreshToken output from above...");
</code></pre>
<p><strong>Output</strong> (always exactly 64 hex characters, always the same for this exact input string):</p>
<pre><code class="language-plaintext">7a3f9b2e1c6d4a8f0b5e2d9c1a7f4e6b8d3c0a9f2e5b7d1c4a6f8e0b3d5c7a91
</code></pre>
<p>Run it again on the exact same input string, get the exact same 64 characters back. Change even one character of the input — say, one letter in the token — and the output changes completely and unpredictably (this is called the <strong>avalanche effect</strong>), for example something like:</p>
<pre><code class="language-plaintext">f1e4c7a0b3d6f9e2c5a8b1d4f7e0c3a6b9d2f5e8c1a4b7d0f3e6c9a2b5d8f1e4
</code></pre>
<hr />
<h2>6. <code>refreshTokenMatchesDigest</code> — safely checking "does this token match what's stored?"</h2>
<pre><code class="language-js">function refreshTokenMatchesDigest(refreshToken, storedDigest) {
  if (typeof storedDigest !== 'string' || !/^[a-f0-9]{64}$/i.test(storedDigest)) {
    return false;
  }
  const presented = Buffer.from(digestRefreshToken(refreshToken), 'hex');
  const stored = Buffer.from(storedDigest, 'hex');
  return timingSafeEqual(presented, stored);
}
</code></pre>
<p><strong>The guard clause on the first line, read slowly.</strong> <code>/^[a-f0-9]{64}$/i</code> is a <strong>regular expression</strong> — a pattern used to test whether a string has a certain shape. Breaking the pattern down symbol by symbol:</p>
<ul>
<li><p><code>^</code> means "the match must start at the very beginning of the string" — no extra characters allowed before it.</p>
</li>
<li><p><code>[a-f0-9]</code> means "any single character that is a lowercase letter from <code>a</code> to <code>f</code>, or a digit from <code>0</code> to <code>9</code>" — exactly the character set a hex digest is made of.</p>
</li>
<li><p><code>{64}</code> means "exactly 64 of the previous thing in a row" — not 63, not 65.</p>
</li>
<li><p><code>$</code> means "and that must be the very end of the string" — no extra characters allowed after it either.</p>
</li>
<li><p>The trailing <code>i</code> flag makes letter matching case-insensitive, so uppercase <code>A-F</code> would also pass.</p>
</li>
</ul>
<p>Put together: "this string must be exactly 64 characters long, and every single one of them must be a hex digit — nothing more, nothing less." <code>.test(storedDigest)</code> runs <code>storedDigest</code> against that pattern and returns plain <code>true</code> or <code>false</code>.</p>
<p><strong>Why does this check exist at all?</strong> Two overlapping reasons. First, it's a cheap sanity filter — from section 5, you already know a real SHA-256 hex digest is <em>always</em> exactly 64 characters, so anything that doesn't match this shape genuinely cannot be a valid digest, and there's no point doing any further, more expensive work on it. Second, and more concretely, it solves a real migration problem: imagine this app <em>used</em> to hash refresh tokens with bcrypt (as the earlier, abandoned version mentioned in section 5 did) — bcrypt hashes look like <code>$2b$12$N9qo8uLOickgx2ZMRZoMy...</code>, nowhere near 64 plain hex characters. Old session records left over from that earlier version, still sitting in the database, would fail this check immediately and safely return <code>false</code> — treated as simply invalid — instead of crashing the function, or worse, silently being fed into a comparison that assumes a shape they don't actually have.</p>
<p><strong>Lines 3–4 convert text into raw bytes.</strong> <code>Buffer.from(hexString, 'hex')</code> takes a string like <code>"7a3f9b2e..."</code> and turns it into an actual sequence of raw binary bytes (a <code>Buffer</code>, Node's representation of raw binary data) — because the comparison on the next line needs to operate on real bytes, not on text characters.</p>
<p><strong>Line 5 is the security-critical line in this whole function.</strong> <code>timingSafeEqual(a, b)</code> looks, at a glance, like it should do exactly what <code>a === b</code> does — check whether two things are equal. It does <em>not</em> work the same way underneath, and the difference matters enormously here.</p>
<p>Picture what <code>a === b</code> (or a hand-written loop comparing byte by byte) does internally: it compares the first byte; if that byte doesn't match, it <strong>stops immediately</strong> and returns <code>false</code>, without bothering to check any of the remaining bytes. That "stop as soon as you find a mismatch" behavior is normally a totally reasonable, harmless optimization. But it creates a subtle, measurable side effect: a guess that gets its <em>first</em> byte wrong returns <code>false</code> microscopically faster than a guess that gets the first ten bytes right and only fails on the eleventh — because more work had to happen before the second one could fail. To one single human eyeballing one single request, that time difference is completely invisible — we're talking fractions of a millisecond. But an attacker who can send <em>millions</em> of automated, precisely-timed requests, and carefully measure the response time of each one, can statistically detect that pattern and use it to reconstruct a correct secret <strong>one byte at a time</strong>, entirely without ever guessing the whole thing at once. This category of attack has a name: a <strong>timing attack</strong>.</p>
<p><code>timingSafeEqual</code> is built specifically to defend against this: it always compares <em>every single byte</em>, every time, with no early exit, so the total time the comparison takes reveals absolutely nothing about how close a wrong guess was.</p>
<p><strong>The general rule this function is teaching:</strong> any time your code compares a secret against a stored value — a token, a cryptographic signature, an API key — always use a timing-safe comparison, never <code>===</code> and never a hand-rolled loop. The one common, deliberate exception in this exact file is comparing <em>passwords</em>, and that's only safe because <code>bcrypt.compare()</code> (section 9) is already timing-safe internally, by construction — you don't need to wrap it in anything extra.</p>
<h3>Worked example</h3>
<p><strong>Input (a genuine match):</strong></p>
<pre><code class="language-js">refreshTokenMatchesDigest(
  "eyJhbGciOi...(the real refresh token)...",
  "7a3f9b2e1c6d4a8f0b5e2d9c1a7f4e6b8d3c0a9f2e5b7d1c4a6f8e0b3d5c7a91"
);
</code></pre>
<p><strong>Output:</strong> <code>true</code> — because hashing that exact token with <code>digestRefreshToken</code> reproduces that exact 64-character string.</p>
<p><strong>Input (a stale, no-longer-matching digest — imagine the session was rotated already, so the stored digest changed to something new):</strong></p>
<pre><code class="language-js">refreshTokenMatchesDigest(
  "eyJhbGciOi...(the same old refresh token)...",
  "f1e4c7a0b3d6f9e2c5a8b1d4f7e0c3a6b9d2f5e8c1a4b7d0f3e6c9a2b5d8f1e4"
);
</code></pre>
<p><strong>Output:</strong> <code>false</code> — the hash of the presented token doesn't equal the stored digest.</p>
<p><strong>Input (a leftover bcrypt-format record from before the migration):</strong></p>
<pre><code class="language-js">refreshTokenMatchesDigest(
  "eyJhbGciOi...(some token)...",
  "$2b$12$N9qo8uLOickgx2ZMRZoMy.eIkP1z4Y1a2b3c4d5e6f7g8h9i0j1"
);
</code></pre>
<p><strong>Output:</strong> <code>false</code>, and immediately — this fails the regex shape check on line one and never even reaches <code>timingSafeEqual</code>.</p>
<hr />
<h2>7. <code>addRefreshSession</code> — recording a new logged-in device, safely</h2>
<pre><code class="language-js">async function addRefreshSession(user, refreshToken, device) {
  const tokenHash = digestRefreshToken(refreshToken);
  const result = await User.updateOne(
    { _id: user._id, isActive: true },
    {
      $set: { lastLogin: new Date() },
      $push: {
        refreshTokens: {
          $each: [{ tokenHash, device: device || 'unknown', createdAt: new Date() }],
          $slice: -MAX_REFRESH_SESSIONS,
        },
      },
    }
  );
  if (result.modifiedCount !== 1) throw new Error('Could not persist refresh session.');
}
</code></pre>
<p><strong>Big picture first:</strong> this function's job is to take a brand-new refresh token that was just issued, and record its <em>hash</em> (never the raw token itself — you already know why from section 5) into that user's document in the database, as one entry in their <code>refreshTokens</code> list, while also updating their "last logged in" timestamp.</p>
<p><strong>The filter,</strong> <code>{ _id: user._id, isActive: true }</code> <strong>— this is the single most important line in the whole file to understand deeply, because the pattern it demonstrates should become a reflex you reuse constantly.</strong></p>
<p>Here's the naive, tempting-but-wrong alternative you might write instead: somewhere earlier in the code, you already fetched this user from the database and confirmed, in a plain JavaScript <code>if</code> statement, that <code>user.isActive === true</code>. Then, later, you run this update using <em>only</em> <code>{ _id: user._id }</code> as the filter — no <code>isActive</code> check <em>inside</em> the update itself, because "I already checked that a moment ago."</p>
<p>The problem is the phrase "a moment ago." Between the instant you <em>checked</em> <code>isActive</code> in memory and the instant this update actually runs against the database, real time passes — however small. In that gap, it is entirely possible for a <em>different</em>, concurrent request to run — for example, an admin, in a completely separate request happening at the same time, deactivating this exact account. If that happens in the gap, your update would still go through, because your code never re-checks anything — it just trusts the answer it got a moment earlier. The result: you'd have silently created a brand-new, fully valid login session for an account that is, at this very instant, supposed to be deactivated. This category of bug has a name: a <strong>check-then-act race condition</strong>, or sometimes <strong>TOCTOU</strong> (time-of-check to time-of-use).</p>
<p>The fix, which is exactly what this line does, is to fold <code>isActive: true</code> directly <strong>into the same filter as the write itself</strong>. MongoDB re-evaluates that entire filter — including <code>isActive: true</code> — at the exact instant it performs the update, not a moment earlier, not based on some stale value you checked previously. If the account was deactivated even one millisecond before this exact line executes, the filter simply matches zero documents, and no write happens at all. This is called an <strong>atomic filter-and-update</strong> — "check and act" collapsed into one indivisible database operation instead of two separate steps with a gap between them. The general lesson to carry forward: whenever a condition genuinely matters for whether a write should happen, put that condition <em>inside</em> the same atomic operation as the write — never check it separately, beforehand, in application code, and trust that nothing changed in between.</p>
<p><code>$set: { lastLogin: new Date() }</code><strong>.</strong> <code>new Date()</code> captures whatever the current moment is, according to the server's clock, at the exact instant this line executes. It's stored on the document as a real BSON date value (MongoDB's native date type), not as a plain text string — which matters, because a real date type can later be sorted, filtered, and compared using normal date operators (<code>$gt</code>, <code>$lt</code>, etc.), whereas a string like <code>"2026-09-07"</code> would need to be parsed back into a real date every time before any of that would work correctly. This line runs on <em>every</em> login, which means the app always has an accurate "last seen" timestamp for every user, at essentially zero extra cost.</p>
<p><code>$push</code> <strong>combined with</strong> <code>$each</code> <strong>and</strong> <code>$slice</code> <strong>— this is the mechanism that actually enforces the</strong> <code>MAX_REFRESH_SESSIONS</code> <strong>policy from section 2.</strong> Read it from the inside out:</p>
<ul>
<li><p>On its own, <code>$push</code> appends exactly one new item to an array field.</p>
</li>
<li><p><code>$each</code> changes that to "append a <em>list</em> of items in one call" — here, the list happens to contain exactly one new session object, but the syntax requires <code>$each</code> regardless, because <code>$slice</code> (below) can only be used alongside it, even when pushing a single item.</p>
</li>
<li><p><code>$slice: -MAX_REFRESH_SESSIONS</code> — which is <code>-10</code> — then trims the <em>entire resulting array</em> down to keep only the <strong>last 10 entries</strong>. Negative numbers in <code>$slice</code> specifically mean "count backward from the end of the array," so <code>-10</code> means "keep the final 10 items, discard everything before them."</p>
</li>
</ul>
<p>Put together: every new login appends a new session entry at the end, and the moment the array would exceed 10 entries, the oldest one (or ones) are silently and automatically dropped from the front. This is a classic computer-science pattern called a <strong>bounded queue</strong> — new items always enter one end, and once the structure is "full," adding a new item pushes the oldest one out. Concretely, this means an 11th device logging in doesn't make the array grow to 11 — it makes the array <em>stay</em> at 10, quietly evicting whatever session had been sitting there longest.</p>
<p><code>device: device || 'unknown'</code><strong>.</strong> The <code>||</code> operator here isn't being used for boolean logic ("is this true or that true") — it's being used for its <strong>short-circuit fallback behavior</strong>: if the left-hand side (<code>device</code>) is "falsy" — meaning <code>undefined</code>, <code>null</code>, <code>''</code> (empty string), or a few other values JavaScript treats as false-like — the whole expression evaluates to whatever's on the right-hand side instead, <code>'unknown'</code>. Where does <code>device</code> come from? Looking ahead to <code>register</code> (section 8), it's read from <code>req.headers['user-agent']</code> — and an HTTP client is technically allowed to send a request with <em>no</em> User-Agent header at all. Without this fallback, a client that omitted that header would cause <code>device</code> to end up <code>undefined</code>, and the database would end up storing the literal value <code>undefined</code> in that field — which is exactly the kind of inconsistent data this one small fallback exists to prevent.</p>
<p><strong>The final line:</strong> <code>if (result.modifiedCount !== 1) throw ...</code><strong>.</strong> This is about correctly reading what <code>updateOne</code> actually tells you. It's tempting to assume "if the call didn't throw an exception, it must have worked" — but that's not what MongoDB's <code>updateOne</code> promises. Its return value is an object reporting, among other things, how many documents <em>matched</em> the filter (<code>matchedCount</code>) and how many were <em>actually modified</em> (<code>modifiedCount</code>) — and critically, if the filter matches zero documents (exactly the scenario from a few paragraphs ago, where <code>isActive</code> had already flipped to <code>false</code>), <code>updateOne</code> does <strong>not</strong> throw anything at all. It succeeds, at the plain JavaScript level, having matched and changed precisely nothing. This line is what turns "the database call technically didn't crash" into "the database call actually did what I needed it to do" — checking <code>modifiedCount</code> specifically, rather than just the absence of a thrown error, is the difference between <em>assuming</em> an outcome and <em>verifying</em> one.</p>
<h3>Worked example</h3>
<p><strong>Input:</strong></p>
<pre><code class="language-js">const user = { _id: "64f1a2b3c4d5e6f7a8b9c0d1", isActive: true };
const refreshToken = "eyJhbGciOi...(a freshly signed refresh token)...";
const device = "Mozilla/5.0 (iPhone; CPU iPhone OS 17_0 like Mac OS X)";

await addRefreshSession(user, refreshToken, device);
</code></pre>
<p><strong>What happens internally, step by step:</strong></p>
<ol>
<li><p><code>digestRefreshToken(refreshToken)</code> produces something like <code>"7a3f9b2e1c6d4a8f0b5e2d9c1a7f4e6b8d3c0a9f2e5b7d1c4a6f8e0b3d5c7a91"</code>.</p>
</li>
<li><p>MongoDB looks for a document where <code>_id</code> equals <code>"64f1a2b3c4d5e6f7a8b9c0d1"</code> <strong>and</strong> <code>isActive</code> is currently <code>true</code>.</p>
</li>
<li><p>Assuming it finds one, it sets <code>lastLogin</code> to the current timestamp, and pushes this new object onto <code>refreshTokens</code>:</p>
<pre><code class="language-js">{ tokenHash: "7a3f9b2e...c7a91", device: "Mozilla/5.0 (iPhone...)", createdAt: &lt;now&gt; }
</code></pre>
</li>
<li><p>If the array now has, say, 11 entries because this was an 11th device, <code>$slice: -10</code> trims it back down to the 10 most recent, dropping the oldest one.</p>
</li>
</ol>
<p><strong>Output:</strong> the function returns <code>undefined</code> (it has no <code>return</code> value) on success — its real effect is the side effect on the database. If the account had been deactivated a moment before this ran, <code>result.modifiedCount</code> would be <code>0</code>, and instead of quietly returning, the function would throw:</p>
<pre><code class="language-plaintext">Error: Could not persist refresh session.
</code></pre>
<hr />
<h2>8. <code>register</code> — walked one line at a time, in the exact order it executes</h2>
<pre><code class="language-js">const { name, email, phone, password, role } = req.body;
</code></pre>
<p>Same destructuring mechanic you saw explained slowly in section 1, now applied to <code>req.body</code> (whatever JSON the client sent) instead of to a package. Without destructuring, you'd write five separate lines: <code>const name = req.body.name;</code>, <code>const email = req.body.email;</code>, and so on. This one line does all five at once — and as a nice side effect, it also functions as instant, always-accurate documentation of exactly which fields this endpoint expects, right at the top of the function.</p>
<pre><code class="language-js">if (!name || !email || !phone || !password || !role) {
  return res.status(400).json({ message: "Missing required fields." });
}
</code></pre>
<p>This is <strong>fail-fast validation</strong>, and specifically, the <em>ordering</em> is deliberate: this check uses nothing but values already sitting in memory — no network call, no database query — so it's essentially free to run, and it runs <em>before</em> any of the more expensive steps below it (which do talk to the database). <code>!name</code> evaluates to <code>true</code> exactly when <code>name</code> is falsy (missing, empty string, etc.); chaining five of these with <code>||</code> means the whole condition becomes <code>true</code> the moment <strong>any single one</strong> of the five fields is missing. The HTTP status code <code>400</code> specifically means "the request itself is malformed" — there's no point asking the database anything yet, because the request wouldn't make sense even in a perfect world with a perfect database.</p>
<pre><code class="language-js">if (!["client", "lawyer"].includes(role)) {
  return res.status(400).json({ message: "Invalid role." });
}
</code></pre>
<p><code>["client", "lawyer"].includes(role)</code> checks whether the string in <code>role</code> is one of exactly those two values, and nothing else — this is called a <strong>whitelist</strong> (an explicit list of what <em>is</em> allowed) as opposed to a <strong>blacklist</strong> (a list of what <em>isn't</em>). Why write this check at all, if the actual signup page's dropdown menu only ever shows those two choices to a real user clicking through the UI? Because <strong>the frontend is not a security boundary.</strong> Nothing about how the website's dropdown is built stops someone from bypassing the website entirely and sending a raw HTTP request directly to this endpoint — using a tool like curl or Postman — with the body <code>{"role": "admin", ...}</code>. The dropdown only constrains people using the actual web page; this one line of server-side code is what actually constrains <em>everyone</em>, regardless of how they're sending the request.</p>
<pre><code class="language-js">const existing = await User.findOne({ $or: [{ email }, { phone }] });
if (existing) {
  return res.status(409).json({ message: "Email or phone already registered." });
}
</code></pre>
<p><code>$or</code> is a MongoDB query operator meaning exactly what it sounds like: "find one document where the email field matches this value, <strong>or</strong> the phone field matches this value." <code>409 Conflict</code> is the HTTP status specifically meaning "this request conflicts with the current state of whatever it's trying to create."</p>
<p>Now, the honest concurrency reality of this check, which is important to understand rather than assume: <strong>this is a check-then-act race condition, structurally identical in shape to the one explained in section 7 — sometimes called TOCTOU (time-of-check to time-of-use).</strong> Picture two registration requests for the exact same email address arriving at nearly the same instant. Both <code>findOne</code> queries can run and come back completely empty — "no existing user found" — <em>before either request has actually inserted anything into the database yet</em>. Both requests, having each seen an empty result, now believe the email is free, and both proceed forward. <strong>This line, by itself, does not prevent two accounts from ending up with the same email.</strong> The actual backstop for that has to live one layer deeper, at the database schema level: a <strong>unique index</strong> on the <code>email</code> and <code>phone</code> fields. With that index in place, when the second (racing) request eventually reaches <code>User.create()</code> a moment later, MongoDB itself refuses the insert outright and throws a specific, recognizable <strong>duplicate-key error (error code 11000)</strong> — and <em>that</em> is the real guarantee against duplicates, not this <code>findOne</code> check. This <code>findOne</code> exists purely to produce a friendlier, more specific error message in the common, everyday, non-racing case — it is explicitly, by design, not the actual safety net.</p>
<pre><code class="language-js">const passwordHash = await bcrypt.hash(password, 12);
</code></pre>
<p>This is the promised contrast to <code>digestRefreshToken</code> from section 5 — the non-deterministic counterpart. <code>bcrypt.hash(plaintext, saltRounds)</code> takes two arguments: the plain password, and a "cost factor." <code>12</code> here means the underlying algorithm runs <code>2^12</code> (4,096) internal iterations. Deliberately raising this number makes the hashing operation slower — and that slowness is the entire point. It costs your server a negligible, barely-perceptible fraction of a second to hash <em>one</em> password during <em>one</em> real user's registration. But it costs an attacker who has stolen your entire database of hashes and is trying to guess passwords offline, billions of times over, an enormous, deliberately inflated amount of total compute time. <code>12</code> is a widely used, well-tested balance point between those two costs. Crucially, and unlike <code>digestRefreshToken</code>, <strong>bcrypt also automatically generates and embeds a random "salt" into its output on every single call</strong> — hash the identical password twice, and you'll get two different-looking hash strings both times. That single property is precisely why checking a password later, in <code>login</code> (section 9), can't just compare two hash strings with <code>===</code> — it has to use <code>bcrypt.compare()</code> instead, which specifically knows how to extract that embedded salt back out of the stored hash before recomputing anything.</p>
<pre><code class="language-js">const user = await User.create({ name, email, phone, passwordHash, role });
</code></pre>
<p>This uses <strong>shorthand property syntax</strong> — the writing-side mirror of the destructuring you saw on the reading side back at the top of this function. Because each variable name here (<code>name</code>, <code>email</code>, <code>phone</code>, <code>passwordHash</code>, <code>role</code>) already exactly matches the key name you want in the resulting object, JavaScript lets you skip repeating yourself: <code>{ name, email, phone, passwordHash, role }</code> means precisely the same thing as writing out <code>{ name: name, email: email, phone: phone, passwordHash: passwordHash, role: role }</code> in full. <code>User.create()</code> is a single Mongoose method that quietly does three separate things for you in one call: builds a complete document in memory, runs every validation rule defined on the schema (correct types, required fields present, any custom rules), and then actually saves it to MongoDB — from the point of view of the code calling it, this feels like one atomic step, even though multiple things are happening underneath.</p>
<pre><code class="language-js">if (role === "client") {
  await Client.create({ userId: user._id });
} else {
  await Lawyer.create({ userId: user._id, specialization: [], yearsOfExperience: 0, consultationFee: 0 });
}
</code></pre>
<p>This is a data-modeling decision worth understanding on its own terms, sometimes called <strong>profile fan-out</strong>. The <code>User</code> document itself only holds what's genuinely common to <em>every</em> account, regardless of role: identity and login credentials — name, email, phone, password hash, role. Anything that's specific to <em>one particular role</em> — a lawyer's list of legal specializations, their years of experience, their consultation fee — lives instead in a completely separate document, in a separate MongoDB collection (<code>Lawyer</code> or <code>Client</code>), linked back to the user only by storing that user's <code>_id</code> as a field named <code>userId</code>. This is exactly why, elsewhere in this app, "a lawyer" conceptually corresponds to <em>two different IDs</em> referring to the same real person: <code>User._id</code> (their login identity) and <code>Lawyer._id</code> (their profile document) — meaning some other piece of code that needs the lawyer's profile has to perform a <em>second</em> lookup, something like <code>Lawyer.findOne({ userId: someUsersId })</code>, to bridge from one to the other.</p>
<p><strong>The concurrency bug this section explicitly flags lives right here.</strong> If <code>User.create()</code> on the previous line succeeded, but <em>this</em> call — whichever of <code>Client.create()</code> or <code>Lawyer.create()</code> applies — then throws for any reason at all (a validation failure, a dropped database connection, anything), there is now a <code>User</code> document sitting permanently in the database with <strong>no matching profile document at all</strong>. Nothing anywhere in this function reaches back and undoes the earlier <code>User.create()</code> — there is no automatic rollback. The correct, complete fix — which the file's own design notes explicitly mark as future work rather than something already solved — is a <strong>MongoDB transaction</strong>: a mechanism that lets multiple writes spanning multiple collections either <em>all</em> succeed together, or <em>all</em> roll back together, with no possibility of the database ever being caught in a visible half-finished state in between.</p>
<pre><code class="language-js">await addRefreshSession(user, refreshToken, req.headers['user-agent']);
</code></pre>
<p>This one line demonstrates a small but genuinely important JavaScript syntax rule: <strong>bracket notation, used here because the property name has a hyphen in it.</strong> Dot notation (<code>req.headers.something</code>) only works when the property name you're accessing is also a legal JavaScript variable name — and hyphens are not legal inside variable names, because a hyphen is the subtraction operator. If this code had been written as <code>req.headers.user-agent</code>, JavaScript would actually try to parse that as "take the value at <code>req.headers.user</code>, then subtract something named <code>agent</code>" — nonsense in this context, and it would either throw an error or silently produce something completely wrong. Bracket notation, <code>req.headers['user-agent']</code>, sidesteps the whole problem entirely by treating the whole thing as a plain string used as a key, rather than as literal JavaScript syntax to be parsed. Any HTTP header containing a hyphen — and there are many common ones, like <code>content-type</code> or <code>x-forwarded-for</code> — needs exactly this same bracket treatment. This is also a small but genuine networking detail worth knowing on its own: HTTP header names very commonly contain hyphens as a matter of convention, which is precisely why Express exposes all headers as a plain object you index into with brackets, rather than something you'd naturally dot into.</p>
<pre><code class="language-js">} catch (err) {
  return res.status(500).json({ message: "Registration failed.", error: err.message });
}
</code></pre>
<p>This is a manually written <code>try/catch</code>, rather than a cleaner, more reusable pattern you might see in a more mature codebase — something like a shared <code>asyncHandler</code> wrapper combined with a custom <code>AppError</code> class that centralizes how errors get turned into responses. There's a genuine security concern worth flagging in this specific line: <code>error: err.message</code> sends whatever the raw, internal error message says directly back to whoever made the request. In the ordinary case, this is completely harmless — usually it's something benign like "email already exists." But if the underlying error came from somewhere less expected — say, a raw database driver failure, or some unrelated internal exception — its message could end up describing internal implementation details (database names, internal file paths, library internals) that have no business being visible to an outside client at all. In a cleaner rewrite, this catch block should ideally shrink down to simply re-throwing the error and letting one single, centralized, shared error handler elsewhere in the app decide, in one place, what's actually safe to reveal externally.</p>
<h3>Worked example</h3>
<p><strong>Input</strong> (an HTTP POST request body):</p>
<pre><code class="language-json">{
  "name": "Amara Chen",
  "email": "amara@example.com",
  "phone": "+15551234567",
  "password": "correcthorsebatterystaple",
  "role": "lawyer"
}
</code></pre>
<p><strong>Output on success</strong> — a new <code>User</code> document is created (with a bcrypt password hash, never the plain password), a matching <code>Lawyer</code> document is created, a refresh session is recorded, and the HTTP response sent back to the client is something like:</p>
<pre><code class="language-json">{
  "accessToken": "eyJhbGciOi...(a signAccessToken output)...",
  "refreshToken": "eyJhbGciOi...(a signRefreshToken output)...",
  "user": { "id": "64f1a2b3c4d5e6f7a8b9c0d1", "name": "Amara Chen", "role": "lawyer" }
}
</code></pre>
<p><strong>Output on a validation failure</strong> — same request, but <code>"role": "admin"</code> instead:</p>
<pre><code class="language-json">{ "message": "Invalid role." }
</code></pre>
<p>sent with HTTP status <code>400</code>.</p>
<p><strong>Output on a duplicate email</strong> — same request, but this email was already registered by someone else:</p>
<pre><code class="language-json">{ "message": "Email or phone already registered." }
</code></pre>
<p>sent with HTTP status <code>409</code>.</p>
<hr />
<h2>9. <code>login</code> — checking a claim of identity against stored credentials</h2>
<pre><code class="language-js">const user = await User.findOne({ email }).select('+passwordHash');
</code></pre>
<p>To understand <code>.select('+passwordHash')</code>, you first need to know something about how the <code>User</code> schema is defined elsewhere in the app: it marks the <code>passwordHash</code> field with <code>select: false</code>. That setting means, <strong>by default, every single query against the</strong> <code>User</code> <strong>model anywhere in this entire codebase</strong> — <code>User.find()</code>, <code>User.findById()</code>, all of them, automatically leaves <code>passwordHash</code> out of whatever it returns, with zero extra effort required from whoever happens to be writing that particular query. This is a strong, safe <em>default</em>: some completely unrelated controller elsewhere in the app, written by someone who has never thought about password security at all, can freely query users for, say, a public profile listing, and it is simply <em>impossible</em> for that query to accidentally leak a password hash into an API response — because the field was never even fetched from the database in the first place, let alone serialized into JSON.</p>
<p><code>.select('+passwordHash')</code> is the deliberate, explicit override to that default. The <code>+</code> prefix specifically means "yes, I am aware this field is normally hidden, and I specifically want it included, right here, right now, because this particular piece of code genuinely needs it." The practical benefit: it forces every single place in the codebase that actually needs to compare passwords to say so explicitly, right at that exact call site — rather than password access being some ambient possibility lurking everywhere, invisible until something goes wrong.</p>
<pre><code class="language-js">if (!user || !user.isActive) {
  return res.status(401).json({ message: "Invalid credentials." });
}
</code></pre>
<p>This line is about <strong>preventing user enumeration</strong>. Notice: "no user with this email exists at all" and "a user with this email exists, but their account has been deactivated" produce the <em>exact same</em> outcome — same status code (<code>401</code>), same message (<code>"Invalid credentials."</code>). Consider what would happen if these two cases produced differently worded responses instead — say, <code>"No account found"</code> versus <code>"Account disabled"</code>. An attacker could then send login attempts for a huge list of email addresses they're curious about, never even attempting a password, and use <em>only the wording difference in the response</em> to silently build up a list of which of those email addresses correspond to real, registered accounts on this system — a genuine privacy and security leak, achieved without ever guessing a single password. Making the two outcomes indistinguishable from the outside closes that door entirely.</p>
<pre><code class="language-js">const isMatch = await bcrypt.compare(password, user.passwordHash);
if (!isMatch) {
  return res.status(401).json({ message: "Invalid credentials." });
}
</code></pre>
<p><code>bcrypt.compare(plaintext, storedHash)</code> is the counterpart to <code>bcrypt.hash</code> from section 8. Internally, it reads the salt that's embedded directly inside <code>storedHash</code> (recall: bcrypt bakes the salt right into its own output string), redoes the exact same hashing computation on the freshly submitted <code>plaintext</code> password using that same extracted salt, and then checks whether the two results match. This comparison is already timing-safe <em>internally</em>, by construction — which is exactly why this file doesn't need to wrap it in <code>timingSafeEqual</code> the way <code>refreshTokenMatchesDigest</code> had to in section 6; that protection is already built into <code>bcrypt.compare</code> itself.</p>
<p>Now look closely at the exact wording of this failure message compared to the one two lines above: <strong>identical</strong>, character for character — <code>"Invalid credentials."</code>, both times, with the same <code>401</code> status both times. This is the second half of the anti-enumeration design started above: whether the underlying reason is "no such email exists at all," or "the account exists but is deactivated," or "the password submitted is simply wrong," an attacker probing this endpoint receives back the exact same signal every single time, with absolutely no way to distinguish any of those three genuinely different situations from the outside.</p>
<h3>Worked example</h3>
<p><strong>Input (correct credentials, active account):</strong></p>
<pre><code class="language-json">{ "email": "amara@example.com", "password": "correcthorsebatterystaple" }
</code></pre>
<p><strong>Output:</strong></p>
<pre><code class="language-json">{
  "accessToken": "eyJhbGciOi...",
  "refreshToken": "eyJhbGciOi...",
  "user": { "id": "64f1a2b3c4d5e6f7a8b9c0d1", "name": "Amara Chen", "role": "lawyer" }
}
</code></pre>
<p><strong>Input (an email that was never registered):</strong></p>
<pre><code class="language-json">{ "email": "nobody-registered-this@example.com", "password": "anything" }
</code></pre>
<p><strong>Output</strong> (status <code>401</code>):</p>
<pre><code class="language-json">{ "message": "Invalid credentials." }
</code></pre>
<p><strong>Input (correct email, wrong password):</strong></p>
<pre><code class="language-json">{ "email": "amara@example.com", "password": "wrongGuessHere" }
</code></pre>
<p><strong>Output</strong> (status <code>401</code> — indistinguishable from the previous case):</p>
<pre><code class="language-json">{ "message": "Invalid credentials." }
</code></pre>
<p><strong>Input (correct email and password, but the account was deactivated by an admin):</strong></p>
<p><strong>Output</strong> (status <code>401</code> — again, indistinguishable):</p>
<pre><code class="language-json">{ "message": "Invalid credentials." }
</code></pre>
<hr />
<h2>10. <code>refreshToken</code> — the most intricate handler in the file</h2>
<pre><code class="language-js">let payload;
try {
  payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
} catch {
  return res.status(401).json({ message: "Invalid or expired refresh token." });
}
</code></pre>
<p><code>jwt.verify(token, secret)</code> performs two distinct checks in one call, and either one failing causes the whole call to throw an exception rather than return some kind of "false" value: first, is the signature actually valid — was this token really signed using <code>JWT_REFRESH_SECRET</code>, and has it not been tampered with since? Second — using the same <code>exp</code>/<code>Date.now()</code> mechanism first described back in section 2 — has the token's expiry timestamp already passed? If either check fails for any reason, <code>jwt.verify</code> <strong>throws</strong>, and that's exactly why this call needs its own dedicated <code>try/catch</code>, separate from any error handling elsewhere in the app.</p>
<p><code>catch { ... }</code> — with nothing at all inside the parentheses — is called <strong>optional catch binding</strong>. The normal, more familiar form is <code>catch (err) { ... }</code>, which lets you inspect exactly what went wrong inside the <code>err</code> variable. Here, the code deliberately doesn't do that, because it genuinely does not care <em>why</em> verification failed — an expired token, a tampered token, a completely malformed garbage string — all of them are treated identically, as "reject this." Since the specific error is never going to be looked at, there's no reason to even bother naming a variable for it.</p>
<pre><code class="language-js">const user = await User.findById(payload.userId).select("+refreshTokens.tokenHash");
</code></pre>
<p>It's important to be precise about what passing <code>jwt.verify</code> actually proves, and what it does <em>not</em> prove. It proves the token's signature is legitimate, and that the token hasn't expired yet. It does <strong>not</strong> prove that the token hasn't been <em>revoked</em> — for example, that the user hasn't already logged out on that device (section 11), or that this exact token hasn't already been used once already as part of the rotation mechanism you're about to read below. That distinction is exactly why the very next step reaches into the database to fetch this user's actual, current, mutable list of active sessions to cross-check against: a cryptographic signature is a fixed mathematical fact about a string that, once true, stays true forever — it can never be "un-issued" after the fact purely by its own logic. Making revocation possible at all requires checking against something <em>external</em> and <em>mutable</em> that lives outside the token itself — which is precisely what this database lookup provides.</p>
<pre><code class="language-js">const oldTokenHash = digestRefreshToken(refreshToken);
const hasMatchingSession = user.refreshTokens.some((session) =&gt; (
  refreshTokenMatchesDigest(refreshToken, session.tokenHash)
));
</code></pre>
<p><code>.some()</code> is a built-in array method that walks through <code>user.refreshTokens</code> one entry at a time and, for each <code>session</code>, runs the given function. The very first time that function returns <code>true</code> for any element, <code>.some()</code> <strong>stops immediately</strong> and returns <code>true</code> for the whole call, without bothering to check any of the remaining array elements — this is called <strong>short-circuit evaluation</strong>, the same underlying idea you already saw at work in the <code>device || 'unknown'</code> fallback back in section 7, just applied here to loop control instead of a single expression. Every individual comparison performed inside this loop goes through <code>refreshTokenMatchesDigest</code> from section 6 — which means every single comparison happening here, not just some overall final result, is timing-safe.</p>
<p>This whole check is the actual mechanism that makes revocation genuinely work, and it's worth restating plainly: a JWT's signature being valid is a permanent, unchangeable mathematical fact — a signed token has no built-in concept of "and by the way, I've since been logged out." The <em>only</em> way to make a previously-valid, unexpired token stop working is to check it against something outside the token entirely — here, the current, mutable list of session hashes stored on this specific user's document. If <code>logout</code> (section 11) has already removed this particular session's hash from that array, <code>.some()</code> finds no matching entry anywhere in the list, <code>hasMatchingSession</code> becomes <code>false</code>, and this otherwise perfectly valid, correctly-signed, not-yet-expired token gets rejected anyway. This is the entire reason this file goes to the trouble of storing hashed sessions server-side in the first place, rather than simply trusting JWTs as fully "stateless" tokens the way some simpler designs do.</p>
<pre><code class="language-js">const newTokenHash = digestRefreshToken(newRefreshToken);
const rotation = await User.updateOne(
  { _id: user._id, isActive: true, 'refreshTokens.tokenHash': oldTokenHash },
  { $set: { 'refreshTokens.$[session].tokenHash': newTokenHash } },
  { arrayFilters: [{ 'session.tokenHash': oldTokenHash }] }
);
if (rotation.modifiedCount !== 1) {
  return res.status(401).json({ message: 'Refresh token was already used or revoked.' });
}
</code></pre>
<p>This is the single most important concurrency mechanism in the entire file — worth reading slower than anything else here.</p>
<p><strong>Look carefully at the filter object first:</strong> <code>{ _id: user._id, isActive: true, 'refreshTokens.tokenHash': oldTokenHash }</code>. Focus on that third condition specifically: it says "only match this document if the <em>old</em> hash is <em>still currently present</em> somewhere in the array, right at this exact instant, at the moment this write is actually executing." This is the same core idea as <code>addRefreshSession</code>'s filter back in section 7 — folding a condition directly into the atomic write itself, rather than checking it separately beforehand — but here it's being used to do something more powerful. This specific pattern has a name: <strong>optimistic concurrency control</strong>, sometimes also called <strong>compare-and-swap</strong>: "update this field, but <em>only if</em> it still currently has the exact value I expect it to have; if it doesn't, do nothing at all."</p>
<p><strong>Here's the exact replay attack this single mechanism defends against.</strong> Imagine an attacker somehow steals a valid, not-yet-expired refresh token — say, through a compromised browser extension sitting on the victim's machine. At some point afterward, purely by unlucky timing, both the legitimate user (using their phone, say) and the attacker (using the stolen token) happen to send a refresh request using that <em>exact same</em> token, at nearly the same instant — a genuine, real race condition, not a hypothetical one. Both of these requests pass every single check described above: both signatures verify fine, neither token has expired, and both find a matching session in the array via <code>.some()</code>. But only <em>one</em> of them can possibly win at <em>this specific line</em>. Whichever <code>updateOne</code> call happens to physically reach MongoDB first finds <code>oldTokenHash</code> still present in the filter, and succeeds — <code>rotation.modifiedCount</code> becomes <code>1</code>, and the stored hash gets replaced with <code>newTokenHash</code>. The instant that happens, the <em>second</em> racing request's filter — which is still looking for the <em>old</em> hash — no longer matches anything at all, because the underlying document has already changed out from under it, milliseconds earlier. Its <code>modifiedCount</code> comes back <code>0</code>, and it gets correctly rejected with "already used or revoked." <strong>The atomicity of a single MongoDB write guarantees that only one of the two racing requests can ever win — there is no possible outcome where both succeed.</strong></p>
<p><code>arrayFilters</code> <strong>is the piece that makes the actual field update precise enough to work at all.</strong> <code>refreshTokens</code> is an array, and any given user could have up to 10 separate session subdocuments sitting inside it at once (thanks to <code>MAX_REFRESH_SESSIONS</code> from section 2). The path <code>'refreshTokens.$[session].tokenHash'</code> uses a special positional placeholder, <code>$[session]</code>, which reads as "whichever specific array element the <code>arrayFilters</code> option below identifies as the one named <code>session</code>." And <code>arrayFilters: [{ 'session.tokenHash': oldTokenHash }]</code> is exactly what defines that: "the one particular array element whose <code>tokenHash</code> field equals <code>oldTokenHash</code>." Without this mechanism, MongoDB would have no way at all to know <em>which one</em> of up to ten array entries you actually meant to update — you'd need some other, clunkier approach to target one specific element inside an array.</p>
<h3>Worked example</h3>
<p><strong>Input</strong> (client sends its current refresh token, along with a freshly generated <code>newRefreshToken</code> the server has already produced internally for this request):</p>
<pre><code class="language-js">const refreshToken = "eyJhbGciOi...(the client's existing, currently-stored token)...";
// newRefreshToken was generated earlier in the handler via signRefreshToken(user)
</code></pre>
<p><strong>Output on success</strong> — the old session's stored hash is atomically replaced with the new one, and the client receives:</p>
<pre><code class="language-json">{
  "accessToken": "eyJhbGciOi...(a brand-new signAccessToken output)...",
  "refreshToken": "eyJhbGciOi...(newRefreshToken)..."
}
</code></pre>
<p><strong>Output if the exact same (now-stale) refresh token is presented a second time</strong> — for example, by a browser tab that cached the old token and tried to reuse it after rotation already happened — status <code>401</code>:</p>
<pre><code class="language-json">{ "message": "Refresh token was already used or revoked." }
</code></pre>
<p><strong>Output if the token has simply expired (30+ days old)</strong> — status <code>401</code>:</p>
<pre><code class="language-json">{ "message": "Invalid or expired refresh token." }
</code></pre>
<hr />
<h2>11. <code>logout</code> — deliberately forgiving, on purpose</h2>
<pre><code class="language-js">try {
  payload = jwt.verify(refreshToken, process.env.JWT_REFRESH_SECRET);
} catch {
  return res.status(200).json({ message: "Logged out successfully." });
}
</code></pre>
<p>This has the exact same shape as the <code>try</code>/<code>catch</code> in <code>refreshToken</code> above — same <code>jwt.verify</code> call, same empty <code>catch</code> — but notice the status code on failure is completely different: <strong>200 here, versus 401 there.</strong> This difference is a deliberate design choice about user experience combined with security reasoning, not an inconsistency to fix. Think about what someone hitting the logout endpoint actually <em>wants</em>: exactly one outcome — to no longer be logged in. If it turns out their token was already garbage, already expired, or was never even valid in the first place, then the end state they were actually asking for — "I am now logged out" — is <em>already true</em>, trivially. Treating that situation as an <em>error</em> would be actively misleading to the caller; nothing needs fixing, nothing went wrong from their point of view, and there's no useful corrective action for them to take. This specific behavior — doing the same operation on an already-invalid or already-applied input and getting the same successful result every time, with no error — is called <strong>idempotent logout</strong> (more generally, an idempotent operation is one where doing it once, or doing it many times, produces the same end state).</p>
<pre><code class="language-js">await User.updateOne(
  { _id: payload.userId },
  { $pull: { refreshTokens: { tokenHash: digestRefreshToken(refreshToken) } } }
);
</code></pre>
<p><code>$pull</code> is a MongoDB update operator that removes every array element matching a given condition — here, specifically the one subdocument (if any) whose <code>tokenHash</code> matches the digest of exactly this one token being logged out with. Because it's scoped to that one exact hash, and not to "clear the whole array," logging out on one device — a phone, say — leaves up to nine other possible active sessions (a laptop, a tablet, a different browser) <strong>completely untouched</strong>. This is exactly the behavior you'd want in a real multi-device app: logging out should end the session you're actually using right now, not silently sign the user out of every device they own everywhere else.</p>
<h3>Worked example</h3>
<p><strong>Input (a currently valid, active refresh token):</strong></p>
<pre><code class="language-json">{ "refreshToken": "eyJhbGciOi...(a live session's token)..." }
</code></pre>
<p><strong>Output</strong> (status <code>200</code>) — this session's entry is removed from <code>refreshTokens</code>, all other sessions untouched:</p>
<pre><code class="language-json">{ "message": "Logged out successfully." }
</code></pre>
<p><strong>Input (a garbage string, or an already-expired token):</strong></p>
<pre><code class="language-json">{ "refreshToken": "not-a-real-token-at-all" }
</code></pre>
<p><strong>Output</strong> (still status <code>200</code>, same message — no error surfaced to the caller):</p>
<pre><code class="language-json">{ "message": "Logged out successfully." }
</code></pre>
<hr />
<h2>12. The Postman test table (reference)</h2>
<p>Look at the <em>shape</em> of this table before the specific rows, because the organizing idea is the actual lesson here, more than any one specific test case: the tests are grouped by <strong>category of scenario</strong> — <em>(happy path)</em>, <em>(validation)</em>, <em>(auth failure)</em>, <em>(not found/revoked)</em>, <em>(edge case: role tampering)</em>, <em>(edge case: duplicate)</em>, <em>(rotation replay)</em>, <em>(logout idempotency)</em>. A genuinely thorough test plan for <strong>any</strong> authentication system, not just this one, should aim to cover that same <em>shape</em> of ground: does the ideal, everything-goes-right path actually work; is obviously bad input rejected early, before it can cause any damage; are wrong credentials rejected without ever leaking information about <em>why</em> they were wrong; are already-used, already-expired, or already-revoked tokens correctly caught and rejected; can someone bypass an intended whitelist by simply sending an unexpected raw value straight to the API; can the same resource accidentally end up created twice; can a token meant to be used exactly once actually be used twice; and does "undo" behavior, like logging out, still behave sensibly even when handed already-broken input. Test #7, labeled "rotation replay," directly exercises the atomicity mechanism from section 10 — reusing a token from <em>before</em> a rotation happened should fail specifically because that token's hash no longer exists anywhere in the array, having already been overwritten by the rotation.</p>
<hr />
<h2>13. Reading the <code>refreshToken</code> flowchart as a decision tree</h2>
<p>Every diamond-shaped decision point in that flowchart corresponds to one real, specific <code>if</code> statement or filter condition inside section 10's code — nothing in the diagram is decorative:</p>
<ul>
<li><p>"refreshToken present?" → the very first input-validation check, before anything else runs.</p>
</li>
<li><p>"Does <code>jwt.verify</code> throw?" → the <code>try/catch</code> wrapped around the verification call.</p>
</li>
<li><p>"Not found, or <code>!isActive</code>?" → the check immediately after <code>User.findById</code>.</p>
</li>
<li><p>"No matching session found?" (via <code>.some()</code>) → the revocation check.</p>
</li>
<li><p>"<code>modifiedCount !== 1</code>?" → the atomic, compare-and-swap rotation check.</p>
</li>
</ul>
<p>The value of actually drawing a chart like this out <strong>before</strong> writing a handler (or while trying to make sense of one someone else already wrote) is that it forces every branch to become explicit, on paper, including the one branch that's genuinely easy to forget entirely: <em>"what if the token is syntactically perfect, correctly signed, and not yet expired — but was already logged out?"</em> That branch simply doesn't exist at all in a naive JWT implementation that trusts tokens as purely "stateless" proof — it exists here <em>only</em> because of the deliberate session-hash-checking design built out across every function in section 10.</p>
<hr />
<h2>Self-check before moving on</h2>
<ol>
<li><p>Why does <code>signRefreshToken</code> deliberately leave <code>role</code> out of its payload, while <code>signAccessToken</code> includes it? What's the worst-case consequence if that were reversed?</p>
</li>
<li><p>Why is <code>digestRefreshToken</code> deterministic while <code>bcrypt.hash</code> is deliberately not — and for each one, why does that specific property matter for the specific job it does?</p>
</li>
<li><p>Walk through, step by step, exactly why two simultaneous requests using the same stolen refresh token can never <em>both</em> succeed inside <code>refreshToken</code>.</p>
</li>
<li><p>Why does <code>login</code> return the literal same error message and status code for "no such user exists" and "the password was simply wrong"?</p>
</li>
<li><p>Why does <code>addRefreshSession</code>'s database filter include <code>isActive: true</code> directly inside it, instead of checking <code>isActive</code> earlier in the code and simply trusting that earlier result?</p>
</li>
<li><p>Trace where in this file a plain <em>string</em> like <code>'15m'</code> actually gets turned into a real numeric timestamp — and then trace where, later, that same timestamp gets read and checked again.</p>
</li>
</ol>
]]></content:encoded></item><item><title><![CDATA[PublicLawyerControllerLearnings]]></title><description><![CDATA[This walks the file top to bottom, in the order the code actually runs. For every chunk of code, everything relevant : software design, security…]]></description><link>https://publiclawyercontrollerlearnings.hashnode.dev/publiclawyercontrollerlearnings</link><guid isPermaLink="true">https://publiclawyercontrollerlearnings.hashnode.dev/publiclawyercontrollerlearnings</guid><dc:creator><![CDATA[Jyoti Tiwari]]></dc:creator><pubDate>Mon, 31 Aug 2026 05:50:03 GMT</pubDate><content:encoded><![CDATA[<h1><code>publicLawyerController.js</code> — Code-Block-by-Code-Block</h1>
<p>This walks the file <strong>top to bottom, in the order the code actually runs</strong>. For every chunk of code, everything relevant : software design, security, JavaScript mechanics, Mongoose, MongoDB, algorithms, concurrency, networking, date/time : is explained together, right where it happens, instead of grouped into separate topic chapters. Small bracket tags like <strong>[Security]</strong> or <strong>[JS]</strong> are kept so you can still recognize <em>what kind</em> of concept you're looking at, but nothing is split into its own section anymore.</p>
<hr />
<h2>Block 0 — Imports and file-level setup</h2>
<pre><code class="language-js">const Lawyer = require('../models/lawyer');
const Client = require('../models/client');
const Availability = require('../models/availibility');
const asyncHandler = require('../middleware/asyncHandler');
const AppError = require('../utils/AppError');
const { sendSuccess, paginationMeta } = require('../utils/apiResponse');
</code></pre>
<ul>
<li><p><strong>[Networking/Node basics]</strong> <code>require(...)</code> is Node.js's module system — it pulls in code defined in another file. Each line gives this file a local variable (<code>Lawyer</code>, <code>Client</code>, etc.) pointing at whatever that other file exports.</p>
</li>
<li><p><strong>[JS]</strong> The last line is <strong>destructuring</strong> an object export: <code>apiResponse.js</code> exports an object with (at least) two functions on it, <code>sendSuccess</code> and <code>paginationMeta</code>, and this line pulls both out into standalone variables in one shot, instead of writing <code>const sendSuccess = require('../utils/apiResponse').sendSuccess;</code> twice.</p>
</li>
<li><p><strong>[SE — audience-based separation]</strong> The fact that this file is called *<code>public</code>*LawyerController, and only ever imports things needed to <em>read and format</em> lawyer data (never anything for a lawyer editing their own profile), is itself a design signal: this file only serves anonymous/public callers, never a lawyer viewing their own account. A sibling file (<code>lawyerProfileController.js</code>, not shown here) handles that other audience. Splitting by <em>who's allowed to call this</em> — not just <em>what it technically does</em> — means this whole file can safely assume "never trust the caller" with no exceptions.</p>
</li>
</ul>
<hr />
<h2>Block 1 — <code>LAWYER_FIELDS</code> (the shared projection object)</h2>
<pre><code class="language-js">const LAWYER_FIELDS = {
  specialization: 1,
  yearsOfExperience: 1,
  courtsPracticed: 1,
  languages: 1,
  consultationFee: 1,
  bio: 1,
  profilePhoto: 1,
  officeAddress: 1,
  rating: 1,
  reviewCount: 1,
  totalConsultations: 1,
};
</code></pre>
<ul>
<li><p><strong>[MongoDB — projection]</strong> This is a MongoDB <em>projection object</em>. <code>1</code> means "include this field in the result." You could use <code>0</code> to mean "exclude," but you generally can't mix <code>1</code>s and <code>0</code>s in the same object (except <code>_id</code>). This object gets reused later inside <code>$project</code> stages (see Block 3.9 and Block 5) — written once here, spread wherever needed.</p>
</li>
<li><p><strong>[Security — data minimization]</strong> Look at what's <em>missing</em>: no <code>verificationStatus</code>, no <code>isProfileVisible</code>, nothing payment-related. Because this object later feeds MongoDB's own <code>$project</code> stage, those excluded fields are never even pulled out of the database for this request — not "fetched then hidden," but never fetched at all. That's a stronger guarantee than filtering in application code, and it's the core of <em>least privilege</em>: only touch the data you're actually willing to expose.</p>
</li>
<li><p><strong>[SE — DRY]</strong> Because this is a single named constant reused by three different handlers (<code>getLawyers</code>, <code>getLawyerById</code>, <code>getLawyerRankings</code>), the "list of public-safe fields" only has to be written and maintained in one place.</p>
</li>
</ul>
<hr />
<h2>Block 2 — <code>formatLawyer(raw)</code></h2>
<pre><code class="language-js">function formatLawyer(raw) {
  return {
    _id: raw._id,
    user: { _id: raw.user._id, name: raw.user.name },
    specialization: raw.specialization,
    yearsOfExperience: raw.yearsOfExperience,
    courtsPracticed: raw.courtsPracticed,
    languages: raw.languages,
    consultationFee: raw.consultationFee,
    bio: raw.bio,
    profilePhoto: raw.profilePhoto,
    officeAddress: raw.officeAddress,
    rating: raw.rating,
    reviewCount: raw.reviewCount,
    totalConsultations: raw.totalConsultations,
  };
}
</code></pre>
<ul>
<li><p><strong>[SE — shared reshape function]</strong> This function is called from <code>getLawyers</code>, <code>getLawyerById</code>, and <code>getLawyerRankings</code> (Block 3.12, Block 4, Block 5) — three handlers that fetch data in <em>different ways</em> (aggregation pipeline vs. <code>.populate()</code>), but all funnel their result through this one function before responding. That guarantees the frontend always sees the exact same "lawyer object" shape no matter which endpoint it called — one parser, not three slightly-different ones that drift apart over time.</p>
</li>
<li><p><strong>[JS — pure function]</strong> Given the same <code>raw</code> input, this always returns the same output, and it never touches anything outside itself (no DB calls, no global state). That predictability is exactly why it's safe to call from three unrelated places — nothing calling it can accidentally affect anything else calling it.</p>
</li>
<li><p><strong>[Security]</strong> Notice it explicitly hand-picks each field by name rather than doing something like <code>return raw;</code>. Even though <code>LAWYER_FIELDS</code> already limited what the <em>database</em> sends back, this function is a second, independent safety net at the <em>application</em> layer — an extra field accidentally left in <code>raw</code> still won't leak out through here.</p>
</li>
<li><p><strong>[JS — nested object literal]</strong> <code>user: { _id: raw.user._id, name: raw.user.name }</code> builds a brand-new nested object rather than just doing <code>user: raw.user</code> — again deliberately picking only two sub-fields instead of passing the whole <code>raw.user</code> object through.</p>
</li>
</ul>
<hr />
<h2>Block 3 — <code>exports.getLawyers</code></h2>
<h3>3.1 — The wrapper</h3>
<pre><code class="language-js">exports.getLawyers = asyncHandler(async (req, res) =&gt; {
  // ...
});
</code></pre>
<ul>
<li><p><strong>[SE — DRY error handling]</strong> In plain Express, if an <code>async</code> function throws, Express does <strong>not</strong> automatically catch it — you'd normally need <code>try { ... } catch (err) { next(err); }</code> in every handler. <code>asyncHandler</code> is a small reusable wrapper (defined in <code>middleware/asyncHandler.js</code>) that does this once, generically, for whatever function you hand it. Every <code>throw new AppError(...)</code> you'll see below automatically gets routed to Express's central error handler because of this wrapper — no repeated boilerplate needed in each handler.</p>
</li>
<li><p><strong>[JS]</strong> <code>async (req, res) =&gt; {...}</code> is an arrow function marked <code>async</code>, meaning you can <code>await</code> promises inside it (see 3.10) and it always returns a promise itself.</p>
</li>
</ul>
<h3>3.2 — Destructuring the query string</h3>
<pre><code class="language-js">const {
  specialization, court, language, minFee, maxFee, minExperience, minRating, name,
  sortBy = 'rating', order = 'desc', page = 1, limit = 20,
} = req.query;
</code></pre>
<ul>
<li><p><strong>[JS — destructuring with defaults]</strong> Pulls each named property off <code>req.query</code> into its own variable in one line. <code>= 'rating'</code>, <code>= 'desc'</code>, <code>= 1</code>, <code>= 20</code> are <strong>default values</strong> — they only kick in if that specific key is <code>undefined</code> (i.e., the caller genuinely never sent it). A subtlety: the default only fires for <code>undefined</code>, not for an empty string — if <code>sortBy</code> were somehow <code>''</code>, the default would <em>not</em> apply.</p>
</li>
<li><p><strong>[Networking — query strings are text]</strong> <code>req.query</code> comes from the URL's query string (everything after <code>?</code>). HTTP doesn't have a "number type" in URLs — every value here, even <code>page</code> and <code>limit</code>, arrives as <strong>text</strong> (<code>"3"</code>, not <code>3</code>), unless it was never sent at all (in which case the JS default kicks in as a real number, since <code>1</code> and <code>20</code> above are written as numeric literals). This is why <code>Number(...)</code> shows up repeatedly further down — the code can't assume these are already numbers.</p>
</li>
</ul>
<h3>3.3 — The base "bookable" filter</h3>
<pre><code class="language-js">const match = { isProfileVisible: true, verificationStatus: 'approved' };
</code></pre>
<ul>
<li><p><strong>[SE — DRY violation, flagged]</strong> This exact object appears, retyped from scratch, in <code>getLawyerById</code> (Block 4), <code>getLawyerRankings</code> (Block 5), and <code>getLawyerAvailability</code> (Block 7) too. It's the file's one business rule for "is this lawyer real and currently bookable?" — but it has no single shared source of truth. If that rule ever needs a 5th condition (say, <code>isSuspended: false</code>), a developer has to remember to update all four spots; missing one creates a real bug.</p>
</li>
<li><p><strong>[MongoDB]</strong> This is a filter object that will be handed to <code>$match</code> (3.9) — MongoDB's equivalent of a SQL <code>WHERE</code> clause.</p>
</li>
</ul>
<h3>3.4 — Optional-auth blocked-lawyer exclusion</h3>
<pre><code class="language-js">if (req.user?.role === 'client') {
  const client = await Client.findOne({ userId: req.user.userId }).select('blockedLawyers');
  if (client?.blockedLawyers?.length) {
    match._id = { $nin: client.blockedLawyers };
  }
}
</code></pre>
<ul>
<li><p><strong>[Networking — optional auth on a public route]</strong> This whole route is reachable by anonymous, logged-out visitors — nothing forces a login. So <code>req.user</code> might simply not exist, and every check here has to account for that explicitly rather than assuming a logged-in caller.</p>
</li>
<li><p><strong>[JS — optional chaining</strong> <code>?.</code><strong>]</strong> <code>req.user?.role</code> reads: "if <code>req.user</code> is <code>null</code>/<code>undefined</code>, stop right there and produce <code>undefined</code> — don't try to read <code>.role</code> off of nothing." Without <code>?.</code>, this would throw a <code>TypeError</code> and crash the request whenever there's no logged-in user. Since <code>undefined === 'client'</code> is simply <code>false</code>, the <code>if</code> block is just skipped for anonymous visitors — no crash, no special-casing needed.</p>
</li>
<li><p><strong>[Mongoose —</strong> <code>.select()</code><strong>]</strong> <code>.select('blockedLawyers')</code> tells Mongoose to fetch <em>only</em> that one field (plus <code>_id</code>) instead of the client's entire document. This runs on every search request from a logged-in client, so keeping it minimal is worth it — there's no reason to pull someone's full profile just to check one array.</p>
</li>
<li><p><strong>[JS — chained optional chaining]</strong> <code>client?.blockedLawyers?.length</code> protects against <em>two</em> possible gaps: <code>client</code> itself could be <code>null</code> (defensively handled, even though it normally shouldn't be), and even if <code>client</code> exists, <code>blockedLawyers</code> could theoretically be missing. Either <code>?.</code> short-circuits safely to <code>undefined</code>, and <code>if (undefined)</code> is falsy, so the block is skipped.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$nin</code><strong>]</strong> If the client does have blocked lawyers, <code>match._id = { $nin: client.blockedLawyers }</code> means "exclude any lawyer whose <code>_id</code> is in this list." This is the actual enforcement point of "hide blocked lawyers from search results."</p>
</li>
<li><p><strong>[Security]</strong> This is a <em>server-side</em> enforcement of a privacy preference — the client never gets a chance to see a blocked lawyer in results at all, rather than the frontend being trusted to filter them out after the fact.</p>
</li>
</ul>
<h3>3.5 — Optional filters</h3>
<pre><code class="language-js">if (specialization) match.specialization = { $in: specialization.split(',') };
if (court) match.courtsPracticed = { $in: court.split(',') };
if (language) match.languages = { $in: language.split(',') };
if (minFee || maxFee) {
  match.consultationFee = {};
  if (minFee) match.consultationFee.$gte = Number(minFee);
  if (maxFee) match.consultationFee.$lte = Number(maxFee);
}
if (minExperience) match.yearsOfExperience = { $gte: Number(minExperience) };
if (minRating) match.rating = { $gte: Number(minRating) };
</code></pre>
<ul>
<li><p><strong>[Networking — comma-separated multi-value convention]</strong> <code>?specialization=property,criminal</code> is one common REST convention for "filter by more than one value on the same field." This code accepts a single query param and manually splits it, rather than the alternative convention of repeating the key (<code>?specialization=property&amp;specialization=criminal</code>).</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$in</code><strong>]</strong> Matches a document if its field's value overlaps with <em>any</em> value in the given array — so a lawyer practicing in either <code>property</code> or <code>criminal</code> law would match <code>specialization.split(',')</code> from that example.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$gte</code> <strong>/</strong> <code>$lte</code><strong>]</strong> "Greater than or equal" / "less than or equal." <code>minFee</code> and <code>maxFee</code> are built independently, so a caller can supply just one, both (giving a true "between X and Y" range), or neither.</p>
</li>
<li><p><strong>[JS —</strong> <code>Number(...)</code> <strong>conversions]</strong> Every one of these values arrived from <code>req.query</code> as text (see 3.2's networking note), so each gets explicitly converted with <code>Number(...)</code> before being compared numerically by MongoDB.</p>
</li>
<li><p><strong>[Security — gap, not present here]</strong> None of these values are clamped or bounds-checked (contrast with <code>getLawyerRankings</code>'s <code>limit</code> handling in Block 5.1) — not dangerous on their own, but worth noticing as an inconsistency within the same file.</p>
</li>
</ul>
<h3>3.6 — Sort field whitelist</h3>
<pre><code class="language-js">const sortFieldMap = {
  rating: 'rating',
  fee: 'consultationFee',
  experience: 'yearsOfExperience',
  consultations: 'totalConsultations',
};
const sortField = sortFieldMap[sortBy] || 'rating';
</code></pre>
<ul>
<li><p><strong>[Security — whitelisting via lookup table]</strong> The tempting shortcut would be <code>{ $sort: { [sortBy]: sortOrder } }</code> — sorting directly on whatever string the caller sent. The problem: nothing would then stop a caller from sending <code>?sortBy=verificationStatus</code> or an internal field name never meant to be exposed as a sort key. <code>sortFieldMap</code> is a <strong>whitelist</strong>: a fixed, hand-written set of the only four acceptable values. Anything else looked up on it returns <code>undefined</code>.</p>
</li>
<li><p><strong>[JS — bracket notation with a dynamic key]</strong> <code>sortFieldMap[sortBy]</code> looks up a property using the <em>value currently stored in</em> <code>sortBy</code> — different from <code>sortFieldMap.sortBy</code>, which would look for a literal property named <code>"sortBy"</code> (which doesn't exist).</p>
</li>
<li><p><strong>[JS —</strong> <code>||</code> <strong>fallback, used correctly]</strong> If the lookup returns <code>undefined</code> (an unrecognized <code>sortBy</code>), <code>|| 'rating'</code> supplies a safe default. <code>||</code> (not <code>??</code>) is fine here because <code>sortFieldMap[sortBy]</code> can only ever be a real string or <code>undefined</code> — never a legitimate <code>0</code>/<code>''</code>/<code>false</code> that <code>||</code> might wrongly overwrite (contrast with Block 5.3, where that distinction matters a lot).</p>
</li>
</ul>
<h3>3.7 — Sort direction</h3>
<pre><code class="language-js">const sortOrder = order === 'asc' ? 1 : -1;
</code></pre>
<ul>
<li><p><strong>[JS — ternary]</strong> A compact if/else that <em>produces a value</em>. If <code>order === 'asc'</code> is true, this expression is <code>1</code>; otherwise <code>-1</code>.</p>
</li>
<li><p><strong>[MongoDB]</strong> MongoDB's <code>$sort</code> only understands <code>1</code> (ascending) / <code>-1</code> (descending) — not the words <code>"asc"</code>/<code>"desc"</code> — so this line is translating the human-readable query param into what MongoDB actually expects. Anything other than exactly <code>"asc"</code> (a typo, or no <code>order</code> at all) silently falls through to <code>-1</code>.</p>
</li>
</ul>
<h3>3.8 — Pagination math</h3>
<pre><code class="language-js">const skip = (Number(page) - 1) * Number(limit);
</code></pre>
<ul>
<li><p><strong>[JS]</strong> <code>page</code> and <code>limit</code> are converted to real numbers first (see 3.2's networking note about why this matters — <code>"3" - 1</code> happens to coerce to <code>2</code> automatically in JS, but relying on that inconsistently across operators like <code>+</code> is a well-known trap, so this code converts explicitly instead of leaning on it).</p>
</li>
<li><p><strong>[MongoDB/Algorithm — offset pagination]</strong> For page 1, <code>skip = 0</code>. For page 2 with <code>limit = 20</code>, <code>skip = 20</code> — skip the first 20 matches, then return the next batch. This is standard <em>offset pagination</em>. Worth knowing as a tradeoff, not a bug: <code>$skip</code> gets slower the deeper you page, because MongoDB genuinely walks past every skipped document rather than jumping straight to a spot. Fine at moderate scale; a different technique (cursor-based pagination) scales better for very deep pages but is more complex.</p>
</li>
</ul>
<h3>3.9 — Building the aggregation pipeline</h3>
<pre><code class="language-js">const basePipeline = [
  { $match: match },
  { $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } },
  { $unwind: '$user' },
  ...(name ? [{ $match: { 'user.name': { $regex: name, $options: 'i' } } }] : []),
];
</code></pre>
<ul>
<li><p><strong>[MongoDB — aggregation pipeline]</strong> An aggregation pipeline is a sequence of stages, each one transforming the output of the stage before it — like a factory line. This lets multi-step data processing (filtering, joining, computing) happen <em>inside</em> the database instead of in application code.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$match</code><strong>]</strong> Runs first, deliberately — narrowing the working set (and letting MongoDB use indexes) before the more expensive join happens next.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$lookup</code><strong>]</strong> A server-side join, conceptually like SQL's <code>JOIN</code>. <code>from: 'users'</code> is the raw MongoDB collection name (lowercase, pluralized — Mongoose's naming convention for a model called <code>User</code>). <code>localField</code>/<code>foreignField</code> say which fields to match on; <code>as: 'user'</code> names where the joined data lands. This runs as one join inside MongoDB itself, in the same query — unlike <code>.populate()</code> (see Block 4), which is a separate follow-up query.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$unwind</code><strong>]</strong> <code>$lookup</code> always produces an <em>array</em> (<code>user: [...]</code>), even though there's guaranteed to be exactly one matching <code>User</code> (the schema enforces <code>userId: { unique: true }</code>). <code>$unwind</code> flattens that single-element array into a plain object, <code>user: {...}</code> — which is what makes <code>'user.name'</code> a valid path in the next line and in <code>formatLawyer</code>.</p>
</li>
<li><p><strong>[JS — conditional array spread]</strong> <code>...(name ? [{...}] : [])</code> evaluates to either a one-element array (spread into <code>basePipeline</code> as a real 4th stage) or an empty array (spreading in nothing at all). This is the array equivalent of an <code>if</code> statement, used because you can't easily "not add an element" with a plain <code>if</code> inside an array literal.</p>
</li>
<li><p><strong>[Security — flagged, real gap]</strong> <code>name</code> comes straight from <code>req.query.name</code> and flows directly into MongoDB's <code>$regex</code> with <strong>no escaping</strong>. <code>$regex</code> interprets its input as an actual regex pattern (special characters like <code>.</code>, <code>*</code>, <code>(</code>, <code>|</code> change meaning), not plain text. A caller could craft a deliberately expensive pattern that makes MongoDB slow to evaluate against every document — a <strong>ReDoS</strong> (Regular Expression Denial of Service) risk. The fix — not present in this file — is to escape regex-special characters in <code>name</code> before using it, so it only ever matches literal text.</p>
</li>
</ul>
<h3>3.10 — Running both queries concurrently</h3>
<pre><code class="language-js">const [lawyersRaw, countResult] = await Promise.all([
  Lawyer.aggregate([
    ...basePipeline,
    { $sort: { [sortField]: sortOrder } },
    { $skip: skip },
    { $limit: Number(limit) },
    { $project: { ...LAWYER_FIELDS, 'user._id': 1, 'user.name': 1 } },
  ]),
  Lawyer.aggregate([...basePipeline, { $count: 'total' }]),
]);
</code></pre>
<ul>
<li><p><strong>[Concurrency —</strong> <code>Promise.all</code><strong>]</strong> These two aggregations are independent — neither needs the other's result. Running them with two separate <code>await</code>s would mean waiting for the first to fully finish before even starting the second. <code>Promise.all([...])</code> starts both at roughly the same time, and the combined <code>await</code> resolves once <em>both</em> finish — total wait time is close to whichever is slower, not the sum of both.</p>
</li>
<li><p><strong>[JS — array destructuring of the result]</strong> <code>Promise.all</code> resolves to an array of results in the <em>same order</em> the promises were given. <code>const [lawyersRaw, countResult] = ...</code> pulls the first aggregation's result into <code>lawyersRaw</code> and the second's into <code>countResult</code>, purely by position.</p>
</li>
<li><p><strong>[JS — computed property name]</strong> <code>{ [sortField]: sortOrder }</code> uses the <em>value</em> stored in <code>sortField</code> (e.g. <code>"consultationFee"</code>) as the object key, not the literal text <code>"sortField"</code>.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$sort</code> <strong>/</strong> <code>$skip</code> <strong>/</strong> <code>$limit</code><strong>]</strong> Order, offset, and page size, applied in that sequence.</p>
</li>
<li><p><strong>[Security — flagged gap]</strong> Unlike <code>getLawyerRankings</code> (Block 5.1), <code>limit</code> here is never clamped — a caller could send <code>?limit=999999999</code> and force MongoDB to try building a huge result set in one go. This is a real resource-exhaustion risk, and notably the developer clearly <em>knows</em> the clamping pattern (they use it elsewhere in this same file) but didn't apply it here.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$project</code><strong>]</strong> Spreads <code>LAWYER_FIELDS</code> (Block 1) plus two more dotted paths reaching into the <code>user</code> sub-object created by <code>$unwind</code>.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$count</code><strong>]</strong> Collapses the whole pipeline's current result set into one document, <code>{ total: &lt;number&gt; }</code>. If zero documents match, the output is an <strong>empty array</strong>, not <code>[{ total: 0 }]</code> — which matters for the next block.</p>
</li>
<li><p><strong>[SE — why run this as a second, separate query]</strong> <code>$skip</code>/<code>$limit</code> only return the current page; there's no way to also learn "and there were 347 total matches" from that same query. So the file runs the filtering logic twice — once as a realized page of documents, once collapsed into a count — both starting from the identical <code>basePipeline</code> so they always agree on what "counts" as a match.</p>
</li>
</ul>
<h3>3.11 — Total and formatted list</h3>
<pre><code class="language-js">const total = countResult[0]?.total || 0;
const lawyers = lawyersRaw.map(formatLawyer);
</code></pre>
<ul>
<li><p><strong>[JS — optional chaining +</strong> <code>||</code> <strong>together]</strong> <code>countResult[0]</code> could be <code>undefined</code> (an empty array has no index <code>0</code>, per the <code>$count</code> note above). <code>?.total</code> safely handles that. <code>|| 0</code> then supplies a plain <code>0</code> if either <code>countResult[0]</code> was missing or <code>.total</code> itself was somehow falsy.</p>
</li>
<li><p><strong>[JS — passing a function reference to</strong> <code>.map()</code><strong>]</strong> <code>.map(formatLawyer)</code> hands <code>.map()</code> the function itself (no wrapping arrow function needed), because <code>formatLawyer</code> already takes exactly the one argument <code>.map()</code> provides per element and returns exactly what's needed for each output element.</p>
</li>
</ul>
<h3>3.12 — Response</h3>
<pre><code class="language-js">return sendSuccess(res, 200, { lawyers }, paginationMeta({ total, page, limit }));
</code></pre>
<ul>
<li><p><strong>[SE — consistent response envelope]</strong> Every success response in the whole API funnels through <code>sendSuccess</code>, so every endpoint's response has the same overall shape regardless of which handler produced it.</p>
</li>
<li><p><strong>[Networking — pagination metadata]</strong> <code>lawyers</code> alone wouldn't tell a frontend how many total matches exist or which page it's viewing. <code>paginationMeta({ total, page, limit })</code> builds that context so a UI can render something like "Page 2 of 18."</p>
</li>
</ul>
<hr />
<h2>Block 4 — <code>exports.getLawyerById</code></h2>
<pre><code class="language-js">exports.getLawyerById = asyncHandler(async (req, res) =&gt; {
  const lawyer = await Lawyer.findOne({
    _id: req.params.id,
    isProfileVisible: true,
    verificationStatus: 'approved',
  })
    .populate('userId', 'name')
    .lean();

  if (!lawyer) {
    throw new AppError('Lawyer not found.', 404);
  }

  const formatted = formatLawyer({ ...lawyer, user: lawyer.userId });

  let isSaved = false;
  let isBlocked = false;
  if (req.user) {
    const client = await Client.findOne({ userId: req.user.userId });
    if (client) {
      isSaved = client.savedLawyers.some((id) =&gt; id.equals(lawyer._id));
      isBlocked = client.blockedLawyers.some((id) =&gt; id.equals(lawyer._id));
    }
  }

  return sendSuccess(res, 200, { lawyer: formatted, isSaved, isBlocked });
});
</code></pre>
<ul>
<li><p><strong>[SE — same DRY gap as 3.3]</strong> The <code>isProfileVisible: true, verificationStatus: 'approved'</code> filter is retyped here, identically, for the second of four times in this file.</p>
</li>
<li><p><strong>[Mongoose —</strong> <code>.findOne()</code> <strong>+</strong> <code>.populate()</code><strong>]</strong> <code>Lawyer</code> documents store <code>userId</code> as just a reference (an ObjectId) to a separate <code>User</code> document. <code>.populate('userId', 'name')</code> tells Mongoose "also fetch that referenced <code>User</code> and swap the raw ID for the actual object (just its <code>name</code> field)." This runs as a <em>separate second query</em> under the hood — a reasonable cost for one document, but not something you'd want per-item in a list (see 3.9's <code>$lookup</code> note for why lists use aggregation instead).</p>
</li>
<li><p><strong>[Mongoose —</strong> <code>.lean()</code><strong>]</strong> Without <code>.lean()</code>, a Mongoose query result is wrapped in a full Document instance (with <code>.save()</code>, change tracking, etc.) — overhead this handler never needs, since it only reads and reshapes data, never writes it back. <code>.lean()</code> returns a plain object instead, skipping that machinery.</p>
</li>
<li><p><strong>[Security — information hiding via identical errors]</strong> Two very different situations — "this <code>_id</code> doesn't exist at all" and "this <code>_id</code> exists but the lawyer is unapproved/hidden" — both make <code>lawyer</code> come back <code>null</code> here, because the visibility filter is baked into the <em>same</em> <code>findOne</code> call as the <code>_id</code> check. Both produce the exact same 404. This deliberately prevents an outside visitor from being able to tell "no such lawyer" apart from "that lawyer is suspended," which would otherwise leak information about lawyer status.</p>
</li>
<li><p><strong>[Networking — 404 semantics]</strong> <code>404</code> here means "not found <em>from this caller's point of view</em>" — a deliberate, security-motivated use of the status code (contrast with the <code>400</code>s in <code>toUtcMidnight</code>, Block 6, which mean "you sent something malformed").</p>
</li>
<li><p><strong>[JS — object spread with key override]</strong> <code>{ ...lawyer, user: lawyer.userId }</code> copies every property off <code>lawyer</code> into a new object, then adds/overwrites a <code>user</code> key pointing at <code>lawyer.userId</code> — because <code>formatLawyer</code> expects a <code>user</code> key, but after <code>.populate()</code> the populated data actually sits under <code>userId</code> (matching the schema's own field name). Because <code>user: lawyer.userId</code> is written <em>after</em> the spread, it wins if there was any conflict. The original <code>lawyer</code> object is never mutated — spread always builds a new object.</p>
</li>
<li><p><strong>[JS —</strong> <code>let</code> <strong>vs</strong> <code>const</code><strong>]</strong> <code>isSaved</code>/<code>isBlocked</code> start as <code>false</code> but might get reassigned below depending on login state — <code>const</code> would be a syntax error here since reassignment is exactly what happens.</p>
</li>
<li><p><strong>[Networking — optional auth]</strong> <code>if (req.user)</code> — this route works for anonymous visitors too; personalization (<code>isSaved</code>/<code>isBlocked</code>) only happens for logged-in callers.</p>
</li>
<li><p><strong>[JS —</strong> <code>Array.prototype.some()</code><strong>]</strong> Runs a test against every array element and returns <code>true</code> the moment any element passes (stopping early once it finds a match). Here: "does this client's saved/blocked list contain this lawyer's ID?"</p>
</li>
<li><p><strong>[Mongoose —</strong> <code>ObjectId.equals()</code><strong>, not</strong> <code>===</code><strong>]</strong> MongoDB IDs are objects, not primitives — two <code>ObjectId</code> instances representing the <em>same</em> underlying ID are still different objects in memory, so <code>===</code> between them is <code>false</code> even for a genuine match (and <code>.includes()</code> would break the same way, since it uses <code>===</code> internally). <code>.equals()</code> does a real value comparison instead. <strong>Always use</strong> <code>.equals()</code> <strong>for ObjectId comparisons in Mongoose, never</strong> <code>===</code> <strong>or</strong> <code>.includes()</code><strong>.</strong></p>
</li>
<li><p><strong>[SE — reused</strong> <code>formatLawyer</code><strong>]</strong> Same shared function from Block 2, guaranteeing this response's <code>lawyer</code> field looks identical in shape to what <code>getLawyers</code> returns.</p>
</li>
</ul>
<hr />
<h2>Block 5 — <code>exports.getLawyerRankings</code></h2>
<h3>5.1 — Setup and clamped pagination</h3>
<pre><code class="language-js">const { specialization, page = 1, limit = 50 } = req.query;
const pageNumber = Math.max(1, Number(page) || 1);
const limitNumber = Math.min(100, Math.max(1, Number(limit) || 50));
</code></pre>
<ul>
<li><p><strong>[Security — clamping, done right here]</strong> Unlike <code>getLawyers</code> (3.10), <code>limitNumber</code> is explicitly bounded between <code>1</code> and <code>100</code> no matter what the caller sends — <code>?limit=999999999</code> can't force a huge query here. <code>Math.max(1, ...)</code> prevents <code>0</code> or negative values; <code>Math.min(100, ...)</code> prevents excessively large ones.</p>
</li>
<li><p><strong>[JS]</strong> <code>Number(limit) || 50</code> falls back to <code>50</code> if <code>Number(limit)</code> is <code>NaN</code> (e.g. someone sent <code>?limit=abc</code>) — <code>NaN</code> is falsy, so <code>||</code> catches that case too, on top of the destructuring default only covering a totally missing param.</p>
</li>
</ul>
<h3>5.2 — Match filter and base pipeline</h3>
<pre><code class="language-js">const match = { isProfileVisible: true, verificationStatus: 'approved' };
if (specialization) match.specialization = { $in: specialization.split(',') };

const basePipeline = [
  { $match: match },
  { $lookup: { from: 'users', localField: 'userId', foreignField: '_id', as: 'user' } },
  { $unwind: '$user' },
];
</code></pre>
<ul>
<li><p><strong>[SE — the DRY gap again]</strong> Third appearance of the bookable filter, retyped from scratch.</p>
</li>
<li><p>Same <code>$match</code> / <code>$lookup</code> / <code>$unwind</code> mechanics as Block 3.9, minus the <code>name</code>-regex stage (this endpoint doesn't support name search).</p>
</li>
</ul>
<h3>5.3 — Getting the value range and total, concurrently</h3>
<pre><code class="language-js">const [rangeResult, countResult] = await Promise.all([
  Lawyer.aggregate([
    { $match: match },
    { $group: { _id: null, min: { $min: '$totalConsultations' }, max: { $max: '$totalConsultations' } } },
  ]),
  Lawyer.aggregate([{ $match: match }, { $count: 'total' }]),
]);

const total = countResult[0]?.total || 0;
const range = rangeResult[0];
const minConsultations = range?.min ?? 0;
const maxConsultations = range?.max ?? 0;
const flatVolume = maxConsultations === minConsultations;
</code></pre>
<ul>
<li><p><strong>[Concurrency]</strong> Same <code>Promise.all</code> reasoning as 3.10 — the min/max computation and the total count don't depend on each other, so both run at once.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$group</code> <strong>with</strong> <code>_id: null</code><strong>]</strong> Normally <code>$group</code> buckets documents by some field's distinct values. <code>_id: null</code> means "don't bucket at all — collapse every matched document into one single group," which is what lets <code>$min</code>/<code>$max</code> compute across the <em>entire</em> matched set at once.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$min</code> <strong>/</strong> <code>$max</code> <strong>accumulators]</strong> Computed inside the database, in one pass, across every matched document — far cheaper than pulling every lawyer's raw <code>totalConsultations</code> back to the app server just to run <code>Math.min</code>/<code>Math.max</code> in JavaScript.</p>
</li>
<li><p><strong>[JS — nullish coalescing</strong> <code>??</code><strong>, not</strong> <code>||</code><strong>]</strong> This is the one spot in the file where the <code>??</code> vs <code>||</code> distinction genuinely matters: if a brand-new approved lawyer has done <strong>zero</strong> consultations, <code>range.min</code> could legitimately be <code>0</code> — a real, meaningful value, not "missing data." <code>||</code> would treat that <code>0</code> as falsy and wrongly try to replace it; <code>??</code> only falls back when the value is truly <code>null</code>/<code>undefined</code> (i.e., <code>range</code> itself is <code>undefined</code> because zero lawyers matched at all). Compare this directly with 3.6, where <code>||</code> was the <em>right</em> tool — same operator family, opposite correctness depending on whether <code>0</code> can be a legitimate value.</p>
</li>
<li><p><strong>[Algorithm — division-by-zero guard]</strong> <code>flatVolume</code> precomputes whether <code>max === min</code> (every matched lawyer tied, or only one/zero matched) <em>before</em> the next block's formula would otherwise divide by <code>(max - min) = 0</code>. This is checked deliberately rather than letting the pipeline silently produce <code>NaN</code>/<code>Infinity</code>.</p>
</li>
</ul>
<h3>5.4 — Skip, and the scoring pipeline</h3>
<pre><code class="language-js">const skip = (pageNumber - 1) * limitNumber;

const rankingsRaw = await Lawyer.aggregate([
  ...basePipeline,
  {
    $addFields: {
      ratingScore: { $cond: [{ $gt: ['$rating', 0] }, { $divide: ['$rating', 5] }, 0] },
      volumeScore: flatVolume
        ? 0.5
        : { $divide: [{ $subtract: ['$totalConsultations', minConsultations] }, { $subtract: [maxConsultations, minConsultations] }] },
    },
  },
  {
    $addFields: {
      score: { $multiply: [{ $add: [{ $multiply: [0.4, '$ratingScore'] }, { $multiply: [0.6, '$volumeScore'] }] }, 100] },
    },
  },
  { $sort: { score: -1, rating: -1, totalConsultations: -1, _id: 1 } },
  { $skip: skip },
  { $limit: limitNumber },
  { $project: { ...LAWYER_FIELDS, 'user._id': 1, 'user.name': 1, score: 1 } },
]);
</code></pre>
<ul>
<li><p><strong>[MongoDB —</strong> <code>$cond</code><strong>]</strong> <code>[condition, thenValue, elseValue]</code>. <code>ratingScore</code> reads: "if <code>rating &gt; 0</code>, compute <code>rating / 5</code>; otherwise <code>0</code>." (Mathematically <code>0 / 5</code> is also <code>0</code>, so the guard is somewhat redundant here — but it states the <em>intent</em> explicitly rather than relying on that coincidence.)</p>
</li>
<li><p><strong>[Algorithm — min-max normalization]</strong> <code>volumeScore</code>'s formula, when not flat, is <code>(totalConsultations - min) / (max - min)</code> — the standard way to rescale any raw number into a fair <code>0</code>–<code>1</code> range relative to the current matched set. When <code>flatVolume</code> is true, everyone instead gets a neutral <code>0.5</code> — the mathematically honest answer when there's no real variation to normalize against.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$divide</code> <strong>/</strong> <code>$subtract</code> <strong>/</strong> <code>$multiply</code> <strong>/</strong> <code>$add</code><strong>]</strong> MongoDB's aggregation syntax for ordinary arithmetic, written as <code>{ $operator: [a, b] }</code> instead of <code>a - b</code> etc., computed per-document, inside the database.</p>
</li>
<li><p><strong>[MongoDB — two sequential</strong> <code>$addFields</code> <strong>stages]</strong> The second stage's <code>score</code> formula references <code>'$ratingScore'</code> and <code>'$volumeScore'</code>, both defined by the <em>first</em> stage. Fields being computed within a single <code>$addFields</code> stage generally can't reliably reference each other yet — splitting into two stages guarantees stage 1 fully finishes and attaches both scores before stage 2 reads them.</p>
</li>
<li><p><strong>[Algorithm — weighted composite score]</strong> <code>score = (0.4 * ratingScore + 0.6 * volumeScore) * 100</code> — two <code>0</code>–<code>1</code> sub-scores combined with explicit weights (40% rating, 60% volume) and scaled to a friendlier <code>0</code>–<code>100</code> range.</p>
</li>
<li><p><strong>[Algorithm — multi-key deterministic sort]</strong> <code>{ score: -1, rating: -1, totalConsultations: -1, _id: 1 }</code> sorts by <code>score</code> first, but if two lawyers tie exactly, falls through to <code>rating</code>, then <code>totalConsultations</code>, then finally <code>_id</code> — which is guaranteed unique, so this last tiebreaker eliminates <em>all</em> possible ties. Without a fully deterministic sort, tied documents' relative order across separate page-1/page-2 requests would technically be unspecified — meaning a lawyer could seem to vanish or duplicate between pages purely due to how MongoDB happened to break the tie that time.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$skip</code><strong>/</strong><code>$limit</code><strong>/</strong><code>$project</code><strong>]</strong> Same mechanics as Block 3.10, with <code>score: 1</code> added to the projection so it survives into the output.</p>
</li>
</ul>
<h3>5.5 — Shaping and responding</h3>
<pre><code class="language-js">const rankings = rankingsRaw.map((raw, index) =&gt; ({
  rank: skip + index + 1,
  score: Math.round(raw.score * 10) / 10,
  lawyer: formatLawyer(raw),
}));

return sendSuccess(res, 200, { rankings }, paginationMeta({ total, page: pageNumber, limit: limitNumber }));
</code></pre>
<ul>
<li><p><strong>[JS —</strong> <code>.map()</code><strong>'s second</strong> <code>index</code> <strong>argument]</strong> <code>.map()</code>'s callback receives the current element's position (<code>0</code>-based) as a second parameter.</p>
</li>
<li><p><strong>[Algorithm — global rank under pagination]</strong> <code>skip + index + 1</code> converts "this lawyer's position <em>within the current page</em>" into "this lawyer's position across the <em>entire</em> sorted dataset." This only works because the sort (5.4) ran on the whole matched set <em>before</em> <code>$skip</code>/<code>$limit</code> narrowed it down — sort first, paginate second. The <code>+1</code> exists because ranks conventionally start at 1, not 0.</p>
</li>
<li><p><strong>[JS — multiply/round/divide for decimal precision]</strong> <code>Math.round()</code> only rounds to whole numbers — there's no built-in "round to 1 decimal" in plain JS. Multiplying by 10 first, rounding, then dividing by 10 again is the standard workaround (e.g. <code>87.36 → 873.6 → 874 → 87.4</code>).</p>
</li>
<li><p><strong>[SE]</strong> Same shared <code>sendSuccess</code>/<code>paginationMeta</code>/<code>formatLawyer</code> reuse as the earlier handlers.</p>
</li>
</ul>
<hr />
<h2>Block 6 — <code>toUtcMidnight(dateInput)</code> (date helper)</h2>
<pre><code class="language-js">function toUtcMidnight(dateInput) {
  const d = new Date(dateInput);
  if (Number.isNaN(d.getTime())) {
    throw new AppError('Invalid date.', 400);
  }
  return new Date(Date.UTC(d.getUTCFullYear(), d.getUTCMonth(), d.getUTCDate()));
}
</code></pre>
<ul>
<li><p><strong>[Date/Time —</strong> <code>Date</code> <strong>leniency and Invalid Date]</strong> JavaScript's <code>Date</code> constructor is very forgiving about input formats, but when it genuinely can't parse something, it doesn't throw — it silently produces an <strong>Invalid Date</strong>: an object that <em>looks</em> like a normal <code>Date</code> but represents no real point in time.</p>
</li>
<li><p><strong>[JS — detecting it correctly with</strong> <code>Number.isNaN</code><strong>]</strong> <code>d.getTime()</code> returns <code>NaN</code> for an Invalid Date. You can't check this with <code>d.getTime() === NaN</code> — by a quirk of the underlying numeric standard, <code>NaN</code> never equals anything, including itself. <code>Number.isNaN(...)</code> is the built-in function specifically designed to detect this correctly.</p>
</li>
<li><p><strong>[SE —</strong> <code>AppError</code><strong>]</strong> Throwing <code>new AppError('Invalid date.', 400)</code> lets this single <code>throw</code> carry both a message and an HTTP status code, which the <code>asyncHandler</code> wrapper (Block 3.1) routes to Express's central error handler — no manual <code>res.status(400).json(...)</code> needed here.</p>
</li>
<li><p><strong>[Date/Time — timezone normalization to UTC midnight]</strong> <code>Date.UTC(year, month, day)</code> builds a timestamp for exactly midnight UTC on a given calendar date, discarding whatever time-of-day or timezone the original input carried. This matches how <code>Availability</code> documents store their own <code>date</code> field. Because MongoDB date comparisons are <em>exact timestamp</em> comparisons — not "same calendar day, loosely interpreted" — a raw, un-normalized date like <code>2026-07-20T15:30:00+05:30</code> would <strong>not</strong> equal <code>2026-07-20T00:00:00.000Z</code>, and a query using it as-is would silently fail to match documents the caller clearly meant. This function guarantees every date this file uses lines up with that storage convention, regardless of how the caller originally formatted it.</p>
</li>
<li><p><strong>[JS — pure function]</strong> Same input always produces the same output (or the same thrown error) — no side effects, safe to call from multiple places (it's used twice in Block 7).</p>
</li>
</ul>
<hr />
<h2>Block 7 — <code>exports.getLawyerAvailability</code></h2>
<h3>7.1 — Confirming the lawyer is bookable</h3>
<pre><code class="language-js">const lawyer = await Lawyer.findOne({
  _id: req.params.id,
  isProfileVisible: true,
  verificationStatus: 'approved',
});
if (!lawyer) {
  throw new AppError('Lawyer not found.', 404);
}
</code></pre>
<ul>
<li><p><strong>[SE — fourth appearance of the DRY gap]</strong> Same bookable filter, retyped a fourth time.</p>
</li>
<li><p><strong>[Security — same identical-error info hiding as Block 4]</strong> "Doesn't exist" and "exists but not bookable" again produce the same 404 here.</p>
</li>
</ul>
<h3>7.2 — Date range, with validation</h3>
<pre><code class="language-js">const { from, to } = req.query;
const fromDate = from ? toUtcMidnight(from) : toUtcMidnight(new Date());
const filter = { lawyerId: lawyer._id, date: { $gte: fromDate } };
if (to) {
  const toDate = toUtcMidnight(to);
  if (toDate &lt; fromDate) {
    throw new AppError('to must be on or after from.', 400);
  }
  filter.date.$lte = toDate;
}
</code></pre>
<ul>
<li><p><strong>[Date/Time]</strong> If <code>from</code> isn't supplied, it defaults to <strong>today</strong> (normalized via Block 6), not "the beginning of time" — a sensible default for a booking feature, since you can't book a slot in the past.</p>
</li>
<li><p><strong>[MongoDB —</strong> <code>$gte</code><strong>/</strong><code>$lte</code> <strong>again, this time on dates]</strong> Same range-filter pattern as Block 3.5, applied to <code>date</code> instead of <code>consultationFee</code>.</p>
</li>
<li><p><strong>[SE — explicit validation instead of a silently empty result]</strong> Without the <code>toDate &lt; fromDate</code> check, sending an impossible range (<code>to</code> before <code>from</code>) would just make MongoDB return zero results — indistinguishable, from the caller's side, from "no availability in a perfectly valid range." This code deliberately catches the nonsensical input <em>before</em> querying and returns a clear <code>400</code>, rather than letting a bug hide behind an innocent-looking empty array.</p>
</li>
<li><p><strong>[Networking — 400 here, not 404]</strong> This is a genuinely malformed/logically-invalid request (the client should change what it sent), which is exactly what <code>400</code> conventionally signals — contrast with the <code>404</code>s in 7.1 and Block 4, which mean "the request was fine, but the resource doesn't exist/isn't visible."</p>
</li>
</ul>
<h3>7.3 — Fetching and reshaping</h3>
<pre><code class="language-js">const availabilityDocs = await Availability.find(filter).sort({ date: 1 }).lean();
const now = new Date();

const availability = availabilityDocs
  .map((doc) =&gt; ({
    availabilityId: doc._id,
    date: doc.date,
    slots: doc.slots
      .filter((slot) =&gt; !slot.isBooked &amp;&amp; new Date(slot.startTime) &gt; now)
      .map((slot) =&gt; ({ slotId: slot._id, startTime: slot.startTime, endTime: slot.endTime })),
  }))
  .filter((day) =&gt; day.slots.length &gt; 0);

return sendSuccess(res, 200, { availability });
</code></pre>
<ul>
<li><p><strong>[Mongoose —</strong> <code>.lean()</code> <strong>again]</strong> Same reasoning as Block 4 — this is a pure read, so there's no need for the full Document machinery.</p>
</li>
<li><p><strong>[Date/Time — capturing</strong> <code>now</code> <strong>once]</strong> <code>now</code> is captured a single time before filtering starts, rather than calling <code>new Date()</code> fresh inside the loop for every slot — slightly more efficient, and avoids any inconsistency if the loop somehow spanned real time.</p>
</li>
<li><p><strong>[JS — chained</strong> <code>.map().filter().map().filter()</code><strong>]</strong> Read top to bottom as a pipeline:</p>
<ol>
<li><p><strong>Outer</strong> <code>.map()</code> — for each <code>Availability</code> document (one per calendar day), build a new object with a renamed <code>availabilityId</code> and a <code>slots</code> array that's itself the result of steps 2–3.</p>
</li>
<li><p><strong>Inner</strong> <code>.filter()</code> — keep only slots where <code>!slot.isBooked</code> (not taken) <strong>and</strong> <code>new Date(slot.startTime) &gt; now</code> (still in the future) — both must be true.</p>
</li>
<li><p><strong>Inner</strong> <code>.map()</code> — rebuild each surviving slot with exactly three fields.</p>
</li>
<li><p><strong>Outer</strong> <code>.filter()</code> — after the outer <code>.map()</code> finishes, drop any day whose <code>slots</code> array ended up empty, so the response doesn't include clutter like <code>{ date: ..., slots: [] }</code>.</p>
</li>
</ol>
</li>
<li><p><strong>[Date/Time — "only future" filtering]</strong> <code>new Date(slot.startTime) &gt; now</code> works because JS automatically converts <code>Date</code> objects to their millisecond-timestamp numbers for comparison — so this really means "is this slot's timestamp later than the current timestamp."</p>
</li>
<li><p><strong>[Security — excluding internal fields]</strong> The inner <code>.map()</code> deliberately builds a brand-new object with only <code>slotId</code>/<code>startTime</code>/<code>endTime</code> — leaving out the raw slot's <code>bookingId</code>, which would otherwise reveal internal booking-record identifiers to an anonymous browsing visitor with no legitimate reason to see them. Same principle as <code>LAWYER_FIELDS</code> (Block 1), applied to a different data shape.</p>
</li>
<li><p><strong>[SE]</strong> Final response again goes through the shared <code>sendSuccess</code> envelope.</p>
</li>
</ul>
<hr />
]]></content:encoded></item></channel></rss>