The http module is one of Node.js's most important built-in modules because it allows you to create web servers and handle HTTP requests directly. Since Node.js is widely used for APIs, back-end services, and real-time applications, understanding the http module gives you a clear picture of how a server actually works before you move to frameworks like Express.
At a high level, the HTTP module helps a Node.js program listen for incoming requests, inspect request information such as the URL and method, and send a response back to the client. This means that when a browser, mobile app, or another service calls your server, the http module is one of the core tools that can receive that request and decide what to return.
The HTTP module is built into Node.js, so you do not need to install it with npm. You can load it immediately using require() in CommonJS.
const http = require("http");
Once the module is imported, you can create a server using http.createServer(). This method returns a server object and takes a callback function that runs every time a client connects.
The simplest use of the HTTP module is to create a server that responds with plain text or HTML. The callback function receives two important objects:
const http = require("http");
const server = http.createServer((req, res) => {
res.write("Hello from Node.js HTTP server!");
res.end();
});
server.listen(8080, () => {
console.log("Server is running on http://localhost:8080");
});
This server listens on port 8080. Whenever someone visits http://localhost:8080, the callback runs, the server writes a response, and res.end() closes the response. It is very important to call res.end(), because without it the client may wait indefinitely for the response to finish.
The request object contains information about what the client asked for. It includes values like the request URL, HTTP method, headers, and body data. The response object is used to set headers, choose status codes, and send the final response body. Together, these two objects form the core of basic Node.js server logic.
For example, if the browser requests /about, the request object lets you inspect that path. If the client sends a POST request to /users, the request object tells you the method is POST. On the response side, you might send back HTML, JSON, or a plain text message depending on what the client needs.
A good HTTP response should include the right status code and content type. The status code tells the client whether the request was successful, redirected, invalid, or failed. The Content-Type header tells the client how to interpret the returned data, such as plain text, HTML, or JSON.
const http = require("http");
http.createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/html" });
res.write("<h1>Welcome to Tutorials Logic</h1>");
res.end();
}).listen(8080);
In this example, 200 means success, and text/html tells the browser to render the response as HTML. If you omit the correct content type, the browser may treat the response as plain text instead of rendering it as intended.
| Status Code | Meaning |
|---|---|
200 | Request succeeded |
201 | Resource created successfully |
400 | Bad request from the client |
404 | Requested resource not found |
500 | Internal server error |
Using the correct status code makes APIs and web applications easier to debug and easier for clients to understand. A response body may look correct, but if the status code is wrong, consumers of the API may still behave incorrectly.
req.url and req.methodBefore using frameworks, basic routing in Node.js is often done manually with req.url and req.method. This lets you return different responses depending on the path or request type.
const http = require("http");
http.createServer((req, res) => {
if (req.url === "/" && req.method === "GET") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Home page");
} else if (req.url === "/about" && req.method === "GET") {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("About page");
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Page not found");
}
}).listen(8080);
This style works well for learning and small examples. In large projects, however, manual routing becomes hard to maintain, which is one reason frameworks like Express are so popular.
The request object contains a url property that includes the path and query string. If a client visits /search?q=node, the req.url value contains that URL fragment. To work with query parameters properly, developers usually combine the HTTP module with the URL class or the url module.
const http = require("http");
http.createServer((req, res) => {
const url = new URL(req.url, "http://localhost:8080");
const keyword = url.searchParams.get("q");
res.writeHead(200, { "Content-Type": "text/plain" });
res.end(`Search keyword: ${keyword}`);
}).listen(8080);
If the browser requests http://localhost:8080/?q=nodejs, this server returns Search keyword: nodejs. Query parameters are very common in search pages, filters, pagination, and API endpoints.
Modern APIs often respond with JSON rather than HTML. To send JSON correctly, you should set the Content-Type header to application/json and convert the JavaScript object to a string using JSON.stringify().
const http = require("http");
http.createServer((req, res) => {
const data = {
success: true,
message: "Data fetched successfully",
users: [
{ id: 1, name: "Aman" },
{ id: 2, name: "Riya" }
]
};
res.writeHead(200, { "Content-Type": "application/json" });
res.end(JSON.stringify(data));
}).listen(8080);
This kind of response is common in REST APIs. A front-end application can call the endpoint and parse the JSON result to display users, products, posts, or any other resource.
When the client sends data to the server using POST, the request body does not arrive all at once in simple Node.js HTTP handling. Instead, it arrives as a stream of chunks. You need to collect the chunks, combine them, and then parse the final result. This is one of the reasons raw Node.js HTTP handling feels more low-level than frameworks like Express.
const http = require("http");
http.createServer((req, res) => {
if (req.method === "POST" && req.url === "/users") {
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({
message: "User created",
user
}));
});
} else {
res.writeHead(404, { "Content-Type": "text/plain" });
res.end("Route not found");
}
}).listen(8080);
This example introduces event-driven request handling. The server listens for data events while chunks arrive and then waits for the end event before using the complete body. This is a good demonstration of why Node.js is often described as asynchronous and event-driven.
The HTTP module can return different types of content depending on the need of the application. For example, a server may return HTML to a browser, JSON to a front-end application, or plain text for a simple status endpoint. Choosing the correct content type is important because the client uses that header to decide how the data should be interpreted.
Learning the raw HTTP module is valuable because it shows you what happens underneath a framework. However, real applications often use Express or similar libraries because they provide easier routing, middleware support, body parsing, and a cleaner structure. For example, instead of manually checking req.url and assembling request bodies yourself, Express gives dedicated methods like app.get(), app.post(), and built-in middleware patterns.
Still, understanding the HTTP module makes Express easier to learn because you can see what the framework is abstracting away. Express does not replace the concept of HTTP requests and responses; it simply provides a more convenient API on top of them.
One common mistake is forgetting to call res.end(), which leaves the connection open and causes the browser or client to keep waiting. Another is returning JSON without using JSON.stringify(). A third mistake is not setting the correct content type, which can lead to incorrect rendering or parsing on the client side. Beginners also often expect POST request bodies to appear instantly in one variable, not realizing that body data usually arrives as chunks when working with the raw HTTP module.
Another frequent issue is using port numbers inconsistently. If your code listens on port 8080, the browser must open the same port. If another app is already using that port, the server will fail to start. Reading the error messages and logging startup output with console.log() is a good habit.
You can think of the HTTP module as the lowest everyday layer of web server programming in Node.js. The client sends a request, your server reads that request, decides what to do, and sends a response. Every route, API endpoint, HTML page, and JSON response eventually follows that same core pattern. Once this model is clear, it becomes much easier to understand frameworks, middleware, REST APIs, and server architecture in general.
http module is built into Node.js and is used to create web servers.
http.createServer() receives a request object and a response object for each incoming connection.
res.end().
res.writeHead() to set status codes and response headers such as Content-Type.
req.url and req.method.
application/json and JSON.stringify().
Explore 500+ free tutorials across 20+ languages and frameworks.