Join our WhatsApp Channel
Cart / 0.00$

No products in the cart.

Visit Support Portal
No Result
View All Result
Friday, October 30, 2026
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our ServicesBook Now
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
    • Video Editing Services
  • Our Softwares
    • JaggoRe AIFree
    • Email ExtractorPaid
    • TimeWell AppFree
Get a Free Quote
  • Login
  • Register
Neuraldemy
Friday, October 30, 2026
Cart / 0.00$

No products in the cart.

No Result
View All Result
Neuraldemy
Get A Free Quote
Home NodeJS

HTTP Explained: History, Protocol Internals, and Building a Web Server with Node.js

Neuraldemy by Neuraldemy
October 28, 2026
in NodeJS, Javascript, Web Development
Reading Time: 123 mins read
A A

In this tutorial, we will try to understand http and http module in node.js Imagine entering this address into your browser:

http://localhost:3000/hello?name=Riya

A moment later, the browser displays:

Hello, Riya!

This looks like a simple interaction. You enter a URL, and the browser shows a result.

But consider everything that has to happen behind the scenes.

The browser must interpret the URL, determine which computer to contact, establish or reuse a network connection, construct an HTTP request, and send it to a server. The server must receive the request, interpret its method and path, execute application logic, construct a response, and send the response back. Finally, the browser must interpret that response and decide how to display it.

HTTP defines the rules governing the request and response exchange.

In our earlier tutorials, we explored JavaScript execution, Node.js modules, file-system operations, Buffers, and Streams. Those concepts now come together naturally. HTTP messages are transmitted as bytes, Node.js parses them, requests and responses expose stream-based interfaces, and the event loop allows the server to handle many incoming events without requiring a separate JavaScript thread for every request.

To understand HTTP properly, however, we should not begin with createServer().

We should begin with the problem HTTP was invented to solve.

This tutorial follows the technology from its historical origins through the protocol itself, then builds a practical server and client using only Node.js’s built-in node:http module. We will not use Express, and we will not implement HTTPS or TLS here.

Table of Contents

  • Part 1: Why Was HTTP Invented?
    • 1.1 The world before the World Wide Web
    • 1.2 The difference between the Internet and the Web
    • 1.3 The original HTTP was remarkably simple
    • 1.4 How HTTP evolved
  • Part 2: What Exactly Is HTTP?
    • 2.1 HTTP is a protocol, not a programming language
    • 2.2 HTTP is a client-server protocol
    • 2.3 What is a resource?
  • Part 3: HTTP Does Not Work Alone
    • 3.1 The networking layers
    • 3.2 Why does TCP matter to HTTP/1.1?
    • 3.3 What is a port?
  • Part 4: How a Real HTTP Request Travels
    • Step 1: The client interprets the URL
    • Step 2: The client resolves the hostname
    • Step 3: A transport connection is established or reused
    • Step 4: The client sends an HTTP request
    • Step 5: Node.js parses the message
    • Step 6: Application logic executes
    • Step 7: The server constructs and transmits a response
  • Part 5: Understanding the Structure of an HTTP Message
    • 5.1 An HTTP request
    • 5.2 The request line
    • 5.3 HTTP headers
    • 5.4 The blank line
    • 5.5 The request body
  • Part 6: Understanding the HTTP Response
    • The status line
    • The response headers
    • The response body
  • Part 7: How Does the Server Know When a Message Ends?
    • 7.1 Content-Length
    • 7.2 Chunked transfer coding
    • 7.3 Why not just close the connection after every response?
  • Part 8: HTTP Methods and Their Meaning
    • Safe methods
    • Idempotent methods
  • Part 9: HTTP Status Codes
  • Part 10: Now Connect HTTP to Node.js
    • 10.1 Creating the server
    • 10.2 What does createServer() actually do?
    • 10.3 What does req represent?
    • 10.4 What does res represent?
  • Part 11: What Happens Inside Node.js When a Request Arrives?
    • 11.1 How this connects to the event loop
    • 11.2 HTTP request and response bodies are streams
  • Part 12: Building Our Own Routing Logic Without Express
    • 12.1 Why do we use the URL class?
    • 12.2 Why must we check the method as well as the path?
  • Part 13: Reading an HTTP Request Body
    • 13.1 A helper to read a small JSON body
    • 13.2 Why does Content-Type matter?
  • Part 14: Building a Small JSON API with the HTTP Module
    • What happens when we create a note?
    • An important limitation of collecting JSON bodies
  • Part 15: How to Send HTTP Requests from Node.js
    • 15.1 Using http.get()
    • 15.2 Using http.request() to send JSON
  • Part 16: HTTP Is Stateless, but Connections Can Persist
  • Part 17: HTTP/1.1, HTTP/2, and HTTP/3: What Changes?
    • 17.1 HTTP/1.1
    • 17.2 HTTP/2
    • 17.3 HTTP/3
  • Part 18: Common Mistakes
  • Conclusion: What You Should Understand About HTTP Now
  • Official Documentation and Further Reading

Part 1: Why Was HTTP Invented?

1.1 The world before the World Wide Web

In the late 1980s, computers were already connected through networks, and researchers could exchange information using various protocols and specialized systems.

But having a network of computers did not automatically create a universal, convenient way to navigate documents distributed across different machines.

At CERN, researchers worked at universities and laboratories around the world. Sharing technical information across different systems was a practical challenge.

Tim Berners-Lee proposed a system that could connect information using hypertext, allowing people to move from one document to related documents through links.

His first proposal for the World Wide Web was submitted in March 1989. Together with Robert Cailliau, he developed the proposal further in 1990. By the end of 1990, the first browser, web server, and essential web technologies were operating at CERN. The first website was publicly announced in 1991. CERN’s history of the Web provides a detailed historical account.

The important insight is that the World Wide Web was not just one technology. It combined several components that worked together.

HTML provided a format for representing linked documents.

URLs provided a way to identify resources.

HTTP provided a protocol for requesting and transferring those resources.

A browser provided a user-facing way to navigate the information, while a web server made resources available to clients.

These components solved different parts of the same problem.

1.2 The difference between the Internet and the Web

The Internet is the underlying network of interconnected systems. The World Wide Web is an information system that operates over that network.

HTTP is one of the protocols used by the Web.

These are not interchangeable terms.

                    World Wide Web
                          |
                          v
                      HTTP
                          |
                          v
                  Transport Protocol
                          |
                          v
                  Internet Protocol
                          |
                          v
                   Physical Network

This is a simplified layer model. The details change depending on the HTTP version, but it illustrates an important relationship: HTTP does not replace the Internet’s networking infrastructure. It uses lower-level protocols to exchange application messages.

This separation is one reason a browser does not need to understand the electrical or radio signals used by a network. It works with HTTP-level concepts such as methods, URLs, headers, status codes, and message bodies.

1.3 The original HTTP was remarkably simple

The earliest HTTP protocol was so small that it was later called HTTP/0.9, even though the original protocol did not use that version number at the time.

