diff --git a/README.md b/README.md
index 61ad6d4..0b35ab6 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,14 @@
# Connectivity Test Server
-A simple HTTP server for connectivity testing. It can be run from the command line or in a container. It relies only on Python's standard library, so it should work in any environment with Python installed.
+A simple HTTP server for connectivity testing. It can be run from the command line or in a container.
+
+The Python version relies only on Python's standard library, so it should work in any environment with Python installed. The Node.js version also relies only on the standard library, so it should work in any environment with Node.js installed.
It displays a simple HTML or plain-text page with the client's IP address and any detected `X-*` headers.
## Disclaimer
-The `ok-server.py` file was generated using AI under human supervision. The owner of the repository is not responsible for any issues that may arise from using the AI-generated code.
+The **ok-server** scripts were generated using AI under human supervision. The owner of the repository is not responsible for any issues that may arise from using the AI-generated code.
## Usage
@@ -23,8 +25,18 @@ python3 ok-server.py --bind 127.0.0.1 --port 8080 --look bootstrap
`--look` accepts `basic`, `nice`, or `bootstrap`.
You can override the look per request with the `look` query parameter, for example:
+or
+
```bash
-curl -i "http://localhost:8000/?look=basic"
+node ok-server.js
+node ok-server.js --look basic
+node ok-server.js --bind 127.0.0.1 --port 8080 --look bootstrap
+```
+
+Connect to the server using a web browser or a tool like `curl`:
+
+```bash
+curl -si "http://localhost:8000"
```
### Docker
diff --git a/ok-server.mjs b/ok-server.mjs
new file mode 100755
index 0000000..29e16af
--- /dev/null
+++ b/ok-server.mjs
@@ -0,0 +1,324 @@
+#!/usr/bin/env node
+/**
+ * Simple HTTP server that responds 200 with a test message including client IP.
+ *
+ * Behavior:
+ * - GET returns HTTP/1.1 200 with a message including the requester IP
+ * - HEAD returns the same headers as GET but no body
+ * - Displays incoming X-* headers when present
+ * - If User-Agent contains "curl" or "wget" (case-insensitive), the server
+ * responds with Content-Type: text/plain; otherwise Content-Type: text/html.
+ * - --look controls which HTML variant is returned for non-CLI agents:
+ * * basic - plain HTML with no external references
+ * * nice - includes Google Font "Noto Sans" (default)
+ * * bootstrap - Bootstrap 5 layout
+ * - The URL query parameter "look" (e.g. /?look=nice) overrides --look for
+ * that request only. Values are case-insensitive and must be basic,nice,bootstrap.
+ */
+
+import http from "node:http";
+import { URL } from "node:url";
+
+const VALID_LOOKS = new Set(["basic", "nice", "bootstrap"]);
+
+const PLAIN_TEMPLATE =
+ "Hello, This is a test HTTP server.\n\n" +
+ "Your request came from {ip}.\n\n" +
+ "{proxy_headers_block}" +
+ "Have a nice day!\n";
+
+const HTML_BASIC = `
+
+