Episode 3 — NodeJS MongoDB Backend Architecture / 3.2 — Creating Server
3.2.b — First Node.js Server with HTTP
Node.js ships with a built-in
httpmodule that lets you create a fully functional web server in under 10 lines of code.
Navigation: ← 3.2 Overview | Next → 3.2.c Serving Responses and Understanding HTTP
1. The http Module — Built-in, No Install Needed
Node.js comes with a collection of built-in modules. The http module is one of the most important — it provides everything you need to create an HTTP server.
const http = require('http');
// No "npm install" needed — this module ships with Node.js
What the http module gives you
| Method / Class | Purpose |
|---|---|
http.createServer() | Creates an HTTP server instance |
http.request() | Makes outgoing HTTP requests (Node as a client) |
http.get() | Shorthand for GET requests |
http.Server | The server class (returned by createServer) |
http.IncomingMessage | The request object (req) |
http.ServerResponse | The response object (res) |
2. http.createServer() — Creating a Server
The createServer function accepts a callback that runs every time a request comes in.
const http = require('http');
const server = http.createServer((req, res) => {
// This function runs for EVERY incoming request
// req = information about the request (what the client sent)
// res = tools to build and send a response (what you send back)
});
What is the callback?
The callback is a request handler. Think of it as the instructions your server follows every time someone knocks on its door.
Client sends request → Node.js receives it → Your callback runs → Response sent back
The callback receives exactly two arguments:
| Parameter | Type | What it represents |
|---|---|---|
req | http.IncomingMessage | The incoming request from the client |
res | http.ServerResponse | The response you will send back |
3. server.listen() — Starting the Server
Creating a server does not start it. You must call .listen() to tell it which port to watch.
server.listen(3000, () => {
console.log('Server is running on http://localhost:3000');
});
listen() signature
server.listen(port, [hostname], [backlog], [callback]);
| Parameter | Required | Default | Description |
|---|---|---|---|
port | Yes | — | Port number to listen on |
hostname | No | '0.0.0.0' | Which network interface to bind to |
backlog | No | 511 | Max pending connections queue length |
callback | No | — | Function called when server starts listening |
Hostname explained
// Listen on ALL network interfaces (accessible from other devices)
server.listen(3000, '0.0.0.0');
// Listen ONLY on localhost (only your machine can access)
server.listen(3000, '127.0.0.1');
// Listen on all interfaces (default behavior)
server.listen(3000);
4. The req Object — What the Client Sent
The req (request) object contains all the information about the incoming request.
Most commonly used properties
const server = http.createServer((req, res) => {
console.log(req.method); // "GET", "POST", "PUT", "DELETE", etc.
console.log(req.url); // "/", "/about", "/api/users?page=2"
console.log(req.headers); // { host: 'localhost:3000', 'user-agent': '...', ... }
});
req.method
The HTTP method (verb) tells you what the client wants to do.
| Method | Purpose | Example |
|---|---|---|
GET | Retrieve data | Fetch a webpage, get user list |
POST | Send data to create something | Submit a form, create a user |
PUT | Update an existing resource completely | Update entire user profile |
PATCH | Update part of a resource | Change just the email |
DELETE | Remove a resource | Delete a user account |
req.url
The URL path the client requested. It includes query strings but not the hostname.
// If the client visits: http://localhost:3000/products?category=shoes&page=2
req.url // "/products?category=shoes&page=2"
req.headers
An object containing all the HTTP headers the client sent.
// Typical headers object
{
host: 'localhost:3000',
'user-agent': 'Mozilla/5.0 ...',
accept: 'text/html,application/json',
'accept-language': 'en-US,en;q=0.9',
connection: 'keep-alive'
}
Note: header names are always lowercase in Node.js, regardless of how the client sent them.
req.httpVersion
console.log(req.httpVersion); // "1.1" or "2.0"
Reading the request body
For POST/PUT requests, the body arrives in chunks (streams). You must collect them:
const server = http.createServer((req, res) => {
let body = '';
req.on('data', (chunk) => {
body += chunk.toString();
});
req.on('end', () => {
console.log('Request body:', body);
res.end('Received');
});
});
We will cover this in detail in the routing section.
5. The res Object — What You Send Back
The res (response) object provides methods to construct and send a response.
res.writeHead(statusCode, headers)
Sets the status code and response headers. Must be called before writing the body.
res.writeHead(200, { 'Content-Type': 'text/plain' });
// Multiple headers
res.writeHead(200, {
'Content-Type': 'application/json',
'Cache-Control': 'no-cache',
'X-Custom-Header': 'hello'
});
res.setHeader(name, value)
An alternative way to set headers one at a time:
res.setHeader('Content-Type', 'text/html');
res.setHeader('X-Powered-By', 'Node.js');
res.writeHead() vs res.setHeader()
| Feature | res.writeHead() | res.setHeader() |
|---|---|---|
| Sets status code | Yes | No |
| Sets multiple headers | Yes (object) | One at a time |
| Can be called multiple times | No (only once) | Yes |
| When to use | When you know all headers upfront | When building headers incrementally |
res.write(data)
Writes data to the response body. Can be called multiple times.
res.write('Hello ');
res.write('World');
// Body so far: "Hello World"
res.end([data])
Signals that the response is complete. Optionally sends final data.
// Option 1: end with data
res.end('Hello, World!');
// Option 2: write then end
res.write('Hello, World!');
res.end();
// IMPORTANT: You MUST call res.end() — otherwise the client waits forever
res.statusCode
An alternative way to set the status code without writeHead:
res.statusCode = 404;
res.setHeader('Content-Type', 'text/plain');
res.end('Not Found');
6. Content-Type Headers
The Content-Type header tells the client what kind of data you are sending, so it knows how to process it.
| Content-Type | Data Format | Use Case |
|---|---|---|
text/plain | Plain text | Simple text responses, debugging |
text/html | HTML markup | Web pages |
application/json | JSON | API responses |
text/css | CSS | Stylesheets |
application/javascript | JavaScript | Script files |
image/png | PNG image | Images |
image/jpeg | JPEG image | Photos |
application/pdf | Documents |
What happens if you skip Content-Type?
The browser will try to guess (MIME sniffing), but this is unreliable and can cause security issues. Always set it explicitly.
7. Complete First Server — Every Line Explained
// LINE 1: Import the built-in 'http' module
const http = require('http');
// LINE 2: Define which port our server will listen on
const PORT = 3000;
// LINE 3-10: Create the server with a request handler
const server = http.createServer((req, res) => {
// LINE 4: Log every incoming request to the terminal
console.log(`${req.method} ${req.url}`);
// LINE 5: Set the response status code to 200 (OK)
// and the Content-Type header to plain text
res.writeHead(200, { 'Content-Type': 'text/plain' });
// LINE 6: Send the response body and close the connection
res.end('Hello, World! This is my first Node.js server.');
});
// LINE 11-13: Start listening for requests
server.listen(PORT, () => {
console.log(`Server is running at http://localhost:${PORT}`);
console.log('Press Ctrl+C to stop the server');
});
Running it
# Save the code as server.js, then:
node server.js
# Output:
# Server is running at http://localhost:3000
# Press Ctrl+C to stop the server
What happens when you visit http://localhost:3000
- Your browser sends a
GET /request. - Node.js invokes your callback with
reqandres. console.logprintsGET /in your terminal.res.writeHead(200, ...)sets status and headers.res.end('Hello, World!...')sends the body and closes the response.- Your browser displays the plain text.
You will also see GET /favicon.ico — the browser automatically requests the site icon.
8. Sending HTML Responses
const http = require('http');
const server = http.createServer((req, res) => {
// Set Content-Type to text/html so the browser renders it as HTML
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>My First Server</title>
<style>
body {
font-family: Arial, sans-serif;
display: flex;
justify-content: center;
align-items: center;
height: 100vh;
margin: 0;
background-color: #f0f0f0;
}
.container {
text-align: center;
padding: 2rem;
background: white;
border-radius: 10px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
</style>
</head>
<body>
<div class="container">
<h1>Hello from Node.js!</h1>
<p>This HTML was generated by your server.</p>
<p>Current time: ${new Date().toLocaleString()}</p>
</div>
</body>
</html>
`);
});
server.listen(3000, () => {
console.log('HTML server running at http://localhost:3000');
});
Notice two key differences from the plain text example:
Content-Typeistext/htmlinstead oftext/plain.- The response body is valid HTML.
9. Sending JSON Responses
This is how APIs work — the server sends data, not pages.
const http = require('http');
const server = http.createServer((req, res) => {
// The data we want to send
const data = {
message: 'Hello from the API!',
timestamp: new Date().toISOString(),
status: 'success',
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' }
]
};
// Convert JavaScript object to JSON string
const jsonString = JSON.stringify(data, null, 2);
// Set Content-Type to application/json
res.writeHead(200, {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*' // Allow any frontend to access this API
});
// Send the JSON string
res.end(jsonString);
});
server.listen(3000, () => {
console.log('JSON API running at http://localhost:3000');
});
Important notes for JSON responses
// CORRECT: stringify the object
res.end(JSON.stringify({ name: 'Alice' }));
// WRONG: passing an object directly
res.end({ name: 'Alice' }); // TypeError: first argument must be string or Buffer
10. Testing Your Server
Method 1: Browser
Open http://localhost:3000 in any browser. Best for HTML responses and quick GET request testing.
Method 2: curl (command line)
# Simple GET request
curl http://localhost:3000
# See response headers too
curl -v http://localhost:3000
# Only show response headers
curl -I http://localhost:3000
# Send a POST request with JSON body
curl -X POST http://localhost:3000 \
-H "Content-Type: application/json" \
-d '{"name": "Alice", "email": "alice@example.com"}'
# Pretty-print JSON response (pipe through python)
curl http://localhost:3000 | python3 -m json.tool
Method 3: Postman / Thunder Client / Insomnia
GUI tools for testing APIs. You can set methods, headers, and body visually. These are especially useful for POST, PUT, and DELETE requests.
Curl cheat sheet
| Command | Purpose |
|---|---|
curl URL | GET request |
curl -v URL | Verbose output with headers |
curl -I URL | HEAD request (headers only) |
curl -X POST URL | POST request |
curl -H "Key: Value" URL | Set custom header |
curl -d "data" URL | Send request body |
curl -o file.html URL | Save response to file |
11. Common Beginner Mistakes
Mistake 1: Forgetting res.end()
// BAD — the browser will hang forever
const server = http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/plain' });
res.write('Hello');
// Missing res.end()!
});
Mistake 2: Writing after res.end()
// BAD — causes an error
res.end('Done');
res.write('More data'); // Error: write after end
Mistake 3: Port already in use
Error: listen EADDRINUSE: address already in use :::3000
This means another process is already using port 3000. Solutions:
# Find what is using port 3000
lsof -i :3000
# Kill the process (replace PID with the actual number)
kill -9 <PID>
# Or just use a different port
server.listen(3001);
Mistake 4: Wrong Content-Type
// Sending JSON but claiming it is HTML
res.writeHead(200, { 'Content-Type': 'text/html' });
res.end(JSON.stringify({ error: 'wrong header' }));
// Browser tries to render JSON as HTML — confusing output
12. The Server Event Model
http.createServer() returns a Server object that emits events.
const server = http.createServer();
// Same as passing callback to createServer
server.on('request', (req, res) => {
res.end('Hello');
});
// Fires when the server starts listening
server.on('listening', () => {
console.log('Server is ready');
});
// Fires when a client connection is established
server.on('connection', (socket) => {
console.log('New TCP connection from', socket.remoteAddress);
});
// Fires on server error
server.on('error', (err) => {
if (err.code === 'EADDRINUSE') {
console.error(`Port ${PORT} is already in use!`);
}
});
// Fires when server closes
server.on('close', () => {
console.log('Server has been shut down');
});
server.listen(3000);
Key Takeaways
- The
httpmodule is built into Node.js — no installation needed. http.createServer(callback)creates a server. The callback receivesreqandres.server.listen(port)starts the server on the specified port.- The
reqobject gives you the method, URL, headers, and body of the request. - The
resobject lets you set headers, write data, and end the response. - Always set
Content-Typeso the client knows how to interpret the response. - Always call
res.end()— without it, the client hangs indefinitely. - Use
JSON.stringify()to convert objects before sending JSON responses.
Explain-It Challenge
Task: Create a server that responds differently based on the time of day:
- Morning (6am-12pm): Send
{ greeting: "Good morning!", period: "morning" }as JSON. - Afternoon (12pm-6pm): Send
{ greeting: "Good afternoon!", period: "afternoon" }as JSON. - Evening (6pm-12am): Send
{ greeting: "Good evening!", period: "evening" }as JSON. - Night (12am-6am): Send
{ greeting: "Good night!", period: "night" }as JSON.
Requirements:
- Use
new Date().getHours()to get the current hour. - Set proper Content-Type header.
- Return status 200.
- Test with
curland verify the JSON is valid.
Then explain to a study partner: what is the difference between res.writeHead(), res.setHeader(), and res.statusCode? When would you use each?
Navigation: ← 3.2 Overview | Next → 3.2.c Serving Responses and Understanding HTTP