A request could look like this:

GET /index.html

The client asked for a resource, and the server responded with its content.

There were no HTTP headers, no response status line, and no general method selection like we have today. The original protocol supported only GET. Its response was simply the document content. These details are described in MDN’s guide to the evolution of HTTP.

The design was sufficient for an early system where the primary goal was retrieving hypertext documents.

But as the Web grew, the protocol needed to support more than returning an HTML file.

How could a client determine whether a request succeeded? How could it distinguish HTML from an image? How could a client tell a server which formats it accepted? How could it submit information rather than only retrieve it?

These questions motivated the evolution of HTTP.

1.4 How HTTP evolved

HTTP has changed significantly while preserving its basic request-response model.

VersionMajor development
HTTP/0.9A minimal protocol for retrieving documents
HTTP/1.0Introduced a more expressive message format with headers and response status codes
HTTP/1.1Standardized persistent connections, message framing, and other improvements for practical Web communication
HTTP/2Introduced binary framing and multiplexing so multiple exchanges can progress over one connection
HTTP/3Carries HTTP semantics over QUIC, providing stream-based multiplexing on a different transport foundation

HTTP/1.1 is specified in RFC 9112, while the semantics shared across HTTP versions are described in RFC 9110.

HTTP/2 is specified in RFC 9113, and HTTP/3 is specified in RFC 9114.

For our implementation, we will concentrate on HTTP/1.1 and Node.js’s built-in node:http module. Node.js provides a separate node:http2 module for HTTP/2. HTTP/3 uses QUIC and is outside the scope of node:http.

We will not cover HTTPS or TLS here. Keep in mind that plain HTTP does not encrypt the request or response contents, so our local examples are for learning rather than transmitting sensitive data over a public network.

Part 2: What Exactly Is HTTP?

2.1 HTTP is a protocol, not a programming language

HTTP stands for Hypertext Transfer Protocol.

A protocol is a set of agreed rules that communicating systems follow.

Consider two independent programs, one running on your laptop and the other on a remote server. They may be written in different programming languages and run on different operating systems.

How can they understand one another?

They need a shared communication format.

For example, if a client sends:

GET /products/42 HTTP/1.1

both the client and server understand that this is a request using the GET method for the /products/42 request target.

The server does not need to know whether the client is a browser, a mobile application, or another Node.js program. It only needs to implement the HTTP protocol and whatever application behavior the requested resource requires.

HTTP defines the meaning and structure of messages. It does not define every detail of how the server retrieves product 42 from a database or how the browser displays it.

That is the distinction between a protocol and an application implementation.

2.2 HTTP is a client-server protocol

HTTP normally follows a client-server interaction.

The client initiates a request. The server processes it and returns a response.

        CLIENT                         SERVER
          |                               |
          | -------- Request -----------> |
          |                               |
          |       Process request         |
          |                               |
          | <-------- Response ---------- |
          |                               |

The client is often a browser, but it can also be a mobile app, a command-line program such as curl, or a backend service.

The server is the program that receives the request and constructs the response.

Notice that the server is not required to return an HTML page. It could return JSON, an image, a PDF, a video stream, or an error message.

HTTP has evolved from document retrieval into a general-purpose protocol for transferring representations and interacting with resources. MDN’s HTTP overview explains this model in detail.

2.3 What is a resource?

HTTP deals with resources identified through a target, commonly a URL.

A resource could represent a product, a user, a document, a collection of notes, or a result produced dynamically by a server.

For example:

http://localhost:3000/notes/42

This address identifies a resource in our application.

The server might retrieve a note from a database, construct an object in memory, read a file, or compute a result. HTTP does not mandate which internal operation must happen.

That is why two servers can respond to the same HTTP request format while implementing completely different business logic.

The protocol describes how to communicate. The application decides what the requested resource represents and how to handle it.

Part 3: HTTP Does Not Work Alone

Before examining HTTP messages, we need to understand how those messages travel between computers.

3.1 The networking layers

Suppose your browser wants to request a page from example.com.

The browser works with HTTP-level information. It does not need to directly construct Wi-Fi radio signals or Ethernet frames.

A simplified model of the stack for ordinary HTTP/1.1 over TCP is:

+--------------------------------------+
| Application Layer                    |
| HTTP request and response semantics  |
+--------------------------------------+
                    |
                    v
+--------------------------------------+
| Transport Layer                      |
| TCP connection and reliable bytes    |
+--------------------------------------+
                    |
                    v
+--------------------------------------+
| Internet Layer                       |
| IP addressing and packet routing     |
+--------------------------------------+
                    |
                    v
+--------------------------------------+
| Link / Physical Network              |
| Ethernet, Wi-Fi, and transmission    |
+--------------------------------------+

Each layer provides a service to the layer above it.

HTTP defines the application messages. TCP provides a reliable, ordered byte stream. IP helps deliver packets across networks, while the lower networking layers transmit those packets between devices.

This model explains why HTTP and TCP are not the same thing.

TCP does not know that a sequence of bytes represents GET /hello. It transports bytes in order.

HTTP interprets those bytes according to its message syntax and semantics.

3.2 Why does TCP matter to HTTP/1.1?

For an ordinary HTTP/1.1 connection, the client and server communicate over TCP.

TCP provides a reliable, ordered stream of bytes. It does not preserve the boundaries of the application messages sent by the client.

This is an important detail.

Suppose the client sends an HTTP request consisting of 200 bytes. The server is not guaranteed to receive those bytes in one single read operation. The operating system and networking stack may deliver them to Node.js in smaller or differently grouped pieces.

For example:

Client sends:
+----------------------------------+
| 200 bytes of HTTP message        |
+----------------------------------+

Server may receive:
+----------+----------+-------------+
| 70 bytes | 80 bytes | 50 bytes    |
+----------+----------+-------------+

The numbers are illustrative. The actual grouping depends on buffering and network conditions.

Node.js’s HTTP implementation parses the incoming bytes and reconstructs the HTTP-level message. Application code should not assume that a network read corresponds to a complete request, header, or body.

This connects directly to our earlier discussion of Buffers and Streams: incoming data arrives incrementally, and the stream interface exposes it as chunks.

3.3 What is a port?

An IP address identifies a network interface or host destination, while a port identifies a transport-level endpoint on that host.

For example:

127.0.0.1:3000

Here, 127.0.0.1 is the loopback IP address, and 3000 is the port.

When a Node.js server listens on port 3000, it tells the operating system to accept suitable connections directed to that local endpoint.

A port is not an HTTP concept by itself. It belongs to the transport layer. HTTP uses the connection established through that port.

The conventional default port for plain HTTP is 80, but applications can use other ports, as our local server will.

Part 4: How a Real HTTP Request Travels

Let’s follow a request from the browser to a Node.js server.

Suppose the browser requests:

http://example.com/products/42

A simplified sequence is:

Browser
   |
   | 1. Parse URL
   v
Determine host, port, and request target
   |
   | 2. Resolve hostname through DNS as needed
   v
