# Express Server Notes

**What problem does each part solve, and how does a request travel through the backend?**

## 1\. Server setup:

```text
Import packages
     ↓
Create Express app
     ↓
Add middleware
     ↓
Connect database
     ↓
Create auth/helpers
     ↓
Create routes/controllers
     ↓
Start server
```

When a request arrives:

```text
CLIENT
  ↓
REQUEST
  ↓
MIDDLEWARE
  ↓
CONTROLLER
  ↓
DATABASE
  ↓
RESPONSE
```

* * *

# 2\. Importing Packages

```js
const express = require('express');
const mongoose = require('mongoose');
const jwt = require('jsonwebtoken');
```

*   **Express** → creates the backend/server.
    
*   **Mongoose** → communicates with MongoDB.
    
*   **jsonwebtoken** → creates and verifies JWTs.
    

```text
Node.js → Express → Server
Node.js → Mongoose → MongoDB
Node.js → JWT → Authentication
```

* * *

# 3\. Importing Our Own Models

```js
const User = require('./User');
const Client = require('./Client');
```

`./User` means:

> Go to `User.js` and give me what that file exports.

The models define how our MongoDB data is structured.

* * *

# 4\. Creating the Express App

```js
const app = express();
```

`express()` creates an Express application.

We store it in `app`, which is why we can use:

```js
app.get(...)
app.post(...)
app.use(...)
app.listen(...)
```

Think:

```text
express()
   ↓
Express application
   ↓
app
```

* * *

# 5\. JSON Middleware

```js
app.use(express.json());
```

If Postman sends:

```json
{
    "bio": "I am learning backend"
}
```

`express.json()` parses it so we can access:

```js
req.body
```

Therefore:

```text
JSON request
    ↓
express.json()
    ↓
req.body
```

* * *

# 6\. Connecting MongoDB

```js
await mongoose.connect(
    'mongodb://127.0.0.1:27017/backendPractice'
);
```

Meaning:

```text
127.0.0.1 → this computer
27017      → MongoDB port
backendPractice → database name
```

A clean startup flow is:

```text
startServer()
     ↓
connect MongoDB
     ↓
successful?
   ↙       ↘
 yes       no
 ↓         ↓
listen    error
```

So the server starts accepting requests only after the database connection succeeds.

* * *

# 7\. `.then()` and `.catch()`

For a promise:

```js
mongoose.connect(...)
    .then(() => {
        console.log('Connected');
    })
    .catch((error) => {
        console.log(error);
    });
```

Think:

```text
Try operation
   ↓
 ┌───────┴───────┐
success         failure
   ↓               ↓
 then            catch
```

* * *

# 8\. Middleware

Middleware sits between the request and the controller.

```text
REQUEST
   ↓
middleware
   ↓
controller
   ↓
response
```

It receives:

```js
(req, res, next)
```

*   `req` → incoming request
    
*   `res` → response
    
*   `next` → continue to the next step
    

Middleware can:

*   authenticate
    
*   validate
    
*   log
    
*   modify `req`
    
*   block requests
    

* * *

# 9\. `req.params`, `req.body`, `req.headers`

These are three important sources of request information.

### `req.params`

For:

```js
app.get('/generate-token/:userId', ...)
```

Request:

```text
/generate-token/ABC123
```

Then:

```js
req.params.userId
```

is:

```text
ABC123
```

Used for values inside the URL.

### `req.body`

For:

```json
{
    "bio": "Hello"
}
```

we get:

```js
req.body.bio
```

Used for data sent inside the request body.

### `req.headers`

For:

```text
Authorization: Bearer XYZ...
```

we can access:

```js
req.headers.authorization
```

Used for request metadata such as authentication information.

* * *

# 10\. Getting the JWT

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

Suppose:

```text
Bearer eyJhb...
```

After:

```js
.split(' ')
```

we get:

```js
["Bearer", "eyJhb..."]
```

`[1]` gives:

```text
eyJhb...
```

So the flow is:

```text
Authorization header
       ↓
"Bearer TOKEN"
       ↓
split(' ')
       ↓
["Bearer", "TOKEN"]
       ↓
[1]
       ↓
TOKEN
```

* * *

# 11\. Optional Chaining `?.`

Without:

```js
authorization.split(' ')
```

if `authorization` doesn't exist, JavaScript can crash.

With:

