DEV Community

Cover image for Creating Routes and Handling Requests with Express
Pratham
Pratham

Posted on

Creating Routes and Handling Requests with Express

How Express turns Node.js from "I can build a server" to "I can build a server in 5 minutes."


In the last article, we built an HTTP server with raw Node.js. It worked. But let me be honest — it was painful. Manually parsing URLs, setting headers, checking req.url with if-else chains, handling different HTTP methods... for a simple 3-page site, it was already getting messy.

Now imagine building an API with 20 routes, request body parsing, error handling, and middleware. With raw Node.js? That's weeks of boilerplate code.

This is why Express.js exists. It takes everything tedious about raw Node.js HTTP handling and gives you a clean, minimal API to do the same thing in a fraction of the code. Express is to Node.js what a power drill is to a screwdriver — same job, dramatically faster.

Let me show you how it works. This was one of those "why didn't I learn this sooner" moments in the ChaiCode Web Dev Cohort 2026.


What Is Express.js?

Express.js is a lightweight web framework for Node.js. It sits on top of Node's built-in http module and adds:

  • Routing — map URLs to handler functions cleanly
  • Middleware — plug in reusable logic (logging, parsing, auth)
  • Request/Response helpersreq.params, req.body, res.json(), res.status()
  • Simpler syntax — less boilerplate, more focus on your logic
Node.js (raw):
  const http = require("http");
  http.createServer((req, res) => {
    if (req.url === "/users" && req.method === "GET") { ... }
    if (req.url === "/users" && req.method === "POST") { ... }
    // manual parsing, manual headers, manual everything
  });

Express:
  const app = express();
  app.get("/users", handler);
  app.post("/users", handler);
  // done. Express handles the rest.
Enter fullscreen mode Exit fullscreen mode

Express is the most popular Node.js framework by far — used by companies like Uber, IBM, and Accenture. It's minimal enough to learn quickly but powerful enough for production apps.


Why Express Simplifies Node.js Development

Let me prove it. Here's the same server — raw Node.js vs Express — doing the exact same thing.

Raw Node.js

const http = require("http");

const server = http.createServer((req, res) => {
  if (req.url === "/" && req.method === "GET") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ message: "Welcome to the API" }));
  } else if (req.url === "/users" && req.method === "GET") {
    res.writeHead(200, { "Content-Type": "application/json" });
    res.end(JSON.stringify([{ name: "Pratham" }, { name: "Arjun" }]));
  } else if (req.url === "/users" && req.method === "POST") {
    let body = "";
    req.on("data", (chunk) => (body += chunk));
    req.on("end", () => {
      const user = JSON.parse(body);
      res.writeHead(201, { "Content-Type": "application/json" });
      res.end(JSON.stringify({ created: user }));
    });
  } else {
    res.writeHead(404, { "Content-Type": "application/json" });
    res.end(JSON.stringify({ error: "Not found" }));
  }
});

server.listen(3000);
Enter fullscreen mode Exit fullscreen mode

27 lines. Manual URL checking, manual method checking, manual body parsing, manual headers everywhere.

Express

const express = require("express");
const app = express();

app.use(express.json()); // Parse JSON bodies automatically

app.get("/", (req, res) => {
  res.json({ message: "Welcome to the API" });
});

app.get("/users", (req, res) => {
  res.json([{ name: "Pratham" }, { name: "Arjun" }]);
});

app.post("/users", (req, res) => {
  res.status(201).json({ created: req.body });
});

app.listen(3000, () => {
  console.log("Server running on http://localhost:3000");
});
Enter fullscreen mode Exit fullscreen mode

18 lines. Clean routing, automatic JSON parsing, built-in response helpers. Same functionality, half the code, ten times more readable.


Setting Up Express

Initialize a Project

mkdir express-app
cd express-app
npm init -y
Enter fullscreen mode Exit fullscreen mode

Install Express

npm install express
Enter fullscreen mode Exit fullscreen mode

This adds Express to your node_modules folder and records it in package.json under dependencies.

Verify Installation

Your package.json should include:

{
  "dependencies": {
    "express": "^4.18.2"
  }
}
Enter fullscreen mode Exit fullscreen mode

Creating Your First Express Server

// server.js