Obtain an IP address
   |
   | 3. Establish or reuse a TCP connection
   v
Remote Server Socket
   |
   | 4. Send HTTP request bytes
   v
Node.js HTTP Parser
   |
   | 5. Build request and response objects
   v
Application Request Handler
   |
   | 6. Construct HTTP response
   v
Write response bytes to the socket
   |
   v
Client receives and interprets the response

Let’s examine these steps more closely.

Step 1: The client interprets the URL

A URL such as:

http://example.com/products/42?currency=INR

contains several pieces of information:

ComponentValue
Schemehttp
Hostnameexample.com
Port80 by default for this scheme
Path/products/42
Query stringcurrency=INR

The hostname identifies the intended host. The path and query help identify the target resource and provide additional request parameters.

Step 2: The client resolves the hostname

The client needs an IP address to establish a network connection. It obtains one through its name-resolution mechanisms, commonly involving DNS.

A hostname can map to one or more IP addresses, and caching or intermediary infrastructure can affect the process.

When we use localhost or 127.0.0.1, we are targeting the local machine rather than a remote website.

Step 3: A transport connection is established or reused

For HTTP/1.1 over TCP, the client establishes a TCP connection if a suitable one does not already exist.

TCP performs its connection-establishment procedure before application data is exchanged on a new connection.

A connection may be reused for subsequent HTTP exchanges, so the client does not necessarily need to establish a new TCP connection for every request.

Step 4: The client sends an HTTP request

The client sends a request message over the connection.

The request identifies the method and request target and may include headers and a body.

Step 5: Node.js parses the message

The Node.js HTTP implementation interprets the incoming bytes and provides application code with a request object and a response object.

Our application does not normally parse the raw request line manually. Node.js provides properties such as req.method, req.url, and req.headers.

Step 6: Application logic executes

The server may inspect the request path, retrieve data, validate input, or perform another operation.

For example, it might retrieve the product with identifier 42 and serialize that product into JSON.

Step 7: The server constructs and transmits a response

The server supplies a status code, headers, and an optional body.

Node.js writes the resulting response to the connection, and the client parses and interprets it.

These steps describe the conceptual path. Real deployments can include reverse proxies, caches, load balancers, and other intermediaries, each with its own role.

Part 5: Understanding the Structure of an HTTP Message

HTTP is easier to understand once you can read its messages directly.

For HTTP/1.1, a request commonly consists of a start line, headers, a blank line, and an optional body. A response has a corresponding status line, headers, a blank line, and an optional body.

5.1 An HTTP request

Consider this example:

GET /products/42 HTTP/1.1
Host: example.com
Accept: application/json
User-Agent: ExampleClient/1.0

The message contains four conceptual parts.

The request line identifies the method, request target, and HTTP version.

The headers provide additional information about the request.

The blank line marks the end of the header section.

The body is optional. This particular GET request does not include one.

The protocol uses specific line-ending and message-framing rules. In a conventional HTTP/1.1 message, lines end with CRLF, represented by \r\n, and the header section is followed by an empty line.

The code examples we use later will rely on Node.js to construct and parse these messages rather than manually generating the line endings.

5.2 The request line

The request line has this general form:

METHOD REQUEST-TARGET HTTP-VERSION

For example:

GET /products/42 HTTP/1.1

GET is the method, /products/42 is the request target in origin-server form, and HTTP/1.1 identifies the HTTP version used in the message.

Each component serves a different purpose.

The method communicates the intended kind of operation. The target identifies what is being requested. The version determines which protocol rules apply.

5.3 HTTP headers

Headers are metadata associated with a message.

For example:

Content-Type: application/json
Content-Length: 27
Accept: application/json

A header consists of a name and a value, separated by a colon.

Common headers include:

HeaderPurpose
HostIdentifies the target host in HTTP/1.1 requests
Content-TypeDescribes the media type of the message content
Content-LengthGives the content length in octets when applicable
AcceptIndicates which response media types the client can accept
AuthorizationCarries authentication credentials or tokens according to the scheme
Cache-ControlCommunicates caching directives
User-AgentIdentifies the client software, when supplied
LocationCommonly identifies a redirect target or the location of a newly created resource

Headers allow HTTP to evolve without requiring every application to invent a completely new message format.

For example, the client and server can negotiate acceptable content types without changing the basic request-response structure.

Header names are case-insensitive under HTTP semantics. Node.js normalizes header names in req.headers to lowercase, making lookups consistent. The raw header representation is available separately when needed.

5.4 The blank line

The empty line separates the headers from the body.

This matters because the server must know where the header section ends and where content begins.

Consider:

POST /notes HTTP/1.1
Host: localhost:3000
Content-Type: application/json
Content-Length: 16

{"text":"hello"}

The JSON object begins after the blank line.

Without a clear boundary between headers and content, the receiver would not know which bytes belong to the message metadata and which belong to the message body.

5.5 The request body

A request body carries content sent by the client.

This is common with methods such as POST, PUT, and PATCH.

For example, an application might send a JSON representation of a new note:

{
  "text": "Learn HTTP"
}

The body is not automatically JSON just because it contains curly braces. Its interpretation depends on the media type, usually indicated by the Content-Type header.

The server must read the incoming bytes and parse them as JSON if that is what the API expects.

In Node.js, request bodies are exposed through the IncomingMessage readable stream. That means data can arrive incrementally in chunks, rather than as one complete object available immediately when the handler starts.

We will implement this shortly.

Part 6: Understanding the HTTP Response

A response communicates the server’s result to the client.

A typical HTTP/1.1 response looks like this:

HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
Content-Length: 16

{"status":"ok"}

It contains a status line, headers, a blank line, and the response body.

The status line

The status line begins with the protocol version and includes a status code and a reason phrase.

In the example:

HTTP/1.1 200 OK

200 is the status code, indicating that the request succeeded according to the relevant HTTP semantics.

The reason phrase is descriptive text. Modern applications should rely on the numeric status code rather than treating the reason phrase as authoritative.

The response headers

The response headers tell the client how to interpret the content and provide other metadata.

In the example:

Content-Type: application/json; charset=utf-8
Content-Length: 16

The Content-Type header identifies the body as JSON text encoded using UTF-8.

Content-Length specifies the number of content bytes. The value 16 corresponds to the UTF-8 byte length of {"status":"ok"}.

That count is important because a JavaScript string’s .length is not always equal to its byte length. When manually setting a content length, use a byte-based calculation such as Buffer.byteLength().

The response body

The body contains the returned representation, if the response permits one.

It could be HTML, JSON, plain text, image bytes, or another type of content.

Some responses do not carry a body. For example, a 204 No Content response indicates success without response content. Responses to HEAD requests also do not include the body that would ordinarily accompany the corresponding GET response.

The HTTP Semantics specification, RFC 9110, defines the meaning of status codes, methods, headers, and content across HTTP versions.

Part 7: How Does the Server Know When a Message Ends?

This question is more important than it first appears.

