Skip to content

Instantly share code, notes, and snippets.

@decagondev
Created August 15, 2025 23:58
Show Gist options
  • Select an option

  • Save decagondev/fef3dd17b73011fec6a30444f03fc632 to your computer and use it in GitHub Desktop.

Select an option

Save decagondev/fef3dd17b73011fec6a30444f03fc632 to your computer and use it in GitHub Desktop.

Building a Simple HTTP Server in JavaScript: Problem Set

Introduction

This problem set guides you through building a basic HTTP server from scratch using Node.js's http module. The goal is to create a server that handles simple GET requests, parses incoming HTTP requests, and sends valid HTTP responses. We'll break it down into "tickets" – small, incremental tasks that build upon each other, simulating a real-world project workflow.

Prerequisites:

  • Basic JavaScript knowledge (Node.js, objects, file system operations).
  • No external libraries are allowed; use Node.js's built-in modules (e.g., http, fs, url).
  • Test your server using tools like curl (e.g., curl http://localhost:8080/) or a web browser.

Project Structure:

  • Create a single JavaScript file, e.g., simple_http_server.js.
  • The server should run on localhost port 8080 by default.
  • Handle only GET requests; respond with 405 Method Not Allowed for others.
  • Serve static files from a www directory in the same folder as your script (create it and add some HTML files for testing).

Running the Server:

  • Run your script: node simple_http_server.js.
  • It should listen indefinitely until interrupted (e.g., Ctrl+C).

Complete the tickets in order, testing each step before proceeding.

Ticket 1: Set Up the Basic HTTP Server

Description: Create the foundation for your HTTP server using Node.js's http module to listen for incoming requests.

Tasks:

  • Import the http module.
  • Create an HTTP server using http.createServer.
  • Make it listen on localhost:8080.
  • For now, respond to every request with a simple 200 OK status and the body "Hello, World!".
  • Log a message like "Server is listening on port 8080..." when starting.

Testing:

  • Run the script and use curl http://localhost:8080 or a browser. You should see "Hello, World!".
  • Ensure the server handles multiple requests without crashing.

Hints:

  • Use res.write() and res.end() to send responses.
  • Handle server errors (e.g., port in use) gracefully.

Ticket 2: Parse the Request Line

Description: Parse the HTTP request to extract the method and path, and validate that the method is GET.

Tasks:

  • Access the request's method and url properties.
  • If the method is not "GET", respond with 405 Method Not Allowed.
  • For GET requests, continue responding with "Hello, World!".
  • Log the method and URL of each request to the console.
  • Handle malformed URLs (e.g., invalid format) with a 400 Bad Request.

Testing:

  • curl -X GET http://localhost:8080/ → "Hello, World!".
  • curl -X POST http://localhost:8080/ → 405 response.
  • Check console logs for method and URL.

Hints:

  • Use req.method and req.url.
  • Set status with res.statusCode and headers with res.setHeader.

Ticket 3: Parse Request Headers

Description: Parse the HTTP headers from the request and store them for later use.

Tasks:

  • Access headers via req.headers and log them to the console.
  • Ensure headers are processed (though no specific action is needed yet).
  • Continue with the existing responses (405 for non-GET, 200 for GET).
  • Handle cases where headers are missing or malformed (respond with 400 if necessary).

Testing:

  • Use curl -v -H "X-Test: value" http://localhost:8080/ to see headers logged.
  • Ensure non-GET requests still return 405.

Hints:

  • req.headers is an object; log it directly.
  • Check for invalid header formats if applicable.

Ticket 4: Handle Simple GET Requests with Proper Responses

Description: Implement basic routing for GET requests with proper HTTP response headers.

Tasks:

  • For path /, respond with 200 OK, Content-Type: text/plain, and body "Hello, World!".
  • For path /echo, respond with the request's method, URL, and headers as a JSON string.
  • For other paths, respond with 404 Not Found and body "Not Found".
  • Include Content-Length in all responses.
  • Ensure \r\n line endings in headers.

Testing:

  • curl http://localhost:8080/ → "Hello, World!".
  • curl http://localhost:8080/echo → JSON with request details.
  • curl http://localhost:8080/invalid → 404 with "Not Found".

Hints:

  • Use JSON.stringify for /echo.
  • Calculate Content-Length based on the response body.

Ticket 5: Serve Static Files

Description: Serve files from a www directory for GET requests.

Tasks:

  • Create a www folder with an index.html containing <h1>Welcome!</h1>.
  • Map / to www/index.html and other paths (e.g., /style.css) to www/style.css.
  • Use fs module to read files.
  • Set Content-Type based on file extension (.htmltext/html, .csstext/css, .txttext/plain, default application/octet-stream).
  • Prevent directory traversal (e.g., /../) by normalizing paths.
  • Respond with 404 if the file doesn't exist.

Testing:

  • curl http://localhost:8080/ → HTML content.
  • curl http://localhost:8080/test.txt → Text content.
  • curl http://localhost:8080/missing → 404.
  • curl http://localhost:8080/../secret → 400 or 404.

Hints:

  • Use path module for safe path handling.
  • Read files with fs.readFile.

Ticket 6: Add Error Handling and Logging

Description: Enhance the server with robust error handling and detailed logging.

Tasks:

  • Handle errors (e.g., file read errors, invalid requests) gracefully.
  • Log each request with method, path, and status code (e.g., GET / 200).
  • Add a Server: SimpleNodeServer header to all responses.
  • Ensure the server doesn't crash on errors.

Testing:

  • Simulate errors (e.g., remove www/index.html, send invalid requests).
  • Check console logs for request details and errors.

Hints:

  • Use try-catch for file operations.
  • Use console.error for error logging.

Ticket 7: Support Query Parameters

Description: Parse query parameters and include them in the /echo route.

Tasks:

  • Use the url module to parse the request URL and extract query parameters.
  • For /echo, include query parameters in the JSON response.
  • Ensure file serving uses the base path (ignoring query parameters).
  • Handle invalid query strings gracefully.

Testing:

  • curl http://localhost:8080/echo?name=test → JSON includes {"name": "test"}.
  • curl http://localhost:8080/?param=value → Serves index.html.

Hints:

  • Use url.parse(req.url, true).query for query parameters.
  • Update /echo to include query data.

Bonus Ticket: Add Basic Routing for Dynamic Content

Description: (Optional) Add a dynamic route /greet/:name that returns a personalized greeting.

Tasks:

  • For paths like /greet/Alice, respond with 200 OK and body "Hello, Alice!".
  • Extract the name parameter from the path.
  • Return 404 for invalid dynamic routes.
  • Maintain all previous functionality.

Testing:

  • curl http://localhost:8080/greet/Alice → "Hello, Alice!".
  • curl http://localhost:8080/greet/ → 404.

Hints:

  • Split the path to extract parameters.
  • Validate the route format.

Conclusion

Completing all tickets will result in a functional HTTP server in Node.js! This project teaches HTTP protocols, request parsing, and file serving. Extend it further if desired (e.g., support HEAD requests or use streams for large files). Submit your code and test outputs for review. Great job!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment