# The Era of Express: Routes, Params and the Art of Handling Requests

## Introduction

Every backend does one job again and again: a request comes in, some logic runs, a response goes out. Node.js can do this on its own, but the code becomes long and repetitive very fast. Express exists to remove that pain.

In this blog, we will clearly understand how Express handles requests, like routes, handlers, responses, URL params and query strings.

* * *

## What is Express and Why Do We Need It?

Express is a minimal web framework built on top of node.js. We can write node.js code without Express, but it becomes too repetitive and hard to manage when the project grows. That\`s why TJ Holowaychuk took the `http` module of Node.js and built Express separately on top of it. With Express, we can create servers, write code efficiently, and handle routes easily, and keep our code organized as the project grows.

> **Let\`s see a small server with raw Node.js (without express):**

```javascript
import http from 'http';
 
const server = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('Home page');
  } else if (req.method === 'GET' && req.url === '/about') {
    res.writeHead(200, { 'Content-Type': 'text/plain' });
    res.end('About page');
  } else {
    res.writeHead(404, { 'Content-Type': 'text/plain' });
    res.end('Not found');
  }
});
 
server.listen(3000);
```

Two routes, and it already looks heavy. Now imagine 40 routes, JSON bodies, URL parameters and query strings.

> **Now let\`s see the same server in Express:**

```javascript
import express from 'express';
 
const app = express();
 
app.get('/', (req, res) => res.send('Home page'));
app.get('/about', (req, res) => res.send('About page'));
 
app.listen(3000, () => console.log('Server running on port 3000'));
```

As we can see, our code now looks much cleaner compared to the raw node.js server.Express gives a clear structure to our code, so it stays clean even when the project grows.

> **Comparing Raw Node.js and Express**

| Responsibility | Raw Node.js `http` | Express |
| --- | --- | --- |
| Routing | Manual `if/else` on `req.url` | `app.get()`, `app.post()` etc. |
| Parsing URL params | Manual | `req.params` |
| Parsing query string | Manual | `req.query` |
| Sending JSON | Set header + `JSON.stringify` yourself | `res.json()` |
| Status codes | `res.writeHead()` | `res.status()` |

Raw HTTP is manual, while Express makes it simple and fast.Express removes the boilerplate and makes routing easy.

![](https://cdn.hashnode.com/uploads/covers/69513d1ce0cbcddf469383e9/343809f9-5c67-459c-bad9-441896bf3ef8.png align="center")

* * *

## Your First Express Server

```bash
npm init -y
npm install express
```

```javascript
// server.js
import express from 'express';
 
const app = express();
const PORT = 3000;
 
app.get('/', (req, res) => {
  res.send('Hello from Express');
});
 
app.listen(PORT, () => {
  console.log(`Server running on http://localhost:${PORT}`);
});
```

Run it with `node server.js` and open `http://localhost:3000`.

Three things are happening here:

1.  `express()` creates the application object, `app`.
    
2.  `app.get(...)` registers a **route**.
    
3.  `app.listen(...)` starts the server and waits for requests.
    

* * *

## The Request Lifecycle

Before writing more routes, understand the path a request takes:

1.  The client sends a request (method + URL).
    
2.  Express looks at its list of routes, **from top to bottom**.
    
3.  The first route that matches both the **method** and the **path** runs its handler.
    
4.  The handler sends a response, and the request ends.
    