TCP provides an ordered stream of bytes. It does not automatically tell the application where one HTTP request ends and the next begins.

HTTP/1.1 therefore has message-framing rules.

Several mechanisms help determine message boundaries.

7.1 Content-Length

A message can specify the length of its body:

Content-Length: 16

Once the receiver has parsed the headers, it knows how many content bytes belong to the body.

That makes it possible to determine the end of the message without closing the entire connection.

7.2 Chunked transfer coding

Sometimes a sender cannot know the complete response size in advance. HTTP/1.1 provides chunked transfer coding, which frames the content as a sequence of length-delimited chunks.

Conceptually, the wire representation looks like this:

3
ABC
4
DEFG
0

The hexadecimal length preceding each chunk describes how many content bytes follow it. A zero-sized final chunk marks the end of the chunked message body, subject to the complete framing rules.

The example is schematic. Real HTTP framing uses the appropriate CRLF delimiters.

This mechanism is not the same as the chunks delivered by a Node.js readable stream. HTTP chunked transfer coding is a protocol-level framing mechanism. Node.js stream chunks are pieces of data delivered by the stream API.

Node.js normally parses HTTP/1.1 transfer framing for you. Application code receives the body through req or res without manually decoding the HTTP chunk-size markers.

The details are specified in RFC 9112, HTTP/1.1.

7.3 Why not just close the connection after every response?

Closing the connection is one way some HTTP message exchanges have indicated completion, particularly in earlier deployments and certain response cases.

But if every request required a new TCP connection, applications would repeatedly pay the overhead of establishing and closing connections.

HTTP/1.1 supports persistent connections, allowing multiple exchanges to use the same connection when the protocol and participants permit it.

This improves efficiency and shows why message framing and connection lifecycle must be understood separately.

Part 8: HTTP Methods and Their Meaning

An HTTP method describes the intended operation.

Different methods have different semantics. They are not merely labels chosen by the client.

Here are the methods you should recognize first.

MethodGeneral purpose
GETRetrieve a representation of a resource
HEADRetrieve the metadata that would accompany a GET response without the response body
POSTSubmit content for processing or resource-specific behavior
PUTCreate or replace the state of a target resource according to the API’s contract
PATCHApply a partial modification to a resource
DELETERemove the relationship or resource identified by the target, according to the application’s semantics
OPTIONSAsk about communication options supported by the server or target

Let’s explore two concepts that are particularly important for understanding these methods.

Safe methods

A safe method is one whose intended semantics do not request a change to the server’s state.

GET and HEAD are examples.

This does not mean the server performs absolutely no internal actions. It may log requests or update operational statistics. The requirement concerns the intended operation requested by the client.

For instance, an application should not expose an endpoint that places an order merely because the client sends a GET request.

See MDN’s explanation of safe HTTP methods.

Idempotent methods

A method is idempotent when multiple identical requests have the same intended effect on server state as one request.

GET, HEAD, PUT, and DELETE are defined as idempotent. POST is not guaranteed to be idempotent, and PATCH is not guaranteed to be idempotent either.

Consider a client deleting a resource:

DELETE /notes/42 HTTP/1.1
Host: example.com

The first request may delete the note and return 204. A repeated request might return 404, because the note no longer exists.

The responses differ, but the intended final state is still that the resource is absent. This is why idempotency does not require identical responses.

Idempotency matters when designing retries. If a client does not receive a response, it may not know whether the server completed the operation. The method’s semantics help determine whether repeating the request is safe. For operations such as payment creation, an application may also need an explicit idempotency-key mechanism.

More details are available in MDN’s guide to idempotent methods.

Part 9: HTTP Status Codes

The method describes the requested operation, while the status code communicates the result.

HTTP status codes are grouped into five broad classes.

RangeClassGeneral meaning
100–199InformationalThe request process is continuing
200–299SuccessfulThe request was successfully handled
300–399RedirectionFurther action or a different location may be involved
400–499Client errorThe request cannot or will not be fulfilled as sent
500–599Server errorThe server encountered a problem handling an apparently valid request

These ranges are defined in the HTTP specifications. See MDN’s status-code reference.

The most useful codes to recognize at the start include:

StatusMeaning
200 OKThe request succeeded
201 CreatedA new resource was created
204 No ContentThe operation succeeded and no response body is returned
301 Moved PermanentlyA resource has a permanent redirect location
302 FoundA temporary redirection response
304 Not ModifiedThe client can use a cached representation under the relevant conditions
400 Bad RequestThe server cannot process the request as received
401 UnauthorizedAuthentication is required or has failed; despite its name, this code concerns authentication
403 ForbiddenThe server refuses to fulfil the request
404 Not FoundThe requested resource was not found
405 Method Not AllowedThe target does not support the requested method
409 ConflictThe request conflicts with the current state of the resource
413 Content Too LargeThe request content exceeds a limit the server is willing or able to process
415 Unsupported Media TypeThe supplied content format is not supported for the operation
429 Too Many RequestsThe client has sent too many requests in a given period or under the applicable rate limit
500 Internal Server ErrorAn unexpected server-side failure occurred
502 Bad GatewayA gateway or proxy received an invalid response from an upstream server
503 Service UnavailableThe server cannot currently handle the request
504 Gateway TimeoutA gateway or proxy did not receive a timely upstream response

The status is part of the protocol contract. A server should not return 200 OK simply because it successfully executed some JavaScript. It should choose a code that communicates the actual outcome of the HTTP request.

For example, a missing note should generally result in 404, not a successful response that contains a misleading message.

Part 10: Now Connect HTTP to Node.js

We now know what an HTTP message looks like and how a client-server exchange works.

Let’s move into Node.js.

Node.js provides the built-in node:http module, which allows us to create HTTP servers and make HTTP client requests without using Express or another framework.

The official API is documented at nodejs.org/api/http.html.

We will use ES Modules for all examples.

Create a file called server.mjs.

10.1 Creating the server

import { createServer } from "node:http";

// Node.js calls this handler when an HTTP request is received.
const server = createServer((req, res) => {
  // req represents the incoming HTTP request.
  // res represents the response we will send.
  res.statusCode = 200;
  res.setHeader("Content-Type", "text/plain; charset=utf-8");

  // End the response and send its body.
  res.end("Hello from Node.js HTTP!");
});

// Listen for incoming connections on localhost port 3000.
server.listen(3000, "127.0.0.1", () => {
  console.log("HTTP server listening at http://127.0.0.1:3000");
});

server.on("error", (error) => {
  console.error("Server error:", error.message);
});

Run it:

node server.mjs

Expected server output:

HTTP server listening at http://127.0.0.1:3000

Open a second terminal and execute:

curl -i http://127.0.0.1:3000

The response will contain the body:

Hello from Node.js HTTP!

The -i option asks curl to show the response headers as well as the body. The exact headers can vary by Node.js version and connection behavior.

This simple example introduces the most important pieces of the Node.js HTTP API.

10.2 What does createServer() actually do?

The call:

const server = createServer((req, res) => {
  // Handle an HTTP request.
});

