Node.js includes a built-in http module that lets you create HTTP servers
without any external dependencies. This is the foundation that frameworks like Express
are built upon.
Understanding the raw HTTP module helps you understand what's happening under the hood
and gives you more control when you need it.
Minimal Server Node.js
server.js
import { createServer } from 'node:http';
const server = createServer((request, response) => {
response.writeHead(200, { 'Content-Type': 'text/plain' });
response.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
How it works:
createServer() takes a callback that runs for every request
request contains info about the incoming request (method, URL, headers)
response is used to send data back to the client
writeHead() sets the status code and headers
end() sends the body and signals the response is complete
Run it
$ node server.js
Server running at http://localhost:3000/
Accessing Request Information
server.js
import { createServer } from 'node:http';
const server = createServer((req, res) => {
console.log(req.method);
console.log(req.url);
console.log(req.headers.host);
console.log(req.headers['user-agent']);
const url = new URL(req.url, `http://${req.headers.host}`);
console.log(url.pathname);
console.log(url.searchParams.get('id'));
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
method: req.method,
path: url.pathname,
query: Object.fromEntries(url.searchParams)
}));
});
server.listen(3000);
The request object is a readable stream that also includes metadata
like method, url, and headers. Use the
URL constructor to parse the URL and extract path and query parameters.
Reading the Request Body
server.js
import { createServer } from 'node:http';
async function parseJSON(request) {
const chunks = [];
for await (const chunk of request) {
chunks.push(chunk);
}
const body = Buffer.concat(chunks).toString();
return body ? JSON.parse(body) : null;
}
const server = createServer(async (req, res) => {
if (req.method === 'POST') {
try {
const data = await parseJSON(req);
console.log('Received:', data);
res.writeHead(201, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ success: true, data }));
} catch (error) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
} else {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Send a POST request with JSON body');
}
});
server.listen(3000);
The request body arrives as a stream of chunks. Use for await...of to
collect them, then concatenate and parse. Always handle parse errors gracefully.
Test with curl
$ curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
{"success":true,"data":{"name":"Alice","email":"alice@example.com"}}
Response Headers & Status Codes
server.js
const server = createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
res.setHeader('X-Request-ID', crypto.randomUUID());
res.setHeader('Cache-Control', 'no-store');
res.setHeader('Access-Control-Allow-Origin', '*');
res.setHeader('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE');
res.statusCode = 200;
res.end(JSON.stringify({ message: 'Success' }));
});
Use setHeader() for individual headers or writeHead()
to set status and multiple headers at once. Headers must be set before calling
write() or end().
Complete Example
server.js
import { createServer } from 'node:http';
const PORT = process.env.PORT || 3000;
async function parseJSON(req) {
const chunks = [];
for await (const chunk of req) chunks.push(chunk);
const body = Buffer.concat(chunks).toString();
return body ? JSON.parse(body) : null;
}
function sendJSON(res, status, data) {
res.writeHead(status, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
const server = createServer(async (req, res) => {
const url = new URL(req.url, `http://${req.headers.host}`);
const { method } = req;
const { pathname } = url;
console.log(`${method} ${pathname}`);
if (pathname === '/health') {
return sendJSON(res, 200, { status: 'ok' });
}
if (pathname === '/echo' && method === 'POST') {
try {
const body = await parseJSON(req);
return sendJSON(res, 200, {
method,
path: pathname,
query: Object.fromEntries(url.searchParams),
body
});
} catch {
return sendJSON(res, 400, { error: 'Invalid JSON' });
}
}
sendJSON(res, 404, { error: 'Not Found' });
});
server.listen(PORT, () => {
console.log(`Server running at http://localhost:${PORT}/`);
});
← Back to HTTP Examples