const express = require("express");
const app = express();
const PORT = 3000;

app.get("/", (req, res) => {
  res.send("Hello from Express! 🚀");
});

app.listen(PORT, () => {
  console.log(`Server running at http://localhost:${PORT}`);
});
Enter fullscreen mode Exit fullscreen mode

Run it:

node server.js
Enter fullscreen mode Exit fullscreen mode

Visit http://localhost:3000 in your browser. You'll see: Hello from Express! 🚀

What Each Line Does

const express = require("express");  // Import Express
const app = express();               // Create an Express application
const PORT = 3000;                   // Define the port

app.get("/", (req, res) => {         // When someone visits "/"...
  res.send("Hello from Express!");   // ...send this response
});

app.listen(PORT, () => {             // Start listening for requests
  console.log(`Server running...`);
});
Enter fullscreen mode Exit fullscreen mode

Express Routing — The Core Concept

Routing means connecting a URL path + HTTP method to a handler function. When a request matches a route, the handler runs.

Route Structure

app.METHOD(PATH, HANDLER);
Enter fullscreen mode Exit fullscreen mode
  • METHODget, post, put, patch, delete
  • PATH — the URL pattern (/, /users, /products/:id)
  • HANDLER — the function that runs when the route matches

Express Routing Structure

Incoming Request: GET /users
        │
        ↓
┌──────────────────────────────────────────┐
│              EXPRESS APP                  │
│                                          │
│  app.get("/")          ← doesn't match   │
│  app.get("/users")     ← MATCH! ✅       │
│  app.post("/users")    ← wrong method    │
│  app.get("/products")  ← doesn't match   │
│                                          │
└──────────────────┬───────────────────────┘
                   │
                   ↓
┌──────────────────────────────────────────┐
│           ROUTE HANDLER                   │
│                                          │
│  (req, res) => {                         │
│    res.json([...users]);                 │
│  }                                       │
│                                          │
└──────────────────┬───────────────────────┘
                   │
                   ↓
┌──────────────────────────────────────────┐
│          RESPONSE TO CLIENT              │
│                                          │
│  Status: 200                             │
│  Body: [{ "name": "Pratham" }, ...]      │
│                                          │
└──────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Express checks routes in the order they're defined and runs the first one that matches.


Handling GET Requests

GET requests are for reading data. They're what happens when you visit a URL in your browser, click a link, or call an API to fetch information.

Basic GET Routes

const express = require("express");
const app = express();

// Home page
app.get("/", (req, res) => {
  res.send("Welcome to the Home Page!");
});

// About page
app.get("/about", (req, res) => {
  res.send("This is the About Page.");
});

// API endpoint — return JSON
app.get("/api/users", (req, res) => {
  const users = [
    { id: 1, name: "Pratham", role: "developer" },
    { id: 2, name: "Arjun", role: "designer" },
    { id: 3, name: "Priya", role: "manager" },
  ];
  res.json(users);
});

app.listen(3000, () => console.log("Server on http://localhost:3000"));
Enter fullscreen mode Exit fullscreen mode

Route Parameters — Dynamic URLs

What if you need /users/1, /users/2, /users/3? You can't create a separate route for each user. Use route parameters:

app.get("/users/:id", (req, res) => {
  const userId = req.params.id;
  res.json({ message: `You requested user with ID: ${userId}` });
});

// GET /users/5    → { message: "You requested user with ID: 5" }
// GET /users/42   → { message: "You requested user with ID: 42" }
Enter fullscreen mode Exit fullscreen mode

:id is a placeholder. Whatever value is in that position gets stored in req.params.id.

Multiple Parameters

app.get("/users/:userId/orders/:orderId", (req, res) => {
  const { userId, orderId } = req.params;
  res.json({
    message: `Order ${orderId} for user ${userId}`,
  });
});

// GET /users/3/orders/101 → { message: "Order 101 for user 3" }
Enter fullscreen mode Exit fullscreen mode

Query Parameters

Query parameters are the ?key=value pairs after the URL:

app.get("/search", (req, res) => {
  const { q, page, limit } = req.query;
  res.json({
    searchTerm: q,
    page: page || 1,
    limit: limit || 10,
  });
});