creates an HTTP server object.

The callback is the request listener. Node.js invokes it when an HTTP request has been parsed and is ready for application handling.

The two parameters are:

req, an IncomingMessage object representing the incoming request.

res, a ServerResponse object used to construct and send the response.

The server does not begin accepting network connections merely because createServer() has been called. We explicitly call:

server.listen(3000, "127.0.0.1");

This asks the operating system to bind the server to the specified address and port and begin listening.

10.3 What does req represent?

The request object gives us access to the parsed HTTP request.

For example:

const server = createServer((req, res) => {
  console.log("Method:", req.method);
  console.log("URL:", req.url);
  console.log("Version:", req.httpVersion);
  console.log("Headers:", req.headers);

  res.end("Request received");
});

If the client requests:

http://127.0.0.1:3000/products?category=books

the important properties include:

Method: GET
URL: /products?category=books
Version: 1.1

The exact header object depends on what the client sends.

One detail worth noticing is that req.url is the request target as received by the server, usually a path plus query string for an ordinary origin-server request. It is not the complete URL containing the scheme and host.

The host information is normally available through the Host header for HTTP/1.1 requests.

Also, req.headers is a normalized object, with lowercase header names. req.rawHeaders exposes the received header names and values in their original sequence when lower-level inspection is needed.

10.4 What does res represent?

The response object lets us construct the server’s answer.

The main operations we will use are:

res.statusCode selects the response status.

res.setHeader() sets a response header.

res.writeHead() sets the status and can set headers.

res.write() submits a chunk of response body data.

res.end() signals completion of the response and can submit a final body chunk.

Consider this example:

res.statusCode = 200;
res.setHeader("Content-Type", "text/plain; charset=utf-8");
res.end("Request processed");

The server prepares a successful response, describes the content as plain text, and sends the response body.

Once response headers have been sent, the server cannot simply change the already transmitted status code or headers. That is why status and header decisions should be made before sending the first response body data.

For more details, see the official Node.js ServerResponse documentation.

Part 11: What Happens Inside Node.js When a Request Arrives?

When you run:

server.listen(3000);

Node.js sets up a listening server socket through the operating system.

When a client connects and sends HTTP bytes, the HTTP implementation parses the incoming data and emits the appropriate events.

The process can be simplified like this:

          Client Sends Bytes
                  |
                  v
           OS Network Stack
                  |
                  v
          Node.js Socket Layer
                  |
                  v
          HTTP Message Parser
                  |
                  v
        IncomingMessage (req)
                  |
                  v
          Request Listener
                  |
                  v
       Application JavaScript
                  |
                  v
        ServerResponse (res)
                  |
                  v
           Socket Writes Bytes
                  |
                  v
             Client Receives

Node.js’s HTTP implementation is built on top of lower-level networking and stream facilities. It is responsible for parsing HTTP messages and exposing request and response objects to application code.

We should distinguish the HTTP parser from the application handler. The parser interprets the HTTP message structure; the handler decides what the server should do in response to that message.

11.1 How this connects to the event loop

We learned earlier that Node.js uses an event-driven model.

The HTTP server participates in that model. Incoming connections and data produce events that Node.js can process through its runtime and event loop.

The request listener is not normally invoked because the program calls it directly. Node.js invokes it as part of handling an incoming HTTP request.

While one request handler is awaiting asynchronous I/O, Node.js can continue processing other events. However, a long-running synchronous computation inside the handler can still block JavaScript execution on that thread.

This is one reason it is important to avoid expensive synchronous work inside a request handler.

A server does not become scalable merely because it uses HTTP. It must still manage CPU work, memory, I/O, and concurrency appropriately.

11.2 HTTP request and response bodies are streams

This is the connection to our Streams tutorial.

IncomingMessage, used for req, is a readable stream. The body of a request can arrive in multiple chunks.

ServerResponse, used for res, is a writable stream. The response body can be written as one string or submitted in multiple chunks.

The data flow is therefore:

HTTP Request Bytes
        |
        v
HTTP Parser
        |
        v
Readable Request Stream
        |
        v
Application Logic
        |
        v
Writable Response Stream
        |
        v
HTTP Response Bytes

This means that the request handler may run before the entire request body has been consumed. If the operation needs to parse a complete JSON document, the application must read the request stream until the body has ended before parsing it.

For large file uploads, the application can instead process the incoming chunks as they arrive and pipe them to an appropriate destination.

This is how HTTP, Streams, and Buffers fit together.

Part 12: Building Our Own Routing Logic Without Express

We now understand the basic request and response flow.

Let’s improve the example by returning different responses depending on the path.

A route is an application-level rule that maps a request’s method and target to a particular action.

HTTP does not require us to use the word “route”. It is a programming concept implemented by the server.

Create server.mjs:

import { createServer } from "node:http";

const server = createServer((req, res) => {
  // Parse the request target so we can inspect its path and query.
  const url = new URL(req.url ?? "/", "http://localhost");

  // Match both the HTTP method and the path.
  if (req.method === "GET" && url.pathname === "/") {
    res.statusCode = 200;
    res.setHeader("Content-Type", "text/plain; charset=utf-8");
    res.end("Welcome to our HTTP server!");
    return;
  }

  if (req.method === "GET" && url.pathname === "/health") {
    res.writeHead(200, {
      "Content-Type": "application/json; charset=utf-8",
    });

    res.end(JSON.stringify({ status: "ok" }));
    return;
  }

  if (req.method === "GET" && url.pathname === "/hello") {
    // URLSearchParams handles query-string decoding.
    const name = url.searchParams.get("name") ?? "World";

    res.statusCode = 200;
    res.setHeader("Content-Type", "text/plain; charset=utf-8");
    res.end(`Hello, ${name}!`);
    return;
  }

  // The request did not match an implemented route.
  res.statusCode = 404;
  res.setHeader("Content-Type", "text/plain; charset=utf-8");
  res.end("Not Found");
});

server.listen(3000, "127.0.0.1", () => {
  console.log("Listening on http://127.0.0.1:3000");
});

Let’s try the routes.

curl http://127.0.0.1:3000/

Response:

Welcome to our HTTP server!

Now test the health endpoint:

curl -i http://127.0.0.1:3000/health

The response body is:

{"status":"ok"}

Finally:

curl "http://127.0.0.1:3000/hello?name=Riya"

Response:

Hello, Riya!

12.1 Why do we use the URL class?

The request target may contain both a path and a query string.

For example:

/hello?name=Riya&language=JavaScript

We need to separate the path from the parameters.

The URL API helps us do this:

const url = new URL(req.url ?? "/", "http://localhost");

console.log(url.pathname);
console.log(url.searchParams.get("name"));
console.log(url.searchParams.get("language"));

Output:

/hello
Riya
JavaScript

The base URL is necessary because req.url commonly contains a relative request target rather than a full absolute URL.

In our simple origin-server example, http://localhost is only the parsing base. It does not cause the server to connect to another host.