![](https://cdn.hashnode.com/uploads/covers/69513d1ce0cbcddf469383e9/ccdb2be0-17d0-475d-8f51-d35ff38206c3.png align="center")

* * *

## Routing Basics

```javascript
app.METHOD(PATH, HANDLER);
```

*   `METHOD`: the HTTP method (`get`, `post`, `put`, `patch`, `delete`).
    
*   `PATH`: the URL pattern to match.
    
*   `HANDLER`: the function that runs when both match. It receives `req` (request) and `res` (response).
    
    ![](https://cdn.hashnode.com/uploads/covers/69513d1ce0cbcddf469383e9/97caa36f-d779-4072-9b05-5fb58fe7f44b.png align="center")
    

* * *

### Handling POST Requests

POST is used to **create** data. The data comes in the request body, and Express does not read a JSON body by default. We must enable it with the built-in `express.json()` middleware.

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

app.post('/users', (req, res) => {
  const name = req.body.name;

  if (!name) {
    return res.status(400).json({ error: 'name is required' });
  }

  // save the user in the database

  res.status(201).json({ message: 'User created', name: name });
});
```

Notice three good habits here:

*   Validate the input, and return `400` if it is wrong.
    
*   Return `201 Created` when something new is created.
    
*   Take only the fields you need (like `name`) from `req.body`. Don’t save the whole `req.body` directly, because the client can send extra fields.
    

* * *

### PUT, PATCH and DELETE

```javascript
//replace the whole resource
app.put('/users/:id', (req, res) => {
  const id = req.params.id;

  // find the user in the database
  // replace the whole user with the new data from req.body

  res.json({ message: `User ${id} replaced` });
});

// PATCH: update only some fields
app.patch('/users/:id', (req, res) => {
  const id = req.params.id;

  // find the user in the database
  // update only the fields sent in req.body

  res.json({ message: `User ${id} updated` });
});

// DELETE
app.delete('/users/:id', (req, res) => {
  const id = req.params.id;

  // find the user in the database
  // delete the user

  res.status(204).end(); // 204 = success, nothing to send back
});
```

Every handler must **send a response**. A handler that never responds leaves the client hanging until it times out.

* * *

## Sending Responses

`res` gives us many ways to reply. These are the ones you will use daily:

| Method | What it does | Example |
| --- | --- | --- |
| `res.send()` | Sends text, HTML, or an object/buffer | `res.send('Hello')` |
| `res.json()` | Sends JSON with the right `Content-Type` | `res.json({ ok: true })` |
| `res.status()` | Sets the status code (chainable) | `res.status(404).json({...})` |
| `res.sendStatus()` | Sets the status and sends its text | `res.sendStatus(200)` sends `OK` |
| `res.redirect()` | Redirects to another URL | `res.redirect(301, '/new')` |
| `res.type()` | Sets the `Content-Type` | `res.type('application/xml')` |
| `res.set()` | Sets a response header | `res.set('Request-Id', 'abc')` |
| `res.end()` | Ends the response without data | `res.status(204).end()` |

**Rule:** send only **one** response per request. Calling `res.send()` or `res.json()` twice throws the error *"Cannot set headers after they are sent to the client"*. This usually happens when you forget a `return`:

```javascript
// Wrong: both lines try to respond
if (!user) res.status(404).json({ error: 'Not found' });
res.json(user);
 
// Correct: return stops the function
if (!user) return res.status(404).json({ error: 'Not found' });
res.json(user);
```

### Common Status Codes

| Code | Meaning | Use it when |
| --- | --- | --- |
| `200` | OK | Normal success |
| `201` | Created | A new resource was created (POST) |
| `204` | No Content | Success, nothing to return (DELETE) |
| `400` | Bad Request | Invalid input |
| `404` | Not Found | Resource or route does not exist |
| `500` | Server Error | Something broke on the server |

* * *

## URL Params vs Query Strings

```plaintext
https://api.example.com/users/42/orders?status=pending&limit=10&page=2
```

It has three useful parts:

*   **Path:** `/users/42/orders`
    
*   **URL param:** `42` (a value inside the path)
    
*   **Query string:** `?status=pending&limit=10&page=2` (key-value pairs after the `?`) Both carry data from the client to the server. But they carry **different kinds** of data.
    

![](https://cdn.hashnode.com/uploads/covers/69513d1ce0cbcddf469383e9/fd906dbe-015b-4ab0-8326-25c194f26725.png align="center")

### URL Parameters (`req.params`)

A URL param is a **dynamic part of the path**. We define it with a colon (`:`) in the route.

```javascript
app.get('/users/:id', (req, res) => {
  const id = Number(req.params.id);
 
  if (!Number.isInteger(id)) {
    return res.status(400).json({ error: 'id must be an integer' });
  }
 
  const user = users.find((u) => u.id === id);
  if (!user) return res.status(404).json({ error: 'User not found' });
 
  res.json(user);
});
```

A request to `/users/1` gives `req.params` the value `{ id: '1' }`.

You can have more than one param:

```javascript
app.get('/users/:userId/orders/:orderId', (req, res) => {
  res.json(req.params); // { userId: '5', orderId: '9' }
});
```

Params **identify a specific resource**. `/users/42` means *"the user whose id is 42"*.

* * *

### Query Strings (`req.query`)

A query string is everything after the `?`. It is **not** part of the route definition. Express reads it for you and puts it in `req.query`.

```javascript
app.get('/users', (req, res) => {
  const { role, city, limit = '10', page = '1' } = req.query;
 
  let result = users;
  if (role) result = result.filter((u) => u.role === role);
  if (city) result = result.filter((u) => u.city === city);
 
  const l = Number(limit);
  const p = Number(page);
 
  res.json({
    count: result.length,
    page: p,
    data: result.slice((p - 1) * l, p * l),
  });
});
```

A request to `/users?role=student&limit=5` gives `req.query` the value `{ role: 'student', limit: '5' }`.

Query strings **filter, sort or paginate** a collection. They are usually optional.

* * *

### A Very Common Mistake: Everything is a String

Both `req.params` and `req.query` always give you **strings**. Even `?limit=5` gives `'5'`, not `5`. Convert it yourself:

```javascript
const limit = Number(req.query.limit);
```

And always validate the result. `Number('abc')` is `NaN`, and that should become a `400`, not a crash.

### Key Differences

|  | URL Params | Query String |
| --- | --- | --- |
| Example | `/users/42` | `/users?role=admin` |
| Defined in route | Yes (`:id`) | No |
| Access with | `req.params` | `req.query` |
| Purpose | **Identify** one resource | **Filter, sort, paginate** |
| Required? | Yes, route will not match without it | Usually optional |
| Position | Inside the path | After the `?` |

### When to Use Which

A simple rule that works almost always:

*   **Which resource?** Use a param. `GET /users/42`
    
*   **How should I see the list?** Use a query. `GET /users?role=admin&page=2` You can combine both:
    

```plaintext
GET /users/42/orders?status=pending
```

*The Code is saying:* `"Give me the orders of user 42, but only the pending ones."`

The param picks the user, and the query filters the orders.

**How to decide:**

*   Use a **param** when the value tells you *which* resource you want. Example: `/users/42`
    
*   Use a **query** when the value tells you *how* you want the result. Example: `/users?role=admin`
    

* * *

## Route Order Matters

Express checks your routes **one by one, from top to bottom**. As soon as one route matches, it runs that route and stops. It does not check the rest.

This can create a bug when a fixed route and a dynamic route look similar.

```javascript
// Wrong order
app.get('/users/:id', (req, res) => res.json({ route: 'by id' }));
app.get('/users/search', (req, res) => res.json({ route: 'search' }));
```

Here, a request to `/users/search` reaches `/users/:id` first. Express treats the word `search` as the `id`, so the second route never runs.

This is how you can fix it: put the fixed route **before** the dynamic route.

```javascript
// Correct order
app.get('/users/search', (req, res) => res.json({ route: 'search' }));
app.get('/users/:id', (req, res) => res.json({ route: 'by id' }));
```

Now `/users/search` matches the first route and stops. Any other value, like `/users/42`, moves down and matches `/users/:id`.

**Rule:** put fixed routes first, and dynamic routes (`:id`) after them.

* * *

**Express Class Github Link:** [https://github.com/abdulrdeveloper/FullStackHub/tree/main/Learn%20Backend/Intro%20to%20Express/express-class](https://github.com/abdulrdeveloper/FullStackHub/tree/main/Learn%20Backend/Intro%20to%20Express/express-class)

* * *

You can find more of my work at [abdulrdeveloper.me](http://abdulrdeveloper.me)

Read more posts at [blog.abdulrdeveloper.me](http://blog.abdulrdeveloper.me)