// GET /search?q=nodejs&page=2&limit=5
// → { searchTerm: "nodejs", page: "2", limit: "5" }
Enter fullscreen mode Exit fullscreen mode

Handling POST Requests

POST requests are for sending data to the server — creating new resources, submitting forms, sending login credentials.

Setup: Parse Request Bodies

Express doesn't parse request bodies by default. Add this middleware:

app.use(express.json()); // Parse JSON bodies
app.use(express.urlencoded({ extended: true })); // Parse form data
Enter fullscreen mode Exit fullscreen mode

Basic POST Route

const express = require("express");
const app = express();

app.use(express.json());

const users = [];

app.post("/api/users", (req, res) => {
  const { name, email } = req.body;

  if (!name || !email) {
    return res.status(400).json({ error: "Name and email are required" });
  }

  const newUser = {
    id: users.length + 1,
    name,
    email,
    createdAt: new Date().toISOString(),
  };

  users.push(newUser);
  res.status(201).json({ message: "User created!", user: newUser });
});

app.listen(3000, () => console.log("Server running"));
Enter fullscreen mode Exit fullscreen mode

Testing POST Requests

You can't test POST requests in a browser address bar (that only sends GET). Use one of these:

Using curl in terminal:

curl -X POST http://localhost:3000/api/users \
  -H "Content-Type: application/json" \
  -d '{"name": "Pratham", "email": "pratham@prathamdev.in"}'
Enter fullscreen mode Exit fullscreen mode

Using the fetch API in browser console:

fetch("http://localhost:3000/api/users", {
  method: "POST",
  headers: { "Content-Type": "application/json" },
  body: JSON.stringify({ name: "Pratham", email: "pratham@prathamdev.in" }),
})
  .then((res) => res.json())
  .then((data) => console.log(data));
Enter fullscreen mode Exit fullscreen mode

Sending Responses

Express gives you several ways to send responses back to the client:

res.send() — Send Any Content

app.get("/text", (req, res) => {
  res.send("Plain text response");
});

app.get("/html", (req, res) => {
  res.send("<h1>HTML response</h1><p>This is rendered by the browser.</p>");
});
Enter fullscreen mode Exit fullscreen mode

res.json() — Send JSON (Most Common for APIs)

app.get("/api/data", (req, res) => {
  res.json({
    success: true,
    data: { name: "Pratham", role: "developer" },
  });
});
Enter fullscreen mode Exit fullscreen mode

res.json() automatically sets the Content-Type header to application/json.

res.status() — Set Status Code

app.post("/api/users", (req, res) => {
  // 201 = Created
  res.status(201).json({ message: "User created!" });
});

app.get("/api/missing", (req, res) => {
  // 404 = Not Found
  res.status(404).json({ error: "Resource not found" });
});

app.get("/api/error", (req, res) => {
  // 500 = Server Error
  res.status(500).json({ error: "Something went wrong" });
});
Enter fullscreen mode Exit fullscreen mode

Common HTTP Status Codes

Code Meaning When to Use
200 OK Successful GET, PUT, PATCH
201 Created Successful POST (new resource)
204 No Content Successful DELETE
400 Bad Request Invalid input from client
401 Unauthorized Missing or invalid authentication
404 Not Found Resource doesn't exist
500 Internal Server Error Something broke on the server

res.redirect() — Redirect to Another URL

app.get("/old-page", (req, res) => {
  res.redirect("/new-page");
});
Enter fullscreen mode Exit fullscreen mode

Request → Route Handler → Response Flow

Here's the complete picture of how a request flows through Express:

Client sends: POST /api/users  { "name": "Pratham" }
                    │
                    ↓
┌──────────────────────────────────────────────┐
│               EXPRESS APP                     │
│                                              │
│  1. Middleware runs first:                   │
│     express.json() → parses request body     │
│                                              │
│  2. Router matches the route:                │
│     app.post("/api/users") ← MATCH! ✅       │
│                                              │
│  3. Handler runs:                            │
│     (req, res) => {                          │
│       const { name } = req.body;             │
│       // create user...                      │
│       res.status(201).json({ user });        │
│     }                                        │
│                                              │
│  4. Response sent to client:                 │
│     Status: 201                              │
│     Body: { "user": { "name": "Pratham" } }  │
│                                              │
└──────────────────────────────────────────────┘
Enter fullscreen mode Exit fullscreen mode