Use the Node.js URL documentation for more details about parsing and working with URLs.

12.2 Why must we check the method as well as the path?

Consider these requests:

GET /notes HTTP/1.1

and:

POST /notes HTTP/1.1

They target the same path, but they communicate different intentions.

A GET might retrieve notes, while a POST might create a new note.

If the server checks only the path and ignores the method, it can accidentally run the wrong operation for a request.

A route should generally consider both the method and the target, alongside any application-specific requirements such as authorization and request validation.

Part 13: Reading an HTTP Request Body

So far, our routes have mainly used the method, path, and query parameters.

But many requests also include a body.

Imagine a client wants to create a note by submitting this JSON:

{
  "text": "Understand HTTP"
}

The server must receive the content, interpret it as JSON, validate it, and then decide whether to create the resource.

Because IncomingMessage is a readable stream, the body may arrive in multiple chunks.

We cannot assume that the complete JSON document will be available immediately when the handler begins.

13.1 A helper to read a small JSON body

For small JSON requests, we can collect the chunks, combine them into a Buffer, decode the bytes as UTF-8, and parse the result.

async function readJsonBody(req) {
  const chunks = [];

  // Read each incoming body chunk.
  for await (const chunk of req) {
    chunks.push(chunk);
  }

  // Combine the chunks into one Buffer, then decode as UTF-8.
  const text = Buffer.concat(chunks).toString("utf8");

  // Convert the JSON text into a JavaScript value.
  return JSON.parse(text);
}

This illustrates the flow, but it is not yet a production-ready body parser.

It collects the entire body in memory, and JSON.parse() throws if the input is malformed. A real server should impose body-size limits, handle parsing errors deliberately, and validate the resulting object.

That is especially important when accepting requests from untrusted clients.

13.2 Why does Content-Type matter?

If a client sends:

Content-Type: application/json

it indicates that the request body is JSON content.

The server should not automatically assume that every body is JSON. A different client might submit plain text, form data, or binary file content.

Content-Type describes the representation being sent. A server can inspect it and decide whether that representation is supported for the requested operation.

The header is not a guarantee that the body is valid. The server still needs to parse and validate the content.

Part 14: Building a Small JSON API with the HTTP Module

Let’s bring the pieces together into a slightly more realistic application.

We will build a small notes API with these endpoints:

MethodPathBehavior
GET/healthCheck whether the server is running
GET/notesRetrieve all notes
POST/notesCreate a note
GET/notes/:idRetrieve one note
DELETE/notes/:idDelete a note

The data will be stored in a Map so that we can focus on HTTP rather than database design. The notes will disappear when the process stops.

Create notes-server.mjs:

import { createServer } from "node:http";

const notes = new Map();
let nextId = 1;

const MAX_BODY_BYTES = 1_000_000;

function sendJson(res, statusCode, data, extraHeaders = {}) {
  const body = JSON.stringify(data);

  // Set the status and metadata before sending the body.
  res.writeHead(statusCode, {
    "Content-Type": "application/json; charset=utf-8",
    "Content-Length": Buffer.byteLength(body),
    "Cache-Control": "no-store",
    ...extraHeaders,
  });

  // End the response with the serialized JSON.
  res.end(body);
}

function createHttpError(statusCode, message) {
  const error = new Error(message);
  error.statusCode = statusCode;
  return error;
}

async function readJsonBody(req) {
  const chunks = [];
  let totalBytes = 0;
  let tooLarge = false;

  // The body arrives through the request's readable stream.
  for await (const chunk of req) {
    if (tooLarge) {
      // Continue consuming without retaining any more data.
      continue;
    }

    totalBytes += chunk.length;

    if (totalBytes > MAX_BODY_BYTES) {
      tooLarge = true;
      chunks.length = 0;
      continue;
    }

    chunks.push(chunk);
  }

  if (tooLarge) {
    throw createHttpError(413, "Request body is too large");
  }

  const text = Buffer.concat(chunks).toString("utf8");

  try {
    return JSON.parse(text);
  } catch {
    throw createHttpError(400, "Invalid JSON body");
  }
}

async function handleRequest(req, res) {
  // Parse the request target into its pathname and query parameters.
  const url = new URL(req.url ?? "/", "http://localhost");
  const { pathname } = url;
  const method = req.method ?? "GET";

  if (method === "GET" && pathname === "/health") {
    sendJson(res, 200, { status: "ok" });
    return;
  }

  if (method === "GET" && pathname === "/notes") {
    // Map values are converted into an ordinary array for JSON.
    sendJson(res, 200, Array.from(notes.values()));
    return;
  }

  if (method === "POST" && pathname === "/notes") {
    const contentType = req.headers["content-type"];

    if (
      typeof contentType !== "string" ||
      contentType.split(";")[0].trim().toLowerCase() !== "application/json"
    ) {
      // The endpoint accepts JSON, not arbitrary body formats.
      sendJson(res, 415, {
        error: "Content-Type must be application/json",
      });
      return;
    }

    const body = await readJsonBody(req);

    if (typeof body.text !== "string" || body.text.trim() === "") {
      sendJson(res, 400, {
        error: "The text field must be a non-empty string",
      });
      return;
    }

    const id = String(nextId++);

    const note = {
      id,
      text: body.text.trim(),
      createdAt: new Date().toISOString(),
    };

    notes.set(id, note);

    // A 201 response indicates creation of the resource.
    sendJson(res, 201, note, {
      Location: `/notes/${id}`,
    });
    return;
  }

  const match = pathname.match(/^\/notes\/(\d+)$/);

  if (match) {
    const id = match[1];

    if (method === "GET") {
      const note = notes.get(id);

      if (!note) {
        sendJson(res, 404, { error: "Note not found" });
        return;
      }

      sendJson(res, 200, note);
      return;
    }

    if (method === "DELETE") {
      const existed = notes.delete(id);

      if (!existed) {
        sendJson(res, 404, { error: "Note not found" });
        return;
      }

      // 204 means the operation succeeded with no response body.
      res.writeHead(204);
      res.end();
      return;
    }

    // The resource exists as a route, but this method is unsupported.
    sendJson(
      res,
      405,
      { error: "Method not allowed" },
      { Allow: "GET, DELETE" },
    );
    return;
  }

  if (pathname === "/notes") {
    sendJson(
      res,
      405,
      { error: "Method not allowed" },
      { Allow: "GET, POST" },
    );
    return;
  }

  sendJson(res, 404, { error: "Route not found" });
}

const server = createServer((req, res) => {
  // The HTTP request listener does not automatically await async handlers.
  // Attach a rejection handler so failures become HTTP errors.
  handleRequest(req, res).catch((error) => {
    if (res.headersSent) {
      // The response has already started, so a clean error response
      // may no longer be possible.
      res.destroy(error);
      return;
    }

    const statusCode = error.statusCode ?? 500;

    sendJson(res, statusCode, {
      error:
        statusCode >= 500
          ? "Internal server error"
          : error.message,
    });
  });
});

