When learning Node.js, one of the best ways to understand how backend development works is to create a web server without using a framework.
Node.js provides a built-in module called the
HTTP module that allows you to create web servers, receive HTTP requests, and send responses to clients.
In this tutorial, we will learn how to use the Node.js HTTP module to create our first web server from scratch.
You will learn:
- What the Node.js HTTP module is
- How HTTP requests and responses work
- How to create a basic web server
- How to handle different URLs
- How to handle HTTP methods
- How to return HTML and JSON responses
- How to set HTTP status codes
- How to set response headers
- How to read request data
- How to create a simple REST API
- Why frameworks such as Express.js are useful
What is the Node.js HTTP Module?
The
HTTP module is a built-in Node.js module that provides functionality for creating HTTP servers and making HTTP-related requests.
Because it is a built-in module, you don’t need to install it using npm.
You can import it using:
const http = require("http");
You can then use the module to create a web server.
What is a Web Server?
A web server is a program that receives requests from clients and sends responses back.
For example, when you enter:
http://localhost:3000
into your browser, the browser sends an HTTP request to the server.
The server processes the request and sends a response.
The basic flow looks like this:
Browser
|
| HTTP Request
↓
Node.js Server
|
| HTTP Response
↓
Browser
A response could contain:
- HTML
- JSON
- Text
- Images
- Files
- Other data
What is HTTP?
HTTP stands for
Hypertext Transfer Protocol.
It is a protocol used for communication between clients and servers.
For example:
Client → HTTP Request → Server
Client ← HTTP Response ← Server
An HTTP request can contain information such as:
- HTTP method
- URL
- Headers
- Request body
The server sends back:
- Status code
- Response headers
- Response body
Creating Your First Node.js Web Server
Let’s create a simple Node.js server.
Create a file called:
server.js
Add the following code:
const http = require("http");
const server = http.createServer((req, res) => {
res.end("Hello from Node.js!");
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});
Now run:
node server.js
You should see:
Server running on port 3000
Open your browser and visit:
http://localhost:3000
You should see:
Hello from Node.js!
Congratulations! You have created your first Node.js web server.
Understanding the Code
Let’s break down the example.
Importing the HTTP Module
const http = require("http");
This loads Node.js’s built-in HTTP module.
No npm installation is required.
Creating the Server
const server = http.createServer((req, res) => {
res.end("Hello from Node.js!");
});
The
createServer() method creates an HTTP server.
The callback function receives two important objects:
req
res
req
req stands for
request.
It contains information about the request sent by the client.
res
res stands for
response.
It is used to send a response back to the client.
Sending the Response
The following line sends a response:
res.end("Hello from Node.js!");
Once
res.end() is called, the response is completed.
Starting the Server
server.listen(3000, () => {
console.log("Server running on port 3000");
});
The
listen() method tells Node.js to listen for incoming connections on port
3000.
You can then access the server using:
http://localhost:3000
Understanding req and res
The
req and
res objects are central to working with the Node.js HTTP module.
For example:
const http = require("http");
const server = http.createServer((req, res) => {
console.log(req.method);
console.log(req.url);
res.end("Request received");
});
server.listen(3000);
If you visit:
http://localhost:3000
you may see:
GET
/
in your terminal.
Understanding req.method
The
req.method property tells you which HTTP method was used.
For example:
console.log(req.method);
Common HTTP methods include:
GET
POST
PUT
PATCH
DELETE
If you visit a URL normally from your browser, the request will usually use:
GET
Understanding req.url
The
req.url property contains the requested URL path.
For example:
console.log(req.url);
If the user visits:
http://localhost:3000/about
the value will generally be:
/about
For:
http://localhost:3000/contact
it will be:
/contact
This allows us to create different responses for different URLs.
Creating Multiple Routes
The basic HTTP module doesn’t provide Express-style routing, but we can implement simple routing ourselves.
For example:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/" && req.method === "GET") {
res.end("Home Page");
}
else if (req.url === "/about" && req.method === "GET") {
res.end("About Page");
}
else if (req.url === "/contact" && req.method === "GET") {
res.end("Contact Page");
}
else {
res.statusCode = 404;
res.end("Page Not Found");
}
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});
Now you can visit:
/
/about
/contact
Each URL returns a different response.
Setting the HTTP Status Code
HTTP status codes tell the client what happened with a request.
For example, a successful response commonly uses:
200 OK
A page that doesn’t exist commonly uses:
404 Not Found
You can set a status code using:
res.statusCode = 404;
For example:
const http = require("http");
const server = http.createServer((req, res) => {
res.statusCode = 404;
res.end("Page Not Found");
});
server.listen(3000);
Common HTTP Status Codes
Here are some status codes you will frequently encounter:
| Status Code |
Meaning |
| 200 |
OK |
| 201 |
Created |
| 204 |
No Content |
| 400 |
Bad Request |
| 401 |
Unauthorized |
| 403 |
Forbidden |
| 404 |
Not Found |
| 500 |
Internal Server Error |
Understanding HTTP status codes is important when building web servers and APIs.
Setting Response Headers
HTTP responses can contain headers that provide additional information about the response.
You can set headers using:
res.setHeader("Content-Type", "text/plain");
For example:
const http = require("http");
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "text/plain");
res.end("Hello Node.js");
});
server.listen(3000);
The
Content-Type header tells the client what type of content the response contains.
Returning HTML
You can return HTML from a Node.js HTTP server.
Set the content type to:
text/html
Example:
const http = require("http");
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "text/html");
res.end(`
<html>
<head>
<title>Node.js Server</title>
</head>
<body>
<h3>Welcome to Node.js</h3>
<p>This page is served by Node.js.</p>
</body>
</html>
`);
});
server.listen(3000);
Open:
http://localhost:3000
Your browser will render the HTML page.
Returning JSON
Node.js HTTP servers can also return JSON data.
This is particularly useful when creating APIs.
Example:
const http = require("http");
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
const response = {
message: "Hello from Node.js",
success: true
};
res.end(JSON.stringify(response));
});
server.listen(3000);
The response will look like:
{
"message": "Hello from Node.js",
"success": true
}
The
JSON.stringify() method converts the JavaScript object into a JSON string that can be sent as the HTTP response body.
Creating a Simple JSON API
Let’s create a small API that returns a list of users.
const http = require("http");
const users = [
{
id: 1,
name: "John"
},
{
id: 2,
name: "Sarah"
}
];
const server = http.createServer((req, res) => {
if (req.url === "/api/users" && req.method === "GET") {
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify(users));
} else {
res.statusCode = 404;
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
message: "Route not found"
}));
}
});
server.listen(3000, () => {
console.log("API running on port 3000");
});
Now visit:
http://localhost:3000/api/users
The server returns:
[
{
"id": 1,
"name": "John"
},
{
"id": 2,
"name": "Sarah"
}
]
This is the basic idea behind an API.
Handling Different HTTP Methods
You can check both the URL and HTTP method.
For example:
if (req.url === "/users" && req.method === "GET") {
// Get users
}
For a POST request:
if (req.url === "/users" && req.method === "POST") {
// Create user
}
For PUT:
if (req.url === "/users" && req.method === "PUT") {
// Update user
}
For DELETE:
if (req.url === "/users" && req.method === "DELETE") {
// Delete user
}
This allows you to build basic REST APIs using only Node.js.
Reading POST Request Data
When a client sends data using a POST request, the request body arrives as a stream.
You can listen for data using the
data event.
For example:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/users" && req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
console.log(body);
res.end("User data received");
});
} else {
res.statusCode = 404;
res.end("Not Found");
}
});
server.listen(3000);
The request body is received in chunks.
The
data event is triggered when a chunk arrives.
The
end event indicates that the entire request body has been received.
Parsing JSON Request Data
If the client sends JSON, you can parse the request body using
JSON.parse().
For example:
const http = require("http");
const server = http.createServer((req, res) => {
if (req.url === "/users" && req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
try {
const user = JSON.parse(body);
console.log(user);
res.setHeader("Content-Type", "application/json");
res.end(JSON.stringify({
message: "User received",
user: user
}));
} catch (error) {
res.statusCode = 400;
res.end("Invalid JSON");
}
});
} else {
res.statusCode = 404;
res.end("Not Found");
}
});
server.listen(3000);
A client could send:
{
"name": "John",
"email": "john@example.com"
}
The server parses the JSON and can work with it as a JavaScript object.
Understanding Request Headers
HTTP requests can contain headers that provide additional information about the request.
You can access them through:
req.headers
For example:
const http = require("http");
const server = http.createServer((req, res) => {
console.log(req.headers);
res.end("Headers received");
});
server.listen(3000);
You can also access a specific header:
console.log(req.headers["user-agent"]);
Another common header is:
console.log(req.headers["content-type"]);
Headers are commonly used for things such as:
- Content type
- Authentication
- Cookies
- Caching
- Client information
Understanding Query Parameters
A URL can contain query parameters.
For example:
http://localhost:3000/search?name=john
The query parameter is:
name=john
Node.js provides the
URL class to help parse URLs.
For example:
const http = require("http");
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const name = url.searchParams.get("name");
res.end(`Searching for: ${name}`);
});
server.listen(3000);
If you visit:
/search?name=john
the response will be:
Searching for: john
Creating a Simple REST API
Let’s combine what we have learned to create a small REST-style API.
const http = require("http");
const users = [
{
id: 1,
name: "John"
},
{
id: 2,
name: "Sarah"
}
];
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url === "/users" && req.method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify(users));
} else if (req.url === "/users" && req.method === "POST") {
let body = "";
req.on("data", (chunk) => {
body += chunk;
});
req.on("end", () => {
try {
const newUser = JSON.parse(body);
newUser.id = users.length + 1;
users.push(newUser);
res.statusCode = 201;
res.end(JSON.stringify(newUser));
} catch (error) {
res.statusCode = 400;
res.end(JSON.stringify({
message: "Invalid JSON"
}));
}
});
} else {
res.statusCode = 404;
res.end(JSON.stringify({
message: "Route not found"
}));
}
});
server.listen(3000, () => {
console.log("Server running on port 3000");
});
This simple example demonstrates:
- Creating an HTTP server
- Handling GET requests
- Handling POST requests
- Reading request bodies
- Parsing JSON
- Sending JSON responses
- Setting status codes
- Basic routing
Testing Your HTTP Server
You can test a Node.js server using your browser,
curl, or an API client.
For a GET request:
curl http://localhost:3000/users
For a POST request:
curl -X POST http://localhost:3000/users \
-H "Content-Type: application/json" \
-d "{\"name\":\"Michael\"}"
You can also use tools such as Postman or similar API clients to send different types of HTTP requests.
Serving an HTML File
The HTTP module can also work with the File System module to serve files.
For example:
const http = require("http");
const fs = require("fs");
const server = http.createServer((req, res) => {
if (req.url === "/") {
fs.readFile("index.html", (error, data) => {
if (error) {
res.statusCode = 500;
res.end("Unable to load page");
return;
}
res.setHeader("Content-Type", "text/html");
res.end(data);
});
} else {
res.statusCode = 404;
res.end("Page Not Found");
}
});
server.listen(3000);
This demonstrates how Node.js modules can work together.
Here:
const http = require("http");
const fs = require("fs");
we are using:
- HTTP module for the server
- File System module for reading the HTML file
Understanding Ports
A server needs a port on which it listens for incoming connections.
For example:
server.listen(3000);
means the application is listening on port
3000.
You can use another port:
server.listen(5000);
The application would then be available at:
http://localhost:5000
Common development ports include:
3000
5000
8000
8080
The port you choose depends on your application and environment.
Using an Environment Variable for the Port
Instead of hard-coding the port, you can use an environment variable:
const port = process.env.PORT || 3000;
server.listen(port, () => {
console.log(`Server running on port ${port}`);
});
This is useful when deploying an application because the hosting environment may provide the port number.
Node.js HTTP Module vs Express.js
At this point, you might wonder:
If Node.js already provides an HTTP module, why do developers use Express.js?
The HTTP module provides the low-level functionality needed to create an HTTP server.
However, as applications become larger, manually handling routing, request parsing, middleware, validation, and other concerns can become repetitive.
Express.js provides a higher-level framework that simplifies many of these tasks.
For example, with the HTTP module:
if (req.url === "/users" && req.method === "GET") {
// Handle request
}
With Express.js:
app.get("/users", (req, res) => {
res.json(users);
});
Express provides convenient features such as:
- Routing
- Middleware
- Request handling
- Response helpers
- Error handling
- API development
Understanding the HTTP module first makes it easier to understand what frameworks such as Express.js are doing for you.
Common Beginner Mistakes
1. Forgetting to Call listen()
Creating a server is not enough.
You also need to start listening:
server.listen(3000);
2. Forgetting res.end()
A response should eventually be completed.
For example:
res.end("Hello");
If your request handler never completes the response, the client may continue waiting.
3. Using the Wrong Content-Type
If you return JSON, use:
res.setHeader("Content-Type", "application/json");
If you return HTML:
res.setHeader("Content-Type", "text/html");
Using the appropriate headers helps clients correctly interpret the response.
4. Forgetting to Handle Errors
File operations, JSON parsing, and other asynchronous operations can fail.
Always consider how errors should be handled.
5. Forgetting That Request Bodies Arrive as Streams
POST and other requests with bodies may arrive in multiple chunks.
Use:
req.on("data", ...)
and:
req.on("end", ...)
to collect the body before processing it.
Important Node.js HTTP Methods
The HTTP module provides several useful methods and properties.
Some important ones include:
http.createServer()
Creates an HTTP server.
server.listen()
Starts listening for incoming connections.
req.method
Returns the HTTP request method.
req.url
Returns the requested URL.
req.headers
Provides request headers.
res.statusCode
Sets or retrieves the response status code.
res.setHeader()
Sets a response header.
res.write()
Writes part of the response.
res.end()
Finishes the response.
res.write() vs res.end()
You can use
res.write() to send part of a response.
For example:
res.write("Hello ");
res.write("from ");
res.write("Node.js");
res.end();
The client receives:
Hello from Node.js
However, you must eventually call:
res.end();
You can also provide the final content directly:
res.end("Hello from Node.js");
For simple responses,
res.end() is often sufficient.
Complete Beginner Example
Here is a complete example that combines several concepts:
const http = require("http");
const server = http.createServer((req, res) => {
res.setHeader("Content-Type", "application/json");
if (req.url === "/" && req.method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify({
message: "Welcome to the Node.js server"
}));
} else if (req.url === "/about" && req.method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify({
message: "This is the About page"
}));
} else if (req.url === "/api/users" && req.method === "GET") {
res.statusCode = 200;
res.end(JSON.stringify([
{
id: 1,
name: "John"
},
{
id: 2,
name: "Sarah"
}
]));
} else {
res.statusCode = 404;
res.end(JSON.stringify({
message: "Route not found"
}));
}
});
server.listen(3000, () => {
console.log("Server running at http://localhost:3000");
});
This small server demonstrates the fundamental concepts you need to understand before moving to a web framework.
Conclusion
The Node.js HTTP module provides the basic functionality required to create HTTP servers without installing an external framework.
In this article, you learned how to:
- Import the HTTP module
- Create a Node.js web server
- Start a server with
listen()
- Handle requests
- Send responses
- Work with URLs
- Check HTTP methods
- Set status codes
- Set response headers
- Return HTML
- Return JSON
- Read request bodies
- Handle query parameters
- Build a basic REST API
The most important objects to remember are:
req → Incoming request
res → Outgoing response
And some of the most useful properties and methods are:
req.method
req.url
req.headers
res.statusCode
res.setHeader()
res.write()
res.end()
Although the HTTP module is powerful, manually handling routing and request processing can become complicated as an application grows. This is one of the reasons frameworks such as
Express.js are so popular in Node.js development.
Now that you understand how a basic Node.js HTTP server works, the next logical topic is
Express.js Routing: A Beginner’s Guide, where we will learn how Express makes routing much easier and cleaner.