1. 개요

Node.js의 net 모듈(TCP 레벨)만 사용하여 Express와 유사한 API를 제공하는 웹 애플리케이션 서버(WAS)를 직접 구현한 프로젝트입니다. TCP 소켓 수신부터 HTTP 메시지 파싱, 라우팅, 응답 작성까지 전 과정을 라이브러리 없이 구현했습니다.

목표

다음과 같이 Node.js 의 net 모듈만으로 Express 와 같은 인터페이스를 제공하는 WAS 를 구현하는 것을 목표로 했습니다.

const net = require('net');

const server = net.createServer(socket => {
    socket.on('data', data => {
        const request = data.toString();
        if (request.includes('GET')) {
            socket.write('HTTP/1.1 200 OK\r\n');
            socket.write('Content-Type: text/plain\r\n');
            socket.write('\r\n');
            socket.write('Hello, World!');
            socket.end();
        }
    });
});

server.listen(3000, () => {
    console.log('HTTP server running on port 3000');
});
// app.js
const app = createApp();

app.get('/hello/:name', (req, res) => {
    res.json({ msg: `Hi, ${req.params.name}!` });
});

app.listen(3000, '127.0.0.1', () => {
    console.log('listening on <http://127.0.0.1:3000>');
});

프레임워크 사용자가 흔히 접하는 app.get(), app.post(), res.json()과 같은 추상화를 가장 밑바닥부터 직접 쌓아 올리는 것이 관건이었습니다.


2. 아키텍처 설계

가장 먼저 전체 구조를 Architecture.md에 설계했습니다.

하나의 요청이 처리되는 흐름을 "TCP 수신 → HTTP 파싱 → 라우팅 → 응답 작성" 으로 나누고, 각 단계를 독립된 모듈로 분리하여 책임과 역할을 명확히 구분했습니다.

BE/
├─ core/
│  ├─ HttpServer.js      // net.Server 생성, 연결마다 HttpConnection 인스턴스 생성
│  ├─ HttpConnection.js  // 소켓 1개의 수명·상태 관리, data 이벤트 수신
│  ├─ HttpParser.js      // 상태 머신으로 바이트 스트림 → HTTP 메시지 파싱
│  └─ app.js             // 라우트 테이블 등록, 핸들러 디스패치, app 파사드 반환
├─ http/
│  ├─ Request.js         // 요청 객체(method, path, query, headers, body)
│  └─ Response.js        // 응답 객체(status, setHeader, send, json, end)
├─ routing/
│  ├─ pattern.js         // 경로 패턴을 정규식으로 변환, :param 키 추출
│  └─ match.js           // 라우팅 테이블 순회, 매칭 및 params 바인딩
└─ server.js             // 테스트용 라우트 모음

이때 요청 처리의 데이터 흐름은 다음과 같습니다.

클라이언트 (TCP)
   │
   ▼
net.Server ── 연결 수립 ──▶ HttpConnection (소켓 1:1)
                              │  socket.on('data')
                              ▼
                          HttpParser.parseChunk()  ← 바이트 스트림을 완성된 메시지로
                              │
                              ▼
                          Request / Response 생성
                              │
                              ▼
                          app.handle() ─▶ match() ─▶ 라우트 핸들러 실행

3. 핵심 구현

3-1. TCP 서버 진입점 — HttpServer

예제코드의 net.createServer()를 감싸, 클라이언트 연결이 수립될 때마다 전달받는 net.Socket 인스턴스를 HttpConnection으로 넘겨 각 연결을 관리하도록 했습니다.