server.listen(3000, "127.0.0.1", () => {
  console.log("Notes API listening at http://127.0.0.1:3000");
});

server.on("error", (error) => {
  console.error("Server startup or runtime error:", error.message);
});

This is still an educational application rather than a complete production API. It provides a useful foundation for understanding routing, status codes, headers, request streaming, response serialization, error handling, and resource creation without hiding those mechanisms behind a framework.

What happens when we create a note?

Start the server:

node notes-server.mjs

Then, in another terminal, submit a request:

curl -i \
  -X POST \
  http://127.0.0.1:3000/notes \
  -H "Content-Type: application/json" \
  -d '{"text":"Understand HTTP"}'

The response uses status 201 Created and contains the new note. Its Location header identifies the new resource.

Now retrieve all notes:

curl http://127.0.0.1:3000/notes

Retrieve a particular note:

curl http://127.0.0.1:3000/notes/1

Delete it:

curl -i -X DELETE http://127.0.0.1:3000/notes/1

The delete operation returns 204 No Content and no response body.

These commands show how the HTTP contract maps to application behavior: the method expresses the operation, the path identifies the target, the body supplies data when required, and the response communicates the outcome.

An important limitation of collecting JSON bodies

This API bounds the amount of body content it retains, but it continues consuming an oversized request until the request body ends before returning 413. That is a deliberate simplification for this tutorial.

In a production server, you would also consider rejecting oversized requests as early as practical, enforcing suitable timeouts and connection policies, and avoiding long periods spent draining an untrusted upload.

If you are accepting large files, the better design is often to stream the request body directly to a controlled destination rather than collecting everything into an array and constructing one large Buffer.

This is where the stream concepts become particularly valuable.

Part 15: How to Send HTTP Requests from Node.js

So far, we have focused on the server side.

But Node.js also provides an HTTP client through the same node:http module.

This lets a Node.js program request data from another HTTP server without needing browser APIs or an external library.

There are two client methods worth learning first: get() and request().

15.1 Using http.get()

For a straightforward GET request, use the get() method.

Create client.mjs:

import { get } from "node:http";

get("http://127.0.0.1:3000/health", (res) => {
  console.log("Status:", res.statusCode);

  // The response body is a readable stream.
  res.setEncoding("utf8");

  let body = "";

  res.on("data", (chunk) => {
    // Collect each text chunk for this small JSON response.
    body += chunk;
  });

  res.on("end", () => {
    console.log("Response:", body);
  });
}).on("error", (error) => {
  console.error("Request failed:", error.message);
});

When the request succeeds, the output includes a successful status and the JSON body:

Status: 200
Response: {"status":"ok"}

The response object is itself an IncomingMessage, so the response body is consumed as a stream.

We collect its chunks only because this example needs the complete small JSON response before printing it. For larger responses, you could process the chunks incrementally or pipe the response into a destination.

The get() method automatically ends the outgoing request. You do not need to call end() manually for this basic GET operation.

15.2 Using http.request() to send JSON

For requests with a custom method or body, use request().

import { request } from "node:http";
import { Buffer } from "node:buffer";

const payload = JSON.stringify({
  text: "Created from a Node.js HTTP client",
});

const req = request(
  {
    hostname: "127.0.0.1",
    port: 3000,
    path: "/notes",
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "Content-Length": Buffer.byteLength(payload),
    },
  },
  (res) => {
    console.log("Status:", res.statusCode);

    res.setEncoding("utf8");

    let responseBody = "";

    res.on("data", (chunk) => {
      responseBody += chunk;
    });

    res.on("end", () => {
      console.log("Response:", responseBody);
    });
  },
);

req.on("error", (error) => {
  console.error("Request failed:", error.message);
});

// Send the body and indicate that the request is complete.
req.end(payload);

This request sends a JSON payload to the notes endpoint.

Notice the use of Buffer.byteLength(payload). The content length represents bytes, not simply the number of JavaScript string code units.

Also notice req.end(payload). Unlike the get() convenience method, request() lets you construct the outgoing request and submit its body explicitly. Calling end() signals that no more request body data will be provided.

For a more detailed overview, see the official Node.js http.request() documentation.

Part 16: HTTP Is Stateless, but Connections Can Persist

You will often hear that HTTP is a stateless protocol.

What does that actually mean?

The core HTTP semantics do not automatically preserve application state between requests. A server does not inherently know that two unrelated requests were made by the same person or are part of the same shopping session.

For example:

Request 1: GET /products
Request 2: GET /cart

Without additional application mechanisms, the protocol itself does not establish that these requests belong to one user’s continuing session.

Applications can maintain that relationship using cookies, session identifiers, authorization tokens, or other application-level state.

But statelessness does not mean that every request must use a new connection.

HTTP/1.1 supports persistent connections. The same TCP connection can carry multiple request-response exchanges when the connection remains usable and both sides follow the protocol’s rules.

One TCP Connection
       |
       +---- Request 1 ---- Response 1
       |
       +---- Request 2 ---- Response 2
       |
       +---- Request 3 ---- Response 3

The application requests are separate protocol messages, even when the transport connection is reused.

This distinction is important when studying server performance. Creating a new connection for every request can incur additional network overhead, while persistent connections can reduce that cost.

HTTP/1.1 connection reuse and message framing are discussed in RFC 9112.

Part 17: HTTP/1.1, HTTP/2, and HTTP/3: What Changes?

Although our implementation uses Node.js’s node:http module, it is useful to understand why the protocol has evolved.

The core concepts of requests, responses, methods, status codes, and headers remain central across HTTP versions, but the wire representations and transport mechanisms differ.

17.1 HTTP/1.1

HTTP/1.1 uses a textual message format, including start lines, headers, and body framing. It usually operates over TCP.

Persistent connections allow multiple exchanges to reuse one connection. HTTP/1.1 also defines mechanisms such as chunked transfer coding for message-body framing when the complete length is not known in advance.

This is the version we have been examining through the raw request and response examples.

17.2 HTTP/2

HTTP/2 preserves the core HTTP semantics but uses a binary framing layer. It supports multiplexing, allowing several request-response exchanges to progress concurrently on a single connection.

That reduces some limitations associated with HTTP/1.1 connection management. It also introduces mechanisms such as header compression and stream-level flow control.

HTTP/2 is not simply HTTP/1.1 with different text formatting. It is a different wire protocol with a different framing layer.

The HTTP/2 specification, RFC 9113, describes how its streams and frames work.

Node.js provides a separate node:http2 module for HTTP/2 functionality. The node:http module used in our examples is not the API for implementing HTTP/2.

17.3 HTTP/3

HTTP/3 carries HTTP semantics over QUIC, which uses UDP as its underlying transport. QUIC provides connection establishment, stream multiplexing, and transport-level mechanisms needed for reliable data delivery.

HTTP/3 therefore changes the transport foundation while preserving the general HTTP request-response model.

You can read more in the HTTP/3 specification, RFC 9114.

