In this module, you'll build a web server from scratch using Node's built-in http module. This shows you what frameworks like Express abstract away.
Run locally: node basic-server.js then visit http://localhost:3000
Try it live: cse135.site:3001/demo - A raw Node.js server running on this site
In PHP, Apache is the server and PHP runs inside it. In Node.js, your code IS the server:
// basic-server.js
const http = require('http');
const server = http.createServer((req, res) => {
// This function runs for EVERY request
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 basic-server.js Server running at http://localhost:3000/
Open http://localhost:3000 in your browser. You'll see "Hello World!"
The callback function receives two objects:
req)// Basic request info
req.url // '/about' or '/users?id=5'
req.method // 'GET', 'POST', 'PUT', 'DELETE'
req.headers // { 'content-type': '...', 'user-agent': '...' }
Request headers are available as a lowercase key object:
// Access specific headers
const userAgent = req.headers['user-agent'];
const contentType = req.headers['content-type'];
const acceptLang = req.headers['accept-language'];
const host = req.headers['host'];
// Get client IP (may be behind proxy)
const clientIP = req.headers['x-forwarded-for'] ||
req.connection.remoteAddress;
The req.url includes the query string. Use the built-in url module to parse it:
const url = require('url');
const server = http.createServer((req, res) => {
// Parse URL: '/search?q=nodejs&page=2'
const parsedUrl = url.parse(req.url, true);
const pathname = parsedUrl.pathname; // '/search'
const query = parsedUrl.query; // { q: 'nodejs', page: '2' }
console.log(`Searching for: ${query.q}, page ${query.page}`);
});
$_GET['q'] gives you query parameters instantly. In Node.js, you must parse the URL yourself. Frameworks like Express provide req.query for convenience.
res)// Methods to send the response
res.writeHead(200, { 'Content-Type': 'text/html' }); // Set status and headers
res.write('Some content'); // Write to body (can call multiple times)
res.end('Final content'); // End the response
Without a framework, you handle routing manually by checking req.url:
// routing.js
const http = require('http');
const server = http.createServer((req, res) => {
// Check the URL and method
if (req.url === '/' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>Home Page</h1>');
}
else if (req.url === '/about' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end('<h1>About Page</h1>');
}
else if (req.url === '/api/data' && req.method === 'GET') {
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ message: 'Hello from API' }));
}
else {
res.writeHead(404, { 'Content-Type': 'text/html' });
res.end('<h1>404 Not Found</h1>');
}
});
server.listen(3000);
To serve HTML, CSS, and image files, you need to read them from disk:
// static-files.js
const http = require('http');
const fs = require('fs');
const path = require('path');
// Map file extensions to content types
const mimeTypes = {
'.html': 'text/html',
'.css': 'text/css',
'.js': 'application/javascript',
'.json': 'application/json',
'.png': 'image/png',
'.jpg': 'image/jpeg'
};
const server = http.createServer((req, res) => {
// Build file path (default to index.html)
let filePath = '.' + req.url;
if (filePath === './') filePath = './index.html';
// Get the file extension
const ext = path.extname(filePath);
const contentType = mimeTypes[ext] || 'application/octet-stream';
// Read and serve the file
fs.readFile(filePath, (err, content) => {
if (err) {
if (err.code === 'ENOENT') {
res.writeHead(404);
res.end('File not found');
} else {
res.writeHead(500);
res.end('Server error');
}
} else {
res.writeHead(200, { 'Content-Type': contentType });
res.end(content);
}
});
});
server.listen(3000);
This is a lot of code just to serve files. Express does this in one line:
app.use(express.static('public')); // That's it!
mime.types configuration. And this code is still missing many things Apache handles out of the box:
This illustrates Node.js's trade-off: it's less "batteries included" and more DIY. This can be good for performance (you only include what you need) and limiting attack surface (less code = fewer vulnerabilities). But it's a double-edged sword: you can easily forget things that Apache handles automatically.
POST data arrives in chunks. You must collect them:
const server = http.createServer((req, res) => {
if (req.method === 'POST') {
let body = '';
// Collect data chunks
req.on('data', chunk => {
body += chunk.toString();
});
// All data received
req.on('end', () => {
console.log('Received:', body);
res.end('Data received');
});
}
});
$_POST. In raw Node.js, you must handle the stream yourself. Express middleware does this for you.
Unlike PHP running under Apache (where Apache handles process management), if your Node.js server crashes, it's dead. Nobody restarts it automatically.
# Start your server $ node server.js Server running on port 3000... # If an unhandled exception occurs... TypeError: Cannot read property 'x' of undefined # Server exits. No more requests are handled. # Users see "connection refused" until you manually restart.
This is another thing Apache handles for you: if a PHP script crashes, only that request fails. Apache keeps running and handles the next request normally.
In production, you need a process manager to:
PM2 is the most popular Node.js process manager:
# Install PM2 globally $ npm install -g pm2 # Start your app with PM2 $ pm2 start server.js --name my-app # PM2 keeps it running, restarts on crash # View status $ pm2 status ┌─────────┬────────┬──────────┬────────┬─────────┐ │ name │ status │ restarts │ uptime │ memory │ ├─────────┼────────┼──────────┼────────┼─────────┤ │ my-app │ online │ 0 │ 2h │ 45.2mb │ └─────────┴────────┴──────────┴────────┴─────────┘ # Make it start on system boot $ pm2 startup $ pm2 save
You'll probably use Express (next module), not raw http. But understanding this helps you:
We have a raw Node.js HTTP server (no Express!) running on this site. Visit it to see everything in action:
This demo shows you:
?name=YourName&course=CSE135View the source: live-demo.js (~200 lines of raw Node.js)
Note: This demo runs on HTTP (not HTTPS) because raw Node.js doesn't include SSL/TLS handling. In production, you'd either add the https module with certificates or (more commonly) put nginx in front as a reverse proxy to handle SSL termination.
http.createServer()req contains request info: URL, method, headers (accessed by lowercase key)url moduleres is used to send the response with status, headers, and body