```js
authorization?.split(' ')
```

JavaScript says:

> If it exists, continue. Otherwise give `undefined`.

Use `?.` when something **may legitimately be missing**.

It is different from `try/catch`:

```text
?.         → safely access missing value
try/catch → handle an operation that throws
```

* * *

# 12\. Authentication Middleware

A simple authentication flow:

```js
const auth = (req, res, next) => {

    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'
        });
    }
};
```

The mental model:

```text
Request
   ↓
Get token
   ↓
Token exists?
   ↓
Verify JWT
   ↓
Valid?
 ↙     ↘
NO      YES
↓        ↓
401    req.user
         ↓
       next()
```

* * *

# 13\. Why `req.user = decoded`?

Suppose the JWT contains:

```js
{
    userId: "12345"
}
```

After verification:

```js
req.user = decoded;
```

Now later code can use:

```js
req.user.userId
```

The flow is:

```text
JWT
 ↓
verify
 ↓
decoded data
 ↓
req.user
 ↓
controller
```

* * *

# 14\. Why `next()`?

`next()` means:

> **Authentication succeeded. Continue the request pipeline.**

If authentication fails:

```js
return res.status(401).json(...)
```

The request stops.

If authentication succeeds:

```js
next();
```

The request continues.

Think of middleware as a security checkpoint:

```text
Request
   ↓
Security check
   ↓
 ┌───────┴───────┐
fail            success
 ↓                 ↓
STOP              next()
```

* * *

# 15\. Helper Functions

Example:

```js
const formatClient = (client) => {
    return {
        savedLawyers: client.savedLawyers,
        blockedLawyers: client.blockedLawyers,
        bio: client.bio
    };
};
```

A helper takes existing data and creates the format we want to send.

```text
MongoDB document
      ↓
formatClient()
      ↓
clean response
```

This prevents us from sending unnecessary fields.

* * *

# 16\. Controller

Example:

```js
app.get('/api/client/profile', auth, async (req, res) => {
    ...
});
```

There are three parts:

```text
/api/client/profile → URL
auth                → middleware
async (req, res)    → controller logic
```

The controller answers:

> **What should happen when this endpoint is called?**

For a profile request:

```text
Get user ID
    ↓
Find User
    ↓
Find Client
    ↓
Format data
    ↓
Send response
```

* * *

# 17\. Finding User and Client

```js
const id = req.user.userId;

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

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

The User has:

```text
_id = 12345
```

The Client has:

```text
userId = 12345
```

So they are connected:

```text
USER
_id = 12345
    ↑
    │
CLIENT
userId = 12345
```

If either doesn't exist:

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

* * *

# 18\. Sending Response

```js
return res.status(200).json({
    userProfile: formatUser(user),
    clientProfile: formatClient(client)
});
```

`200` means the request succeeded.

The response is sent back to Postman/frontend.

* * *

# 19\. `try/catch` in Controllers

Database operations can throw errors:

```js
try {
    const user = await User.findOne(...);
}
catch (error) {
    res.status(500).json({
        message: 'Server error'
    });
}
```

Think:

```text
Expected problem
     ↓
if check
     ↓
401 / 404 / etc.

Unexpected thrown error
     ↓
try/catch
     ↓
500
```

* * *

# 20\. Updating Profile

```js
app.put('/api/client/profile', auth, async (req, res) => {
    ...
});
```

Remember:

```text
GET → retrieve
PUT → update
```

Example:

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

Three objects:

### 1\. What to find

```js
{ userId: id }
```

### 2\. What to change

```js
{ bio: req.body.bio }
```

### 3\. What to return

```js
{ new: true }
```

`new: true` means:

> Return the updated document instead of the old one.

* * *

# 21\. JWT Creation

```js
const token = jwt.sign(
    { userId: req.params.userId },
    'mysecretkey',
    { expiresIn: '1h' }
);
```

This creates a JWT containing:

```js
{
    userId: "ABC123"
}
```

with:

```text
secret → mysecretkey
expiry → 1 hour
```

Then:

```js
res.json({
    token: token
});
```

sends it to the client.

The client can later send:

```text
Authorization: Bearer TOKEN
```

* * *

# 22\. Middleware vs Controller

This is the important distinction.

Ask:

> **Is this checking/preparing something before the actual operation?**

If yes → **middleware**

Ask:

> **Is this actually performing what the endpoint was requested to do?**

If yes → **controller**

### Middleware

```text
Check authentication
Check permissions
Validate
Prepare request
   ↓
