Creating Routes and Handling Requests with Express
Build APIs faster with Express: routes, requests, and responses made simple.

Hi, I’m Abdul Samad. A web development learner and tech enthusiast. I write about what I learn, share practical coding tips, and publish in-depth blogs on programming and modern web development.
Check out my full collection of blogs on Hashnode: https://abdulsamad30.hashnode.dev/
Connect with me on X for quick updates and insights: @abdul_sama60108
If you've ever tried building a web server using just Node.js, you know it can get messy pretty quickly. You're manually parsing URLs, figuring out request methods, and writing a lot of boilerplate code just to handle a simple request. That's where Express.js comes in and honestly, once you use it, going back feels painful.
In this post, we'll walk through what Express.js is, why it makes your life easier, and how to actually use it to create routes and handle requests. By the end, you'll have a solid understanding of how requests flow through an Express app and how to send proper responses back to the client.
What is Express.js?
Express.js is a minimal and flexible web framework built on top of Node.js. It gives you a clean, straightforward way to handle HTTP requests, define routes, and build web applications or APIs without reinventing the wheel every time.
Think of Node.js as the raw engine of a car, and Express.js as the steering wheel, dashboard, and controls that make the engine actually usable for driving. The engine is still doing the heavy lifting, but Express gives you the interface to control it comfortably.
It's part of what's often called the MERN or MEAN stack, and it's one of the most downloaded packages on npm for a reason it's simple, stable, and widely supported.
Why Express Simplifies Node.js Development
To really appreciate Express, it helps to see what building a server looks like without it.
Raw Node.js HTTP Server
const http = require('http');
const server = http.createServer((req, res) => {
if (req.url === '/about' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('About Page');
} else if (req.url === '/contact' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Contact Page');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000, () => {
console.log('Server running on port 3000');
});
Output (visiting /about in browser or via curl):
About Page
This works, but notice how you're manually checking req.url and req.method for every single route. As your app grows, this becomes a tangled mess.
The Same Thing with Express
const express = require('express');
const app = express();
app.get('/about', (req, res) => {
res.send('About Page');
});
app.get('/contact', (req, res) => {
res.send('Contact Page');
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Output (visiting /about):
About Page
Same result. But the Express version is cleaner, more readable, and scales much better. Each route is clearly defined, and you're not writing manual conditionals everywhere. That's the core value Express brings to the table.
Creating Your First Express Server
Let's start from scratch and build a basic Express server.
First, make sure you have Node.js installed. Then set up your project:
mkdir my-express-app
cd my-express-app
npm init -y
npm install express
Now create a file called index.js and add the following:
const express = require('express');
const app = express();
const PORT = 3000;
app.get('/', (req, res) => {
res.send('Welcome to my Express server!');
});
app.listen(PORT, () => {
console.log(`Server is running on <http://localhost>:${PORT}`);
});
Run it with:
node index.js
Output in terminal:
Server is running on <http://localhost:3000>
Output in browser at http://localhost:3000:
Welcome to my Express server!
Here's what's happening step by step:
require('express')imports the Express moduleexpress()creates an application instanceapp.get('/', ...)defines a route for GET requests at the root pathapp.listen(PORT, ...)starts the server on port 3000
Simple, clean, and ready to build on.
Understanding the Request → Response Flow
Before we go further into routes, it's worth visualizing how a request actually travels through an Express app.
When a client (browser, mobile app, or tool like Postman) sends a request:
The request hits your Express server
Express looks through the defined routes to find a match (method + path)
The matching route handler runs
The handler sends back a response to the client
If no route matches, Express returns a default 404 response unless you've defined a custom one.
Express Routing Structure
Routing in Express is the way you define how your app responds to different URLs and HTTP methods. Each route has a path and a handler function.
The general pattern looks like this:
app.METHOD(PATH, HANDLER);
METHODthe HTTP method (get,post,put,delete, etc.)PATHthe URL path ('/','/users','/products/1')HANDLERa function that receivesreq(request) andres(response)
This structure is what makes Express so intuitive. You can glance at a route and immediately understand what it does and when it runs.
Handling GET Requests
GET requests are used to retrieve data. When someone visits a URL in their browser, they're making a GET request.
const express = require('express');
const app = express();
app.get('/users', (req, res) => {
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
];
res.json(users);
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Output (visiting /users):
[
{ "id": 1, "name": "Alice" },
{ "id": 2, "name": "Bob" }
]
Here, res.json() automatically sets the Content-Type header to application/json and sends the data as a JSON response which is exactly what you'd want when building an API.
You can also use route parameters to make routes dynamic:
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.send(`Fetching user with ID: ${userId}`);
});
Output (visiting /users/42):
Fetching user with ID: 42
req.params gives you access to the dynamic parts of the URL. Clean and straightforward.
Handling POST Requests
POST requests are used to send data to the server like submitting a form or creating a new resource.
To read the data sent in a POST request body, you need to add a middleware that parses JSON. Express has this built in:
const express = require('express');
const app = express();
app.use(express.json()); // Middleware to parse JSON request bodies
app.post('/users', (req, res) => {
const newUser = req.body;
console.log('Received user data:', newUser);
res.status(201).json({
message: 'User created successfully',
user: newUser,
});
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
To test this, you can use Postman or curl:
curl -X POST <http://localhost:3000/users> \\
-H "Content-Type: application/json" \\
-d '{"name": "Charlie", "email": "charlie@example.com"}'
Output in terminal:
Received user data: { name: 'Charlie', email: 'charlie@example.com' }
Response sent back to client:
{
"message": "User created successfully",
"user": {
"name": "Charlie",
"email": "charlie@example.com"
}
}
app.use(express.json()) is the line that does the heavy lifting it parses incoming JSON payloads and makes the data available at req.body. Without it, req.body would be undefined.
Sending Responses
Express gives you several ways to send responses depending on what you need to return.
const express = require('express');
const app = express();
// Send plain text
app.get('/text', (req, res) => {
res.send('This is plain text');
});
// Send JSON
app.get('/json', (req, res) => {
res.json({ message: 'This is JSON' });
});
// Send with a specific status code
app.get('/not-found', (req, res) => {
res.status(404).send('This page does not exist');
});
// Send with status 201 (Created)
app.get('/created', (req, res) => {
res.status(201).json({ message: 'Resource created' });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});
Outputs:
GET /text→This is plain textGET /json→{ "message": "This is JSON" }GET /not-found→ Status 404,This page does not existGET /created→ Status 201,{ "message": "Resource created" }
Here's a quick reference for the most commonly used response methods:
| Method | Use Case |
|---|---|
res.send() |
Send text or HTML |
res.json() |
Send JSON data |
res.status() |
Set HTTP status code |
res.sendFile() |
Send a file |
Always pair your status codes with your responses it helps the client understand what happened. 200 means OK, 201 means something was created, 400 is a bad request, and 404 means not found.
Wrapping Up
Express takes the complexity out of building HTTP servers with Node.js. Instead of juggling raw request parsing and manual conditionals, you get a clean routing system where each path and method has its own dedicated handler.
Here's a quick recap of what we covered:
Express.js is a lightweight framework built on top of Node.js
It's significantly cleaner than raw Node.js HTTP servers
You create a server with
express()and start it withapp.listen()GET routes handle data retrieval, and you can use
req.paramsfor dynamic pathsPOST routes handle incoming data, and
express.json()middleware makesreq.bodyavailableResponses can be plain text, JSON, or status-coded replies depending on your needs
From here, a natural next step would be exploring Express middleware more deeply, or organizing routes into separate files using express.Router() but that's a topic for another post.
For now, experiment with what you've learned here. Build a small API with a few routes, test it with Postman, and get comfortable with the request-response cycle. That foundation will take you a long way.
FIN ✌️



