Build a Simple Web-Server in 10 MinutesCreating a simple web server is one of the most practical ways to learn how the web works. In this guide you’ll build a minimal, fast, and secure server in under ten minutes using Node.js. You’ll see the core concepts — handling HTTP requests, serving files, setting content types, and basic error handling — without frameworks getting in the way. This article includes step-by-step instructions, code you can copy, explanations of each part, and small improvements to make the server production-friendlier.
What you’ll need
- Node.js installed (v14 or later recommended)
- A terminal/command prompt
- A basic text editor (VS Code, Sublime, etc.)
- A folder for the project
If Node.js isn’t installed, download it from nodejs.org and follow the installer.
Project setup (1 minute)
- Create a folder for the project and open a terminal there:
mkdir simple-web-server cd simple-web-server
- Initialize a minimal Node project (optional but helpful for later):
npm init -y
Step 1 — Minimal HTTP server (2 minutes)
Create a file named server.js and add the following code:
const http = require('http'); const PORT = process.env.PORT || 3000; const server = http.createServer((req, res) => { res.writeHead(200, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('Hello from your simple web server! '); }); server.listen(PORT, () => { console.log(`Server is listening on http://localhost:${PORT}`); });
Run it:
node server.js
Open http://localhost:3000 in your browser — you should see the greeting.
Explanation:
- http.createServer provides the request/response callback.
- writeHead sets status and headers.
- res.end sends the response body.
Step 2 — Serve static files (3 minutes)
Replace server.js with a version that serves files from a public folder:
const http = require('http'); const fs = require('fs'); const path = require('path'); const PORT = process.env.PORT || 3000; const PUBLIC_DIR = path.join(__dirname, 'public'); const mimeTypes = { '.html': 'text/html; charset=utf-8', '.css': 'text/css; charset=utf-8', '.js': 'application/javascript; charset=utf-8', '.json': 'application/json; charset=utf-8', '.png': 'image/png', '.jpg': 'image/jpeg', '.svg': 'image/svg+xml', '.txt': 'text/plain; charset=utf-8', }; const server = http.createServer((req, res) => { let safePath = path.normalize(decodeURI(req.url)).replace(/^(..[/\])+/, ''); if (safePath === '/' || safePath === '') safePath = '/index.html'; const filePath = path.join(PUBLIC_DIR, safePath); fs.stat(filePath, (err, stats) => { if (err || !stats.isFile()) { res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); return res.end('404 Not Found '); } const ext = path.extname(filePath).toLowerCase(); const contentType = mimeTypes[ext] || 'application/octet-stream'; res.writeHead(200, { 'Content-Type': contentType }); const stream = fs.createReadStream(filePath); stream.pipe(res); stream.on('error', () => { res.writeHead(500, { 'Content-Type': 'text/plain; charset=utf-8' }); res.end('500 Internal Server Error '); }); }); }); server.listen(PORT, () => { console.log(`Server running at http://localhost:${PORT}/`); });
Create a public folder and a basic index.html:
mkdir public
public/index.html:
<!doctype html> <html> <head><meta charset="utf-8"><title>Simple Web-Server</title></head> <body> <h1>It works!</h1> <p>This page is served by your simple web server.</p> </body> </html>
Reload http://localhost:3000 — the HTML page should appear.
Step 3 — Add caching headers and logging (1–2 minutes)
Improve performance and debugging by adding basic caching and request logs. Update the file-serving response section:
// inside fs.stat callback, before creating stream: const maxAge = 3600; // seconds res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': `public, max-age=${maxAge}` }); console.log(`${new Date().toISOString()} - ${req.method} ${req.url} 200`);
For errors:
console.log(`${new Date().toISOString()} - ${req.method} ${req.url} 404`);
Small production tips (brief)
- Use a reverse proxy (Nginx) to handle TLS, gzip, and static caching.
- Run behind a process manager (pm2, systemd) to auto-restart.
- Limit request body size and validate URLs to reduce attack surface.
- Use helmet-like headers (Content-Security-Policy, X-Content-Type-Options) if serving dynamic content.
Troubleshooting
- PORT in use: choose another port or kill the process that’s using it.
- Files not found: check PUBLIC_DIR path and filename case-sensitivity on your OS.
- Binary files corrupted: ensure you stream files and don’t modify encoding.
Next steps and learning links
- Add routing to serve dynamic content or APIs.
- Explore Express.js for middleware and routing.
- Learn HTTPS with the built-in tls module or let a reverse proxy handle certificates.
You now have a lightweight, readable web server that serves static files, supports basic MIME types, caching, and logging. It’s a practical base for learning and small projects — extend it as your needs grow.
Leave a Reply