Node.js: HTTP Module
Last updated: 2026-08-26
Bob needed to quickly validate an API idea but didn’t want to set up an entire Express project, so he used the native HTTP module to get an API server up and running in just 20 lines of code. From handling request methods and parsing URL paths to returning JSON data and setting status codes, Bob found that once he understood the underlying principles, using a framework actually became much easier.
1. What You'll Learn
- Use
http.createServer/server.listento create an HTTP server - Read the core properties (method / url / headers) of the
requestobject - Use the
responseobject to send a response (writeHead / end / statusCode) - Use
new URL()to parse route paths and query parameters - Handling GET Requests and Query Parameters
- Handling POST Requests and Collecting Request Bodies
- Set the Content-Type and custom response headers
- The Meanings and Use Cases of Common HTTP Status Codes
2. Creating Your First HTTP Server
http.createServer Accepts a callback function that is triggered every time a request is received. The callback takes two parameters: request (the request object) and response (the response object). server.listen specifies the listening port.
▶ Example: Minimizing an HTTP Server
const http = require('http');
const server = http.createServer((req, res) => {
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
node server.js
Server running at http://localhost:3000/
Simply visit http://localhost:3000/ in your browser to view Hello, World!.
3. The HTTP Request-Response Lifecycle
Every HTTP interaction follows the process of Request → Routing → Processing → Response; understanding this lifecycle is the foundation for building web services.
flowchart LR
A["Client"] -->|"Send Request"| B["request Object<br/>method / url / headers"]
B --> C["Routing Resolution<br/>pathname + searchParams"]
C --> D{"Request Method?"}
D -->|GET| E["Read Query Parameters"]
D -->|POST / PUT| F["Collect the request body"]
E --> G["Business Processing"]
F --> G
G --> H["response Object<br/>statusCode / headers / body"]
H -->|"Return Response"| A
4. Core Properties of the request Object
request The object contains all the request information sent by the client; the three most commonly used properties are method, url, and headers.
| Property / Method | Type | Description | Example Value |
|---|---|---|---|
req.method |
string | Request method | 'GET', 'POST' |
req.url |
string | Request path (including query string) | '/api/users?id=1' |
req.headers |
object | Request header object | { 'content-type': 'application/json' } |
req.httpVersion |
string | HTTP protocol version | '1.1' |
req.socket |
object | underlying socket object | — |
▶ Example: Printing Request Information
const http = require('http');
const server = http.createServer((req, res) => {
console.log(`Method: ${req.method}`);
console.log(`URL: ${req.url}`);
console.log(`Content-Type: ${req.headers['content-type'] || 'N/A'}`);
res.end('Check your terminal for request info.');
});
server.listen(3000);
Send a test request using curl:
curl -X POST http://localhost:3000/api/data -H "Content-Type: application/json"
Method: POST
URL: /api/data
Content-Type: application/json
5. Core Methods of the response Object
response This object is used to send response data to the client, including the status code, response headers, and response body.
| Method / Property | Description | Example |
|---|---|---|
res.writeHead(statusCode, headers) |
Writing status codes and multiple response headers in a single operation | res.writeHead(200, { 'Content-Type': 'text/plain' }) |
res.statusCode = n |
Set status code individually | res.statusCode = 404 |
res.setHeader(name, value) |
Set a single response header | res.setHeader('Content-Type', 'application/json') |
res.write(data) |
Write response body data (can be called multiple times) | res.write('partial') |
res.end(data) |
Send the response body and end the response | res.end('done') |
res.writeHead After being called, then res.end() |
Send buffered data | — |
▶ Example: Return a JSON response
const http = require('http');
const server = http.createServer((req, res) => {
const data = { message: 'Success', timestamp: Date.now() };
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
});
server.listen(3000);
curl http://localhost:3000/
{"message":"Success","timestamp":1719792000000}
6. URL Routing Resolution
req.url contains the complete request path and query string. Using new URL() makes it easy to split the pathname and searchParams, enabling path-based routing.
▶ Example: Path-Based Routing
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
res.writeHead(200, { 'Content-Type': 'text/plain' });
if (pathname === '/') {
res.end('Home Page');
} else if (pathname === '/about') {
res.end('About Page');
} else if (pathname === '/api/status') {
res.end('OK');
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
7. GET Requests and Query Parameters
The parameters for a GET request are included in the URL's query string, and you can retrieve the key-value pairs directly using url.searchParams.
▶ Example: Parsing Query Parameters
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method !== 'GET') {
res.writeHead(405, { 'Content-Type': 'text/plain' });
res.end('Method Not Allowed');
return;
}
const url = new URL(req.url, `http://${req.headers.host}`);
const name = url.searchParams.get('name') || 'Guest';
const page = url.searchParams.get('page') || '1';
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ name, page }));
});
server.listen(3000);
curl "http://localhost:3000/?name=Bob&page=3"
{"name":"Bob","page":"3"}
Note:
searchParams.get()always returns a string, so you must manually convert it to a number or other type.
8. POST Requests and Request Body Collection
Data for POST requests is transmitted via the request body. The request object is a readable stream; you need to listen for the data event to collect data blocks and listen for the end event to process the complete data.
▶ Example: Collecting the POST request body
const http = require('http');
const server = http.createServer((req, res) => {
if (req.method === 'POST' && req.url === '/api/users') {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
try {
const data = JSON.parse(body);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id: 1, ...data }));
} catch (e) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.writeHead(404, { 'Content-Type': 'text/plain' });
res.end('Not Found');
}
});
server.listen(3000);
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Bob\",\"age\":30}"
{"id":1,"name":"Bob","age":30}
9. Content-Type and Response Headers
Content-Type This is one of the most critical response headers in HTTP communication, as it specifies the data format of the response body to the client. Setting it incorrectly will prevent the client from parsing the data correctly.
| Content-Type | Purpose | Description |
|---|---|---|
text/plain |
Plain text | The most basic text type, with no formatting |
text/html |
HTML page | The browser renders this as a web page |
application/json |
JSON Data | The Most Commonly Used Response Format for APIs |
application/x-www-form-urlencoded |
Form Data | Default Form Submission Format |
multipart/form-data |
File Upload | Form Submission with Files |
application/xml |
XML Data | SOAP API or Legacy Interface |
text/css |
CSS Style Sheet | Style Sheet |
application/octet-stream |
Binary Stream | File Download Scenarios |
▶ Example: The Effect of Different Content-Types for the Same Data
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
if (url.pathname === '/plain') {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('<h1>This is plain text</h1>');
} else if (url.pathname === '/html') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>This is HTML</h1>');
} else if (url.pathname === '/json') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'This is JSON' }));
}
});
server.listen(3000);
When you visit /plain, the browser displays the source code tab; when you visit /html, the browser renders the main heading; and when you visit /json, the browser displays JSON data.
10. Quick Reference for HTTP Status Codes
A status code is a standardized representation of the server's response to a request; the client determines its next course of action based on the status code.
| Status Code | Category | Meaning | Common Scenarios |
|---|---|---|---|
| 200 | 2xx Success | OK | GET request successfully returned data |
| 201 | 2xx Success | Created | POST Resource created successfully |
| 204 | 2xx Success | No Content | Deletion successful, no content returned |
| 301 | 3xx Redirect | Moved Permanently | Permanent redirect to new URL |
| 302 | 3xx Redirect | Found | Temporary Redirect |
| 304 | 3xx Redirect | Not Modified | Cache hit; no need to retransmit |
| 400 | 4xx Client Error | Bad Request | Invalid request parameter format |
| 401 | 4xx Client Error | Unauthorized | Not authenticated; login required |
| 403 | 4xx Client Error | Forbidden | Authenticated but no permissions |
| 404 | 4xx Client Error | Not Found | Route or Resource Does Not Exist |
| 405 | 4xx Client Error | Method Not Allowed | The request method is not allowed |
| 500 | 5xx Server Error | Internal Server Error | Internal Server Error |
| 502 | 5xx Server Error | Bad Gateway | The gateway/proxy received an invalid response |
| 503 | 5xx Server Error | Service Unavailable | Service temporarily unavailable |
▶ Example: Returning Different Status Codes Based on Conditions
const http = require('http');
const server = http.createServer((req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const id = url.searchParams.get('id');
if (!id) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Missing id parameter' }));
} else if (id === '0') {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
} else {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ id, name: 'Bob' }));
}
});
server.listen(3000);
11. Comprehensive Example: A Simple REST API Server
Combine all the concepts covered so far to build a REST API server that supports GET/POST routes, JSON responses, and query parameter parsing. Maintain a list of users in memory and support three operations: querying all users, querying a single user, and creating a user.
const http = require('http');
const users = [
{ id: 1, name: 'Bob', email: 'bob@example.com' },
{ id: 2, name: 'Alice', email: 'alice@example.com' },
];
let nextId = 3;
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', (chunk) => { body += chunk.toString(); });
req.on('end', () => {
try { resolve(JSON.parse(body)); }
catch (e) { reject(e); }
});
req.on('error', reject);
});
}
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, {
'Content-Type': 'application/json',
'X-Powered-By': 'Node.js',
});
res.end(JSON.stringify(data));
}
const server = http.createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const pathname = url.pathname;
// GET /api/users
if (req.method === 'GET' && pathname === '/api/users') {
const limit = parseInt(url.searchParams.get('limit')) || 10;
sendJSON(res, 200, users.slice(0, limit));
return;
}
// GET /api/users/:id
if (req.method === 'GET' && pathname.startsWith('/api/users/')) {
const id = parseInt(pathname.split('/').pop());
const user = users.find((u) => u.id === id);
if (!user) {
sendJSON(res, 404, { error: 'User not found' });
} else {
sendJSON(res, 200, user);
}
return;
}
// POST /api/users
if (req.method === 'POST' && pathname === '/api/users') {
try {
const data = await parseBody(req);
if (!data.name || !data.email) {
sendJSON(res, 400, { error: 'name and email are required' });
return;
}
const newUser = { id: nextId++, name: data.name, email: data.email };
users.push(newUser);
sendJSON(res, 201, newUser);
} catch (e) {
sendJSON(res, 400, { error: 'Invalid JSON body' });
}
return;
}
// 404 fallback
sendJSON(res, 404, { error: 'Route not found' });
});
server.listen(3000, () => {
console.log('REST API server running at http://localhost:3000/');
});
Test all interfaces:
# Query All Users
curl http://localhost:3000/api/users
# Query a Single User
curl http://localhost:3000/api/users/1
# Create a New User
curl -X POST http://localhost:3000/api/users -H "Content-Type: application/json" -d "{\"name\":\"Charlie\",\"email\":\"charlie@example.com\"}"
# Accessing a Route That Does Not Exist
curl http://localhost:3000/unknown
[{"id":1,"name":"Bob","email":"bob@example.com"},{"id":2,"name":"Alice","email":"alice@example.com"}]
{"id":1,"name":"Bob","email":"bob@example.com"}
{"id":3,"name":"Charlie","email":"charlie@example.com"}
{"error":"Route not found"}
❓ FAQ
req object is a readable stream. You need to listen for the data event to collect buffer blocks and concatenate them, then listen for the end event to indicate that data reception is complete, and finally use JSON.parse() or Buffer.concat() to process the complete data.res.end() Sends data and closes the response; it must be called once—and only once—for each request; res.write() only writes data but does not close the response; it can be called multiple times for streaming or chunked transmission, but res.end() must still be called at the end to close the response.text/plain. Clients (browsers, fetch, curl) will not automatically parse the response as JSON, which may result in response.json() errors or the data not being displayed correctly. Setting it to application/json ensures that clients know how to parse the response body.new URL(req.url, 'http://localhost') to create a URL object, then use url.searchParams.get('key') to retrieve parameter values, or use url.searchParams.entries() to iterate through all parameters.server.listen?server.on('listening', callback) event, which has the same effect.new URL need to include base?req.url contains only the path portion (e.g., /api?id=1) and is not a complete URL. new URL() requires a base parameter to complete the protocol and hostname; otherwise, a TypeError will be thrown. The value of base does not affect the parsing results of pathname and searchParams.📖 Summary
- Key Concepts and How to Apply Them
- Core Concepts and How to Use the First HTTP Server
- Core Concepts and Usage of the HTTP Request-Response Lifecycle
- Key Concepts and Usage of the Core Properties of the
requestObject - Key Concepts and Usage of the Core Methods of the
responseObject - Core Concepts and Usage of URL Routing
- Core Concepts and Usage of GET Requests and Query Parameters
- Core Concepts and Usage of POST Requests and Request Body Collection
📝 Exercises
- Complete all the code examples in this lesson and make sure each one runs correctly.
- Modify the comprehensive example and add your own extensions
- Review the official documentation, identify 1–2 APIs not covered in this lesson, and write test code for them.
- Reflection: How would you apply what you’ve learned in this lesson to a real-world project?
- Try to combine what you’ve learned in this lesson with material from previous lessons to build a small project.