Creating Routes and Handling Requests with Express

What Express.js Is
Express is a minimal, unopinionated web framework for Node.js. It provides a thin layer of fundamental web application features on top of Node's built-in HTTP module -> routing, middleware support, and response utilities -> without dictating how you structure your application or which tools you use alongside it.
Express does not replace Node.js. It runs on top of it. Every Express application is still a Node.js application. What Express adds is a cleaner, more productive interface for the things developers do in almost every web server: defining routes, parsing requests, and sending responses.
It is one of the most widely used packages in the Node.js ecosystem and forms the foundation of many production backends, REST APIs, and server-rendered applications.
Why Express Simplifies Node.js Development
To appreciate what Express provides, 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.method === 'GET' && req.url === '/users') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ users: [] }));
} else if (req.method === 'POST' && req.url === '/users') {
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'User created' }));
} else {
res.writeHead(404);
res.end('Not found');
}
});
server.listen(3000);
This works, but it does not scale. As the number of routes grows, this single callback becomes a deeply nested chain of conditionals. There is no clean way to separate route logic, no built-in body parsing, and no straightforward way to share logic across routes.
The Same Server with Express
const express = require('express');
const app = express();
app.use(express.json());
app.get('/users', (req, res) => {
res.json({ users: [] });
});
app.post('/users', (req, res) => {
res.status(201).json({ message: 'User created' });
});
app.listen(3000);
Each route is defined independently. The intent of every handler is immediately clear. Response methods like res.json() and res.status() handle headers automatically. Adding a new route means adding one new line - not modifying a growing conditional block.
This is the core value of Express: it brings structure to HTTP server code without adding unnecessary complexity.
Creating Your First Express Server
Installation
Initialize a Node.js project and install Express
npm init -y
npm install express
Basic Server Setup
const express = require('express');
const app = express();
const PORT = 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});
express() creates an application instance. This instance is the central object you use to define routes, register middleware, and configure the server. app.listen() starts the HTTP server on the specified port.
At this point the server runs but has no routes defined. Any request made to it will receive a default 404 response from Express.
Adding Middleware
Before defining routes, it is common to register middleware that processes every incoming request. For a JSON API, the most essential middleware is the built-in JSON body parser:
app.use(express.json());
This line tells Express to parse incoming request bodies with a Content-Type of application/json and make the parsed data available on req.body.
Handling GET Requests
GET requests are used to retrieve data. They should not modify server state. In Express, a GET route is defined using app.get().
Basic GET Route
app.get('/users', (req, res) => {
res.json([
{ id: 1, name: 'Purakhnath' },
{ id: 2, name: 'Randeep' }
]);
});
When a client sends GET /users, Express matches this route and calls the handler function. The handler receives two objects: req (the incoming request) and res (the outgoing response). res.json() sends a JSON response with a 200 status code automatically.
Route Parameters
Route parameters allow dynamic segments in a URL path. They are defined with a colon prefix and accessed via req.params.
app.get('/users/:id', (req, res) => {
const userId = req.params.id;
res.json({ id: userId, name: 'Purakhnath' });
});
A request to GET /users/42 would set req.params.id to "42".
Query Parameters
Query parameters are the key-value pairs after the ? in a URL. They are accessed via req.query.
app.get('/users', (req, res) => {
const role = req.query.role;
res.json({ filteredBy: role });
});
A request to GET /users?role=admin would set req.query.role to "admin".
Handling POST Requests
POST requests are used to send data to the server, typically to create a new resource. The request body carries the data. In Express, a POST route is defined using app.post().
Basic POST Route
app.post('/users', (req, res) => {
const newUser = req.body;
console.log(newUser);
res.status(201).json({ message: 'User created', user: newUser });
});
req.body contains the parsed JSON sent by the client. This is only populated when express.json() middleware is registered. Without it, req.body is undefined.
res.status(201) sets the HTTP status code to 201 Created before sending the response. Chaining .json() sends the response body.
Example Request
POST /users
Content-Type: application/json
{
"name": "Karan",
"role": "admin"
}
The handler receives req.body as { name: "Karan", role: "admin" } and responds with a 201 status and confirmation JSON.
Sending Responses
Every route handler must send exactly one response. Express provides several methods on the res object for this purpose.
res.json()
Sends a JSON response. Automatically sets the Content-Type header to application/json.
res.json({ status: 'ok' });
res.send()
Sends a response of any type. If passed a string, Content-Type is set to text/html. If passed an object, it behaves like res.json().
res.send('Hello, world');
res.status()
Sets the HTTP status code. Must be chained with a send method to actually send the response.
res.status(404).json({ error: 'Not found' });
res.status(201).json({ message: 'Created' });
res.sendStatus()
Sends a response with just the status code and its standard message as the body. Useful for simple acknowledgments.
res.sendStatus(204); // sends "No Content"
Common Status Codes in REST APIs
| Code | Meaning | Typical Use |
|---|---|---|
| 200 | OK | Successful GET or PUT |
| 201 | Created | Successful POST |
| 204 | No Content | Successful DELETE |
| 400 | Bad Request | Invalid input from client |
| 404 | Not Found | Resource does not exist |
| 500 | Internal Server Error | Unhandled server error |
Handling Unmatched Routes
Express does not automatically return a meaningful 404 for undefined routes unless you add a catch-all handler at the end of your route definitions.
app.use((req, res) => {
res.status(404).json({ error: 'Route not found' });
});
This middleware runs only when no earlier route has matched the request. Placing it last ensures it acts as a fallback.
Conclusion
Express gives Node.js developers a clean, structured way to define routes and handle HTTP requests. It removes the boilerplate of raw Node HTTP servers and replaces it with a readable, route-first model that scales naturally as applications grow.
The patterns covered here -> GET and POST routes, route parameters, query strings, body parsing, and response methods -> are the building blocks of any Express-based API. Once these are solid, the next logical step is learning Express middleware in depth, organizing routes using express.Router(), and adding error handling middleware for production-grade reliability.




