Building an HTTP Server
Node.js was built for networking. Let's create an HTTP server from scratch - no frameworks needed.
Your First Server
// server.js
const http = require('http');
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.end('Hello, World!');
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Run it:
node server.js
Output:
Server running at http://localhost:3000/
Open http://localhost:3000 in your browser - you'll see "Hello, World!"
Press Ctrl+C to stop the server.
Understanding Request and Response
const http = require('http');
const server = http.createServer((req, res) => {
// req = incoming request
console.log('Method:', req.method); // GET, POST, etc.
console.log('URL:', req.url); // /path/to/resource
console.log('Headers:', req.headers); // Object with all headers
// res = our response
res.statusCode = 200; // HTTP status
res.setHeader('Content-Type', 'text/plain');
res.write('Hello '); // Write partial response
res.end('World!'); // Complete the response
});
server.listen(3000);
Basic Routing
Route different URLs to different responses:
// router.js
const http = require('http');
const server = http.createServer((req, res) => {
// Set default headers
res.setHeader('Content-Type', 'text/plain');
// Route based on URL
if (req.url === '/') {
res.statusCode = 200;
res.end('Welcome to the homepage!');
} else if (req.url === '/about') {
res.statusCode = 200;
res.end('About us page');
} else if (req.url === '/contact') {
res.statusCode = 200;
res.end('Contact us at hello@example.com');
} else {
res.statusCode = 404;
res.end('Page not found');
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Test it:
curl http://localhost:3000/
curl http://localhost:3000/about
curl http://localhost:3000/unknown
Output:
Welcome to the homepage!
About us page
Page not found
Returning JSON
APIs return JSON. Here's how:
// api.js
const http = require('http');
const users = [
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
{ id: 3, name: 'Charlie', email: 'charlie@example.com' }
];
const server = http.createServer((req, res) => {
// CORS headers (allow browser requests from any origin)
res.setHeader('Access-Control-Allow-Origin', '*');
if (req.url === '/api/users' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(users));
} else if (req.url.startsWith('/api/users/') && req.method === 'GET') {
const id = parseInt(req.url.split('/')[3]);
const user = users.find(u => u.id === id);
if (user) {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(user));
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'User not found' }));
}
} else {
res.writeHead(404, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ error: 'Not found' }));
}
});
server.listen(3000, () => {
console.log('API running at http://localhost:3000/');
});
Test it:
curl http://localhost:3000/api/users
curl http://localhost:3000/api/users/1
curl http://localhost:3000/api/users/99
Output:
[{"id":1,"name":"Alice","email":"alice@example.com"},...]
{"id":1,"name":"Alice","email":"alice@example.com"}
{"error":"User not found"}
Handling POST Requests
POST requests have a body. You need to collect it:
// post-server.js
const http = require('http');
const users = [];
const server = http.createServer((req, res) => {
res.setHeader('Content-Type', 'application/json');
if (req.url === '/api/users' && req.method === 'GET') {
res.end(JSON.stringify(users));
} else if (req.url === '/api/users' && req.method === 'POST') {
// Collect the request body
let body = '';
req.on('data', chunk => {
body += chunk.toString();
});
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 (err) {
res.statusCode = 400;
res.end(JSON.stringify({ error: 'Invalid JSON' }));
}
});
} else {
res.statusCode = 404;
res.end(JSON.stringify({ error: 'Not found' }));
}
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Test it:
# Create a user
curl -X POST http://localhost:3000/api/users \
-H "Content-Type: application/json" \
-d '{"name":"Alice","email":"alice@example.com"}'
# Get all users
curl http://localhost:3000/api/users
Output:
{"name":"Alice","email":"alice@example.com","id":1}
[{"name":"Alice","email":"alice@example.com","id":1}]
Serving HTML Files
// html-server.js
const http = require('http');
const fs = require('fs');
const path = require('path');
const server = http.createServer((req, res) => {
// Default to index.html
let filePath = req.url === '/' ? '/index.html' : req.url;
filePath = path.join(__dirname, 'public', filePath);
// Get file extension for content type
const ext = path.extname(filePath);
const contentTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'text/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg'
};
const contentType = contentTypes[ext] || 'text/plain';
// Read and serve the file
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>404 - File Not Found</h1>');
} else {
res.writeHead(500);
res.end('Server Error');
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
}
});
});
server.listen(3000, () => {
console.log('Server running at http://localhost:3000/');
});
Create public/index.html:
<!DOCTYPE html>
<html>
<head>
<title>My Node.js Server</title>
</head>
<body>
<h1>Hello from Node.js!</h1>
<p>This HTML file is served by our custom server.</p>
</body>
</html>
Deploy Your Server
Get a dedicated cloud environment to build and deploy Node.js servers. No local setup needed.
Try It Yourself
Build a simple todo API:
// todo-api.js
const http = require('http');
let todos = [
{ id: 1, text: 'Learn Node.js', done: false },
{ id: 2, text: 'Build an API', done: false }
];
function sendJSON(res, statusCode, data) {
res.writeHead(statusCode, { 'Content-Type': 'application/json' });
res.end(JSON.stringify(data));
}
function parseBody(req) {
return new Promise((resolve, reject) => {
let body = '';
req.on('data', chunk => body += chunk);
req.on('end', () => {
try {
resolve(body ? JSON.parse(body) : {});
} catch (e) {
reject(e);
}
});
});
}
const server = http.createServer(async (req, res) => {
const { method, url } = req;
// GET /todos - List all todos
if (url === '/todos' && method === 'GET') {
sendJSON(res, 200, todos);
}
// POST /todos - Create a todo
else if (url === '/todos' && method === 'POST') {
const body = await parseBody(req);
const todo = {
id: todos.length + 1,
text: body.text,
done: false
};
todos.push(todo);
sendJSON(res, 201, todo);
}
// PATCH /todos/:id - Toggle done status
else if (url.match(/^\/todos\/\d+$/) && method === 'PATCH') {
const id = parseInt(url.split('/')[2]);
const todo = todos.find(t => t.id === id);
if (todo) {
todo.done = !todo.done;
sendJSON(res, 200, todo);
} else {
sendJSON(res, 404, { error: 'Todo not found' });
}
}
// DELETE /todos/:id - Delete a todo
else if (url.match(/^\/todos\/\d+$/) && method === 'DELETE') {
const id = parseInt(url.split('/')[2]);
const index = todos.findIndex(t => t.id === id);
if (index !== -1) {
todos.splice(index, 1);
sendJSON(res, 200, { message: 'Deleted' });
} else {
sendJSON(res, 404, { error: 'Todo not found' });
}
}
// 404 for everything else
else {
sendJSON(res, 404, { error: 'Not found' });
}
});
server.listen(3000, () => {
console.log('Todo API at http://localhost:3000/');
console.log('Try: curl http://localhost:3000/todos');
});
Test it:
# List todos
curl http://localhost:3000/todos
# Create a todo
curl -X POST http://localhost:3000/todos \
-H "Content-Type: application/json" \
-d '{"text":"Read documentation"}'
# Toggle done
curl -X PATCH http://localhost:3000/todos/1
# Delete
curl -X DELETE http://localhost:3000/todos/2
Key Takeaways
- http.createServer creates a server with a request handler
- req contains method, url, headers, and body (as stream)
- res is used to send status, headers, and body back
- JSON.stringify converts objects to JSON strings
- Routing is matching req.url and req.method to handlers
- POST bodies must be collected from the stream
This manual routing works, but gets tedious. Next, let's see how Express makes this much easier!