next()
```

### Controller

```text
Perform endpoint's job
   ↓
Database
   ↓
Business logic
   ↓
Response
```

For example:

```text
GET /profile
     ↓
    auth          ← middleware
     ↓
getProfile        ← controller
     ↓
 MongoDB
     ↓
 response
```

* * *

# 23\. Complete Mental Model

Whenever you read Express code, ask:

```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
```

The whole backend can therefore be understood as:

```text
REQUEST
   ↓
Where is the data?
(params/body/headers)
   ↓
MIDDLEWARE
(check/prepare/block)
   ↓
CONTROLLER
(actual endpoint work)
   ↓
DATABASE
   ↓
RESPONSE
```

**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.**

# **HOW TO STRUCTURE THE SAME FOR A LARGE PROJECT**

```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
```

### `server.js`

```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) => {
    res.json({
        message: 'Server is running'
    });
});

app.use(errorHandler);

app.listen(5000, () => {
    console.log('Server running on http://localhost:5000');
});
```

### `config/db.js`

```js
const mongoose = require('mongoose');

const connectDB = async () => {
    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;
```

### `models/User.js`

```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);
```

### `models/Client.js`

```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);
```

### `middleware/auth.middleware.js`

```js
const jwt = require('jsonwebtoken');

const auth = (req, res, next) => {
    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;
```

### `middleware/error.middleware.js`

```js
const errorHandler = (error, req, res, next) => {
    console.log(error);

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

module.exports = errorHandler;
```

### `utils/formatters.js`

```js
const formatClient = (client) => {
    return {
        savedLawyers: client.savedLawyers,
        blockedLawyers: client.blockedLawyers,
        bio: client.bio
    };
};

const formatUser = (user) => {
    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
};
```

### `utils/jwt.js`

```js
const jwt = require('jsonwebtoken');

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

module.exports = {
    generateToken
};
```

### `utils/asyncHandler.js`

```js
const asyncHandler = (fn) => {
    return (req, res, next) => {
        Promise
            .resolve(fn(req, res, next))
            .catch(next);
    };
};

module.exports = asyncHandler;
```

### `controllers/client.controller.js`

```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) => {

    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) => {

    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
};
```

### `controllers/auth.controller.js`

```js
const User = require('../models/User');
const Client = require('../models/Client');

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

const createTestUser = async (req, res) => {

    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) => {

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

    res.json({
        token
    });
};

module.exports = {
    createTestUser,
    getToken
};
```

### `routes/client.routes.js`

```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;
```

### `routes/auth.routes.js`

```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;
```

### Final structure

```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
```

The basic role of each file was already explained. The important next step is understanding **how the files communicate** and **why large projects separate responsibilities**.

* * *

## 1\. How files communicate

Node files communicate mainly through:

```js
module.exports
```

and:

```js
require()
```

For example:

```js
// utils/jwt.js
module.exports = {
    generateToken
};
```

Another file can use it:

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

Think:

```text
jwt.js
   │
   │ exports
   ▼
generateToken
   │
   │ require
   ▼
auth.controller.js
```

### `./` vs `../`

```js
require('./file')
```

→ current folder

```js
require('../file')
```

→ go one folder up first

* * *

# 2\. `express.Router()`

Putting every route inside `server.js` becomes messy:

```js
app.get(...)
app.post(...)
app.put(...)
app.delete(...)
```

So we create separate routers:

```js
const router = express.Router();
```

For example:

```text
routes/
├── auth.routes.js
├── client.routes.js
├── lawyer.routes.js
└── admin.routes.js
```

Each router manages one group of endpoints.

* * *

# 3\. How route paths combine

In `server.js`:

```js
app.use('/api/client', clientRoutes);
```

In `client.routes.js`:

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

Express combines them:

```text
/api/client
      +
/profile
      =
/api/client/profile
```

So the actual request is:

```text
GET /api/client/profile
```

This lets `server.js` handle the **main prefixes**, while route files handle the individual endpoints.

* * *

# 4\. Why controllers are passed without `()`

Route:

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

We pass:

```js
getMyProfile
```

not:

```js
getMyProfile()
```

Because:

```js
getMyProfile
```

means:

> "Here is the function. Run it when the request arrives."

Whereas:

```js
getMyProfile()
```

means:

> "Run it right now."

* * *

# 5\. Middleware and controllers form a pipeline

For:

```text
GET /api/client/profile
```

the flow is:

```text
Request
   ↓
server.js
   ↓
client router
   ↓
auth middleware
   ↓
next()
   ↓
getMyProfile controller
   ↓
User/Client model
   ↓
MongoDB
   ↓
res.json()
   ↓
Response
```

The files are separate, but the **request is still one continuous pipeline**.

* * *

# 6\. `asyncHandler`

Without `asyncHandler`, every async controller may need:

```js
try {
    ...
}
catch(error) {
    next(error);
}
```

That becomes repetitive across hundreds of controllers.

So we can create:

```js
const asyncHandler = (fn) => {
    return (req, res, next) => {
        Promise
            .resolve(fn(req, res, next))
            .catch(next);
    };
};
```

Then:

```js
const getMyProfile = asyncHandler(async (req, res) => {
    const user = await User.findOne(...);
});
```

Conceptually:

```text
Controller
    ↓
asyncHandler
    ↓
success → continue
error   → next(error)
```

* * *

# 7\. Error middleware

Normal middleware:

```js
(req, res, next)
```

Error middleware:

```js
(error, req, res, next)
```

Example:

```js
const errorHandler = (error, req, res, next) => {
    res.status(500).json({
        message: 'Server error'
    });
};
```

Then:

```js
app.use(errorHandler);
```

So an error can travel:

```text
Controller
   ↓
asyncHandler
   ↓
next(error)
   ↓
errorHandler
   ↓
500 response
```

* * *

# 8\. Route vs Controller

This distinction is extremely important.

### Route asks:

> **Which request should trigger something?**

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

### Controller asks:

> **What should happen?**

```js
const getMyProfile = asyncHandler(async (req, res) => {
    // actual request logic
});
```

Think:

```text
ROUTE       → WHERE?
CONTROLLER  → WHAT?
```

* * *

# 9\. Why do we need services?

A controller can become huge if it contains:

```text
database logic
business rules
calculations
permissions
notifications
formatting
...
```

So complicated reusable **business logic** can move into a service.

```text
Route
  ↓
Controller
  ↓
Service
  ↓
Model
  ↓
Database
```

Example:

```js
// client.service.js

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

    return { user, client };
};
```

Then the controller stays simple:

```js
const getMyProfile = asyncHandler(async (req, res) => {
    const data = await getClientProfile(req.user.userId);

    res.json(data);
});
```

The service is especially useful when the same logic is needed by multiple controllers.

* * *

# 10\. `.env`

Instead of hardcoding:

```js
'mongodb://...'
'mysecretkey'
```

we can store configuration in:

```text
.env
```

Example:

```env
PORT=5000
MONGO_URI=...
JWT_SECRET=...
```

Then:

```js
process.env.MONGO_URI
process.env.JWT_SECRET
```

This allows the same code to work with different environments such as:

```text
development
testing
production
```

without changing the JavaScript code.

* * *

# 11\. Model vs Database

This is another important distinction.

```js
const User = require('../models/User');
```

`User` is a **Mongoose model**, not the database itself.

The flow is:

```text
Controller
    ↓
User model
    ↓
Mongoose
    ↓
MongoDB
```

For example:

```js
User.findOne(...)
```

means:

> Ask the User model to perform a database operation.

* * *

# 12\. Complete architecture

A large Express backend can therefore look like:

```text
CLIENT
   ↓
SERVER
   ↓
ROUTES
   ↓
MIDDLEWARE
   ↓
CONTROLLER
   ↓
SERVICE
   ↓
MODEL
   ↓
MONGOOSE
   ↓
DATABASE
```

Errors have their own path:

```text
Any layer
   ↓
next(error)
   ↓
Error middleware
   ↓
Response
```

* * *

# 13\. The mental model to remember

Don't memorize the folder names.

Ask these questions:

```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
```

### The core idea

A large project doesn't separate files because the request itself is different.

It separates them because **different responsibilities are easier to manage when they live in different places**.

So instead of:

```text
10,000 lines → one server.js
```

we get:

```text
10,000 lines
      ↓
many small responsibilities
      ↓
many focused files
      ↓
one connected request pipeline
```

That is what makes a large backend manageable.