Putting It All Together — A Mini CRUD API

Let's build a complete example with GET, POST, and route parameters:

const express = require("express");
const app = express();

app.use(express.json());

// In-memory "database"
let todos = [
  { id: 1, title: "Learn Node.js", completed: false },
  { id: 2, title: "Build Express API", completed: false },
  { id: 3, title: "Deploy to production", completed: false },
];

// GET all todos
app.get("/api/todos", (req, res) => {
  res.json(todos);
});

// GET single todo by ID
app.get("/api/todos/:id", (req, res) => {
  const todo = todos.find((t) => t.id === parseInt(req.params.id));
  if (!todo) {
    return res.status(404).json({ error: "Todo not found" });
  }
  res.json(todo);
});

// POST — create new todo
app.post("/api/todos", (req, res) => {
  const { title } = req.body;
  if (!title) {
    return res.status(400).json({ error: "Title is required" });
  }

  const newTodo = {
    id: todos.length + 1,
    title,
    completed: false,
  };

  todos.push(newTodo);
  res.status(201).json(newTodo);
});

// PUT — update todo
app.put("/api/todos/:id", (req, res) => {
  const todo = todos.find((t) => t.id === parseInt(req.params.id));
  if (!todo) {
    return res.status(404).json({ error: "Todo not found" });
  }

  todo.title = req.body.title || todo.title;
  todo.completed = req.body.completed ?? todo.completed;
  res.json(todo);
});

// DELETE — remove todo
app.delete("/api/todos/:id", (req, res) => {
  const index = todos.findIndex((t) => t.id === parseInt(req.params.id));
  if (index === -1) {
    return res.status(404).json({ error: "Todo not found" });
  }

  todos.splice(index, 1);
  res.status(204).send();
});

app.listen(3000, () => {
  console.log("Todo API running at http://localhost:3000/api/todos");
});
Enter fullscreen mode Exit fullscreen mode

A fully functional REST API with create, read, update, and delete — in under 60 lines.


Let's Practice: Hands-On Assignment

Part 1: Create a Basic Express Server

Set up a new project, install Express, and create a server with three routes:

  • GET / — returns "Welcome to my Express app!"
  • GET /about — returns "About page"
  • GET /api/status — returns JSON { status: "running", uptime: process.uptime() }

Part 2: Build a User API

Create these endpoints:

  • GET /api/users — returns all users
  • GET /api/users/:id — returns a single user by ID (or 404)
  • POST /api/users — creates a new user from req.body (validate name and email)

Part 3: Add Search Functionality

Add a route that accepts query parameters:

// GET /api/users/search?name=Pratham&role=developer
app.get("/api/users/search", (req, res) => {
  const { name, role } = req.query;
  // Filter users based on query parameters
  // Return matching users
});
Enter fullscreen mode Exit fullscreen mode

Key Takeaways

  1. Express.js is a minimal web framework that simplifies Node.js development — routing, request parsing, and response handling become clean and readable.
  2. Routing maps URL paths + HTTP methods to handler functions: app.get("/path", handler).
  3. GET requests read data. Use req.params for URL parameters (:id) and req.query for query strings (?key=value).
  4. POST requests send data. Use express.json() middleware to parse request bodies, then access data via req.body.
  5. Response methodsres.json() for APIs, res.send() for text/HTML, res.status() for HTTP codes, res.redirect() for redirections.

Wrapping Up

Express takes the raw power of Node.js and wraps it in a developer-friendly API. The same server that took 27 lines with raw http takes 18 lines with Express — and it's more readable, more maintainable, and more extensible. That's why Express is the default starting point for nearly every Node.js backend project.

I'm learning all of this through the ChaiCode Web Dev Cohort 2026 under Hitesh Chaudhary and Piyush Garg. Express was the moment backend development went from "I can do this" to "I actually enjoy doing this." Routes, request handling, JSON APIs — it all feels natural with Express.

Connect with me on LinkedIn or visit PrathamDEV.in. More articles on the way as the backend journey continues.

Happy coding! 🚀


Written by Pratham Bhardwaj | Web Dev Cohort 2026, ChaiCode

Top comments (0)