We are not implementing HTTP/2 or HTTP/3 here, but understanding their existence prevents one major misconception: HTTP is not permanently tied to a single wire format or transport protocol.

Part 18: Common Mistakes

Before concluding, let’s bring together several details that matter when building HTTP applications.

Do not confuse a successful handler with a successful response. A server should select a meaningful HTTP status based on the outcome of the request. Returning 200 OK for a missing resource is misleading.

Do not assume that a request body arrives all at once. HTTP request bodies are exposed as streams. Read the body to completion when the operation requires it, validate the input, and set reasonable body-size limits.

Do not assume that req.url is a complete URL. For ordinary incoming HTTP/1.1 server requests, it generally contains the request target, which can include a path and query string. Use the URL API to parse it.

Do not forget to end the response. Calling res.write() submits body data but does not indicate that the response is complete. Call res.end() when the response is finished.

Do not confuse headers with the body. Headers describe the message and its content. The body carries the content itself. Their boundary is essential to HTTP message parsing.

Do not manually guess a content length from string character counts. If you need to set Content-Length, calculate the byte length using Buffer.byteLength().

Do not treat GET and POST as interchangeable. They communicate different intended operations, and the server should implement their semantics consistently.

Do not assume a persistent connection represents a persistent user session. Connection reuse belongs to transport and connection management, while application session state needs an explicit design.

Do not expose a plain HTTP server publicly when the application needs confidential communication. Plain HTTP does not encrypt headers or bodies. HTTPS and TLS are important for protecting public network communication, and we will study them separately.

Conclusion: What You Should Understand About HTTP Now

HTTP began as a small protocol for exchanging hypertext documents. Over time, it evolved into a general-purpose protocol for requesting resources, submitting content, and exchanging data between clients and servers.

We started with the historical problem the Web set out to solve, then examined the relationship between HTTP and the Internet’s lower networking layers. We explored HTTP request and response messages, message framing, methods, headers, status codes, and the distinction between stateless semantics and persistent connections.

We then connected those concepts to Node.js and built a basic HTTP server using node:http, without Express. We examined the request listener, IncomingMessage, ServerResponse, path-based routing, query parameters, JSON bodies, and response handling. Finally, we built a small notes API and used Node.js’s HTTP client methods to make requests to it.

The central insight is that HTTP is a protocol, not a particular server framework. A framework can simplify routing and request handling, but the underlying concepts remain the same: clients send structured requests, servers interpret those requests, application logic determines the result, and servers return structured responses.

Once you understand the protocol and its relationship to Node.js streams, Buffers, the event loop, and sockets, you can reason about HTTP behavior rather than relying on framework conventions.

Official Documentation and Further Reading

The following resources are useful for continuing your study:

  • CERN: A Short History of the Web
  • MDN: Overview of HTTP
  • MDN: Evolution of HTTP
  • RFC 9110: HTTP Semantics
  • RFC 9112: HTTP/1.1
  • Node.js: HTTP Module
  • Node.js: URL Module
  • Node.js: Streams

Support

Buy author a coffee

Support
SummarizeSendShareTweetScan
Previous Post

Node.js Streams Explained: Readable, Writable, Transform, and Backpressure

Next Post

HTTPS and TLS in Node.js Explained : Encryption, Certificates, Handshakes, and Secure Servers

Neuraldemy

Neuraldemy

This is Neuraldemy support. Subscribe to our YouTube channel for more.

Related Posts

Node.js EventEmitter Explained: Events, Listeners, and Event-Driven Architecture

DNS And TCP in Node.js Explained

HTTPS and TLS in Node.js Explained : Encryption, Certificates, Handshakes, and Secure Servers

Node.js Streams Explained: Readable, Writable, Transform, and Backpressure

Node.js Buffer Explained: Bytes, Binary Data, and Character Encoding

Node.js File System Explained: A Beginner’s Guide to fs and fs/promises

Next Post

HTTPS and TLS in Node.js Explained : Encryption, Certificates, Handshakes, and Secure Servers

DNS And TCP in Node.js Explained

Node.js EventEmitter Explained: Events, Listeners, and Event-Driven Architecture

Support

Buy author a coffee

Support

Support Neuraldemy

Newsletter

Top rated products

  • Probability and Statistics for Machine Learning and Data Science Probability and Statistics for Machine Learning and Data Science
    Rated 5.00 out of 5
    30.00$ Original price was: 30.00$.12.99$Current price is: 12.99$.
  • SVM Notes: Optimization & Implementation SVM Notes: Optimization & Implementation
    Rated 5.00 out of 5
    9.99$ Original price was: 9.99$.4.99$Current price is: 4.99$.
  • spidy mail hunter software Spidy Mail Hunter: Powerful And Intelligent Website & PDF Email Extractor Software (Windows App)
    Rated 4.78 out of 5
    149.00$ Original price was: 149.00$.49.00$Current price is: 49.00$.
  • Linear Algebra For Machine Learning And Data Science Linear Algebra For Machine Learning And Data Science
    Rated 4.71 out of 5
    40.00$ Original price was: 40.00$.20.99$Current price is: 20.99$.
  • Javascript DOM Notes Javascript Document Object Model Notes And Codes (Free) 0.00$

Recent Posts

Node.js EventEmitter Explained: Events, Listeners, and Event-Driven Architecture

DNS And TCP in Node.js Explained

HTTPS and TLS in Node.js Explained : Encryption, Certificates, Handshakes, and Secure Servers

October 2026
M T W T F S S
 1234
567891011
12131415161718
19202122232425
262728293031  
« Aug    

Neuraldemy

Neuraldemy

Neuraldemy

Software Development

Neuraldemy helps you learn ML, AI, Web Dev and data science from scratch. Addtionally, we also provide web development and hosting services for businesses, and individuals

[convertkit form=7049059]
  • Get Started
  • Contact
  • Book Services
  • Privacy Policy
  • Terms Of Use
  • Support Portal
  • Managed Hosting Terms
  • Web Development Terms
  • Refund Policy
Neuraldemy

© 2026 - A learning platform by Odist Magazine

Welcome Back!

Login to your account below

Forgotten Password? Sign Up

Create New Account!

Fill the forms below to register

*By registering into our website, you agree to the Terms & Conditions and Privacy Policy.
All fields are required. Log In

Retrieve your password

Please enter your username or email address to reset your password.

Log In
No Result
View All Result
Join Our WhatsApp Channel
  • Home
  • Tutorials
    • Machine Learning
    • Python
    • Web Development
    • Javascript
    • Mathematics
    • Deep Learning
    • Artificial Intelligence
  • Store
  • Our Services
    • Managed Hosting Services
    • Web Development Services
    • Digital Marketing Services
    • Website Security Checker
    • Video Editing Services
  • Our Softwares
    • JaggoRe AI
    • Email Extractor
    • TimeWell App
  • Login
  • Sign Up
  • Cart
Order Details

© 2026 - A learning platform by Odist Magazine

This website uses cookies. By continuing to use this website you are giving consent to cookies being used.
0