A complete HTTP/1.1 server implementation built from scratch using Node.js TCP sockets, demonstrating low-level network programming and HTTP protocol implementation.
This project contains two implementations:
socket.ts
- A simple TCP echo server (foundation)http-server-clean.ts
- A complete HTTP/1.1 server implementationHTTP_SERVER_GUIDE.md
- Detailed step-by-step explanation
- ✅ HTTP/1.1 protocol implementation
- ✅ GET and POST method support
- ✅ Keep-alive connections
- ✅ Content-Length handling
- ✅ Header parsing and validation
- ✅ Error handling with proper HTTP status codes
- ✅ Echo server functionality
- ✅ Dynamic buffer management
- ✅ Promise-based socket API
- ✅ Streaming request body reading
- From Scratch: No HTTP frameworks used - built directly on TCP sockets
- Educational: Every line is explained with comments and documentation
- Production Concepts: Implements real-world concerns like buffer management, error handling, and HTTP compliance
- Modern TypeScript: Uses async/await and proper typing throughout
npm install typescript @types/node ts-node
# Start the server
npx ts-node http-server-clean.ts
# In another terminal, test it
curl http://127.0.0.1:3000/
curl http://127.0.0.1:3000/echo -d "Hello World"
# Or run the test suite
./test-server.sh
# Start the echo server
npx ts-node socket.ts
# In another terminal, test with netcat
echo "hello" | nc 127.0.0.1 1234
curl http://127.0.0.1:3000/
# Response: hello world.
curl http://127.0.0.1:3000/echo -d "Your message here"
# Response: Your message here
curl http://127.0.0.1:3000/echo \
-H "Content-Type: application/json" \
-d '{"name": "test", "value": 123}'
# Response: {"name": "test", "value": 123}
curl -H "X-Custom-Header: value" http://127.0.0.1:3000/echo -d "data"
# The server will echo back: data
┌─────────────────────────────────┐
│ Application Layer │ ← handleReq() - Business logic
├─────────────────────────────────┤
│ HTTP Protocol Layer │ ← HTTP parsing, response generation
├─────────────────────────────────┤
│ Buffer Management Layer │ ← Dynamic buffers, message framing
├─────────────────────────────────┤
│ Promise Socket Layer │ ← soRead(), soWrite() - Promise API
├─────────────────────────────────┤
│ TCP Socket Layer │ ← Raw Node.js net.Socket
└─────────────────────────────────┘
Custom error handling with HTTP status codes
- Grows automatically as needed
- Efficient append/remove operations
- Handles variable-length HTTP messages
- Converts event-based sockets to Promise-based API
- Handles errors, EOF, and data events
- Enables clean async/await usage
- Streaming interface for large request bodies
- Memory-efficient for file uploads
- Supports both Content-Length and chunked encoding
- Parses request line, headers, and body
- Validates HTTP format compliance
- Generates proper HTTP responses
web-server/
├── socket.ts # TCP echo server (foundation)
├── http-server-clean.ts # Complete HTTP server implementation
├── HTTP_SERVER_GUIDE.md # Detailed implementation guide
├── test-server.sh # Test script
├── package.json # Node.js dependencies
├── tsconfig.json # TypeScript configuration
└── README.md # This file
// Convert callback-based socket to Promise-based
async function soRead(conn: TCPConn): Promise<Buffer> {
return new Promise((resolve, reject) => {
conn.reader = { resolve, reject };
conn.socket.resume();
});
}
// Efficient buffer that grows by powers of 2
function bufPush(buf: DynBuf, data: Buffer): void {
let cap = Math.max(buf.data.length, 32);
while (cap < newLen) {
cap *= 2; // Double capacity when needed
}
}
// Parse complete HTTP requests from byte stream
function cutMessage(buf: DynBuf): null | HTTPReq {
const indx = buf.data.indexOf("\r\n\r\n");
if (indx < 0) return null; // Incomplete message
// Parse and remove from buffer
}
// Handle large request bodies without loading into memory
type BodyReader = {
length: number;
read: () => Promise<Buffer>;
};
This implementation teaches:
- Network Programming: TCP sockets, event handling, buffers
- HTTP Protocol: Request/response format, headers, status codes
- Async Programming: Converting callbacks to Promises, async/await
- Buffer Management: Dynamic sizing, efficient memory usage
- Error Handling: Network errors, protocol errors, graceful degradation
- TypeScript: Advanced types, interfaces, error handling
The project includes comprehensive tests:
# Run all tests
./test-server.sh
# Manual testing
curl -v http://127.0.0.1:3000/
curl -X POST http://127.0.0.1:3000/echo -d "test data"
curl -H "Content-Type: application/json" http://127.0.0.1:3000/echo -d '{"test": true}'
- No HTTPS/TLS support
- No chunked transfer encoding
- No compression (gzip)
- No file serving
- Basic routing only
- TLS/HTTPS: Add encryption layer
- HTTP/2: Binary protocol, multiplexing
- Chunked Encoding: Unknown content length support
- Compression: Response compression
- Static Files: File serving with proper MIME types
- Routing: Pattern matching, middleware system
- Authentication: Basic auth, JWT support
- WebSocket: Upgrade from HTTP
- Memory Efficient: Streaming body processing
- Connection Reuse: HTTP/1.1 keep-alive support
- Buffer Management: Power-of-2 growth prevents fragmentation
- Error Handling: Proper cleanup prevents memory leaks
This implementation is designed for learning:
- Complete: Every aspect of HTTP is implemented
- Documented: Extensive comments explain the "why"
- Testable: Includes test scripts and examples
- Extensible: Clean architecture allows easy additions
- Real-world: Handles edge cases and error conditions
MIT License - Feel free to use this for learning and educational purposes.
This is an educational project. If you find bugs or have suggestions for better explanations, please open an issue!
Built with ❤️ for understanding how HTTP really works under the hood.