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 http module 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 / ClassPurpose
http.createServer()Creates an HTTP server instance
http.request()Makes outgoing HTTP requests (Node as a client)
http.get()Shorthand for GET requests
http.ServerThe server class (returned by createServer)
http.IncomingMessageThe request object (req)
http.ServerResponseThe 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:

ParameterTypeWhat it represents
reqhttp.IncomingMessageThe incoming request from the client
reshttp.ServerResponseThe 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]);
ParameterRequiredDefaultDescription
portYesPort number to listen on
hostnameNo'0.0.0.0'Which network interface to bind to
backlogNo511Max pending connections queue length
callbackNoFunction 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.

MethodPurposeExample
GETRetrieve dataFetch a webpage, get user list
POSTSend data to create somethingSubmit a form, create a user
PUTUpdate an existing resource completelyUpdate entire user profile
PATCHUpdate part of a resourceChange just the email
DELETERemove a resourceDelete 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()

Featureres.writeHead()res.setHeader()
Sets status codeYesNo
Sets multiple headersYes (object)One at a time
Can be called multiple timesNo (only once)Yes
When to useWhen you know all headers upfrontWhen 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-TypeData FormatUse Case
text/plainPlain textSimple text responses, debugging
text/htmlHTML markupWeb pages
application/jsonJSONAPI responses
text/cssCSSStylesheets
application/javascriptJavaScriptScript files
image/pngPNG imageImages
image/jpegJPEG imagePhotos
application/pdfPDFDocuments

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

  1. Your browser sends a GET / request.
  2. Node.js invokes your callback with req and res.
  3. console.log prints GET / in your terminal.
  4. res.writeHead(200, ...) sets status and headers.
  5. res.end('Hello, World!...') sends the body and closes the response.
  6. 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:

  1. Content-Type is text/html instead of text/plain.
  2. 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

CommandPurpose
curl URLGET request
curl -v URLVerbose output with headers
curl -I URLHEAD request (headers only)
curl -X POST URLPOST request
curl -H "Key: Value" URLSet custom header
curl -d "data" URLSend request body
curl -o file.html URLSave 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

  1. The http module is built into Node.js — no installation needed.
  2. http.createServer(callback) creates a server. The callback receives req and res.
  3. server.listen(port) starts the server on the specified port.
  4. The req object gives you the method, URL, headers, and body of the request.
  5. The res object lets you set headers, write data, and end the response.
  6. Always set Content-Type so the client knows how to interpret the response.
  7. Always call res.end() — without it, the client hangs indefinitely.
  8. 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 curl and 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