Débutant20 min de lecture

Your First HTTP Server

Build a basic web server using Node.js's built-in http module — no frameworks needed.

The http Module

Node.js has a built-in module called http that lets you create web servers without installing any external packages. This is the most fundamental building block of backend development in Node.js — every web framework, including Express, is built on top of this module.

The http module provides a method called createServer() that takes a callback function. This callback runs every single time a request arrives at your server. It receives two arguments:

  • req (request) — An object containing information about the incoming request: the URL, the HTTP method, headers, and any data the client sent.
  • res (response) — An object you use to send data back to the client: the status code, headers, and the response body.

Here is the mental model: your server sits and waits. A request comes in (someone visits your URL in a browser, or another program makes an HTTP call). The callback function fires. Inside that function, you examine the request, do whatever work is needed (look up data, do a calculation), and then send a response. The cycle repeats for every request.

The listen() method tells your server to start listening on a specific port. A port is like an apartment number in a building — the IP address gets you to the building (the computer), and the port gets you to the specific application. Port 3000 is a common choice for development. Port 80 is the default for HTTP, and port 443 is the default for HTTPS.

This approach is intentionally low-level. You have to handle everything manually — parsing URLs, checking HTTP methods, setting headers, formatting responses. That is tedious for real applications, which is exactly why frameworks like Express exist. But understanding the http module gives you a solid foundation for understanding what frameworks do under the hood.

The Classic First Server

javascript
// Import the built-in http module
const http = require('http');

// Create a server — the callback runs for EVERY incoming request
const server = http.createServer((req, res) => {
  // Set the HTTP status code (200 = OK) and the Content-Type header
  // This tells the client "the response is plain text"
  res.writeHead(200, { 'Content-Type': 'text/plain' });

  // Send the response body and finish the response
  // end() means "I'm done sending data — send it to the client"
  res.end('Hello World');
});

// Start listening on port 3000
// The callback runs once when the server starts successfully
server.listen(3000, () => {
  console.log('Server is running at http://localhost:3000');
});

// Now visit http://localhost:3000 in your browser
// You'll see "Hello World" displayed on the page

Request and Response

The two objects you work with in every request handler are req (IncomingMessage) and res (ServerResponse). Understanding their properties and methods is essential.

The Request Object (req) contains everything about what the client is asking for:

  • req.url — The URL path the client requested. For example, if someone visits http://localhost:3000/api/users?page=2, req.url is "/api/users?page=2". It includes the path and query string but not the domain.
  • req.method — The HTTP method: "GET", "POST", "PUT", "DELETE", etc. This tells you what the client wants to do. GET means "give me data," POST means "here is new data to save."
  • req.headers — An object containing all HTTP headers sent by the client. Headers carry metadata like content-type (what format the data is in), authorization (credentials), and user-agent (what browser or tool sent the request).

The Response Object (res) is how you send data back to the client:

  • res.writeHead(statusCode, headers) — Sets the HTTP status code and response headers. Call this before writing any body data. The status code tells the client whether the request succeeded (200), created something (201), was not found (404), or caused an error (500).
  • res.write(data) — Sends a chunk of data to the client. You can call this multiple times to send data in pieces (streaming).
  • res.end(data) — Finishes the response. You can optionally pass final data to send. After calling end(), you cannot send any more data. Every response must call end() — if you forget, the client will hang forever waiting for a response that never completes.

The typical flow is: request arrives, callback fires, you examine req.url and req.method to figure out what the client wants, you do your work, then you call res.writeHead() followed by res.end() to send the result back.

One important gotcha: res.writeHead() must be called before any res.write() or res.end() that includes data. If you try to set headers after sending data, Node.js will throw an error because HTTP headers must be sent before the body.

Handling Different Routes Manually

Without a framework, you have to manually check the URL and HTTP method of every incoming request using if/else or switch statements. This is called manual routing, and it gets tedious fast — but understanding it is crucial because it shows you exactly what frameworks like Express automate.

Here is the basic pattern:

javascript
const server = http.createServer((req, res) => {
  if (req.method === 'GET' && req.url === '/') {
    // Handle the home page
  } else if (req.method === 'GET' && req.url === '/api/users') {
    // Return a list of users as JSON
  } else if (req.method === 'POST' && req.url === '/api/users') {
    // Create a new user
  } else {
    // 404 — route not found
  }
});

Notice the problems with this approach:

  1. It does not scale. As your application grows to 20, 50, or 100 routes, this if/else chain becomes unmanageable. Imagine scrolling through hundreds of lines of conditionals to find the right route.

  2. No URL parameter support. What if you want a route like /api/users/123 where 123 is a dynamic user ID? With manual routing, you would have to parse the URL string yourself: req.url.split('/')[3]. Ugly and error-prone.

  3. No built-in body parsing. When a POST request arrives with JSON data in the body, the http module does not automatically parse it. You have to manually collect data chunks from a stream and parse them yourself.

  4. No middleware. Common tasks like logging every request, checking authentication, or parsing cookies have to be manually added to every single route.

This is exactly why Express exists. Express solves every one of these problems with an elegant API. But by understanding how manual routing works, you will appreciate what Express does for you and be able to debug issues at a deeper level. Every Express route handler is still just a function that receives a request and sends a response — the same pattern you see here.

A Server with Multiple Routes

javascript
const http = require('http');

const server = http.createServer((req, res) => {
  // Set JSON content type for API routes
  const jsonHeaders = { 'Content-Type': 'application/json' };
  const htmlHeaders = { 'Content-Type': 'text/html' };

  // Route: GET / — Serve an HTML home page
  if (req.method === 'GET' && req.url === '/') {
    res.writeHead(200, htmlHeaders);
    res.end('<h1>Welcome to My API</h1><p>Visit /api/users or /api/products</p>');
  }

  // Route: GET /api/users — Return a list of users as JSON
  else if (req.method === 'GET' && req.url === '/api/users') {
    const users = [
      { id: 1, name: 'Alice', email: 'alice@example.com' },
      { id: 2, name: 'Bob', email: 'bob@example.com' },
    ];
    res.writeHead(200, jsonHeaders);
    res.end(JSON.stringify(users));
  }

  // Route: GET /api/products — Return a list of products
  else if (req.method === 'GET' && req.url === '/api/products') {
    const products = [
      { id: 1, name: 'Laptop', price: 999 },
      { id: 2, name: 'Phone', price: 699 },
    ];
    res.writeHead(200, jsonHeaders);
    res.end(JSON.stringify(products));
  }

  // Route: 404 — Nothing matched
  else {
    res.writeHead(404, jsonHeaders);
    res.end(JSON.stringify({ error: 'Not Found' }));
  }
});

server.listen(3000, () => {
  console.log('Server running at http://localhost:3000');
});

What method finalizes and sends the HTTP response?

Prêt à pratiquer ?

Crée ton compte gratuit pour accéder à l'éditeur de code interactif, lancer les défis et suivre ta progression.