http — HTTP Server
Building HTTP servers in Node.js without frameworks — createServer, req/res objects, routing, status codes, and HTTPS.
Body data arrives as Buffer chunks, and a chunk boundary can fall in the middle of a multi-byte character such as ₹ (3 bytes in UTF-8) or Devanagari letters. body += chunk converts each chunk to a string on its own, so a character split between two chunks turns into � replacement marks. English-only test data never shows it.
import { createServer, request } from "http";
// a server whose response is split mid-character, as TCP chunks can be:
const server = createServer((req, res) => {
const bytes = Buffer.from('{"price":"₹499","city":"मुंबई"}');
res.write(bytes.subarray(0, 11)); // ends in the middle of the 3-byte "₹"
setTimeout(() => res.end(bytes.subarray(11)), 20);
}).listen(0, () => {
const req = request(`http://localhost:${server.address().port}/`, (res) => {
let body = "";
res.on("data", chunk => body += chunk); // each Buffer chunk decoded on its own
res.on("end", () => { console.log(body); server.close(); });
});
req.end();
});
// {"price":"���499","city":"मुंबई"}Your first HTTP server without a framework
Every web framework — Express, Fastify, Hono — sits on top of Node's built-in
http module. Understanding the raw http module shows you what frameworks
are actually doing.
The simplest possible server
import { createServer } from "http";
const server = createServer((req, res) => {
res.writeHead(200, { "Content-Type": "text/plain" });
res.end("Hello World!");
});
server.listen(3000, () => {
console.log("Running at http://localhost:3000");
});Reading the request
const server = createServer((req, res) => {
console.log(req.method); // "GET", "POST", "PUT", "DELETE" etc.
console.log(req.url); // "/api/users?page=2"
console.log(req.headers); // { "content-type": "application/json", ... }
// Parse URL and query string:
const url = new URL(req.url, "http://localhost");
console.log(url.pathname); // "/api/users"
console.log(url.searchParams.get("page")); // "2"
});Sending responses
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, {
"Content-Type": "application/json",
"Access-Control-Allow-Origin": "*", // CORS
});
res.end(JSON.stringify(data));
}
const server = createServer((req, res) => {
if (req.method === "GET" && req.url === "/api/ping") {
sendJSON(res, 200, { ok: true, ts: Date.now() });
return;
}
sendJSON(res, 404, { error: "Not found" });
});Reading the request body (POST/PUT)
// The body arrives as a stream — collect chunks:
function readBody(req) {
return new Promise((resolve, reject) => {
const chunks = [];
req.on("data", chunk => chunks.push(chunk));
req.on("end", () => resolve(Buffer.concat(chunks).toString()));
req.on("error", reject);
});
}
const server = createServer(async (req, res) => {
if (req.method === "POST" && req.url === "/api/users") {
try {
const raw = await readBody(req);
const user = JSON.parse(raw);
// ... save user ...
sendJSON(res, 201, { ...user, id: Date.now() });
} catch {
sendJSON(res, 400, { error: "Invalid JSON body" });
}
}
});The readBody() helper above gets this right: it keeps the raw Buffer chunks and decodes once, after Buffer.concat(). If you prefer working with strings, call res.setEncoding("utf8") (or req.setEncoding) first. Node then decodes across chunk boundaries for you, which also gave the correct output in testing:
import { createServer, request } from "http";
const server = createServer((req, res) => {
const bytes = Buffer.from('{"price":"₹499","city":"मुंबई"}');
res.write(bytes.subarray(0, 11));
setTimeout(() => res.end(bytes.subarray(11)), 20);
}).listen(0, () => {
const req = request(`http://localhost:${server.address().port}/`, (res) => {
const chunks = [];
res.on("data", chunk => chunks.push(chunk)); // keep the raw bytes
res.on("end", () => {
console.log(Buffer.concat(chunks).toString("utf-8")); // decode once, at the end
server.close();
});
});
req.end();
});
// {"price":"₹499","city":"मुंबई"}Official Sources
The Codex links only to official documentation and the ECMAScript specification. No third-party tutorials.