Menu

Node.js HTTP 서버 만들기: http.createServer 예제

http.createServer()는 요청마다 req와 res를 받아 실행되는 함수 하나를 받습니다. req에서 메서드와 URL을 읽고, res에 상태, 헤더, 본문을 설정한 뒤 res.end()를 호출합니다.

이 페이지에는 실행 가능한 에디터가 있습니다. 편집하고 실행하면 결과를 바로 볼 수 있습니다.

http.createServer()는 요청 핸들러라는 함수 하나로 웹 서버를 만듭니다. Node.js는 요청이 들어올 때마다 이 함수를 두 객체와 함께 호출합니다. req(클라이언트가 보낸 메서드, URL, 헤더)와 res(돌려보낼 것)입니다. res에 상태 코드와 헤더를 설정하고, 본문과 함께 res.end()를 호출하세요. server.listen(3000)은 3000번 포트에서 연결을 받기 시작합니다.

첫 요청은 You asked for GET /hello?name=Ada로 응답하고, Console에도 같은 메서드와 URL이 기록됩니다. 요청 바에서 DELETE /anything을 보내 보세요. 같은 핸들러가 응답합니다. 순수한 http 서버에는 라우트가 없어서, 모든 요청이 그 함수 하나를 거치기 때문입니다.

JSON 보내고 상태 코드 설정하기

res.writeHead(status, headers)는 상태 코드와 헤더를 한 번에 설정합니다. JSON이라면 Content-Type: application/json을 설정하고 JSON.stringify()의 결과를 보내세요. res.end()는 문자열이나 Buffer만 받습니다.

GET /users?limit=1은 [{"id":1,"name":"Ada"}]를 반환합니다. req.url은 경로에 쿼리 문자열이 붙은 것이라서, new URL(req.url, 'http://localhost')로 pathname과 searchParams로 나눕니다(기준 주소는 URL이 절대 주소를 요구하기 때문에 넣은 것뿐입니다). GET /nothing을 보내면 상태가 404로 바뀝니다.

switch로 요청 라우팅하기

작은 API라면 메서드와 경로에 대한 switch 하나면 충분합니다. 'GET /users' 같은 키를 만들고 라우트마다 case를 두세요.

본문 {"name": "Grace"}로 POST /users를 보내면 201과 {"id":2,"name":"Grace"}가 돌아오고, 이제 GET /users는 두 사용자를 모두 나열합니다. 같은 POST를 본문 hello로 보내면 400과 {"error":"Body must be JSON"}을 받습니다.

POST 본문 읽기

Node.js는 본문을 대신 파싱해 주지 않습니다. req는 읽기 가능한 스트림이고, 본문은 data 이벤트로 조각조각 도착한 다음 다 오면 end가 발생합니다. 위의 readBody()는 조각을 문자열로 모아 Promise를 반환하므로 핸들러가 그것을 await할 수 있습니다. req는 비동기 이터러블이라서 이 반복문도 같은 일을 합니다.

let data = '';
for await (const chunk of req) data += chunk;
const body = JSON.parse(data);

실제 서버라면 크기도 제한하고(data.length가 한도를 넘으면 읽기를 멈추고 413으로 응답), JSON.parse를 호출하기 전에 req.headers['content-type']을 확인합니다.

흔한 실수

res.end()를 잊는 것. 상태만 설정하고 응답을 끝내지 않는 분기가 있으면 클라이언트는 시간이 초과될 때까지 기다립니다.

본문 다음에 헤더를 설정하는 것. res.end()나 첫 res.write()가 실행되면 헤더는 이미 전송된 것이라 res.setHeader()가 ERR_HTTP_HEADERS_SENT를 던집니다. 예제처럼 일찍 응답을 보낼 때는 return을 쓰세요.

req.url을 경로와 비교하는 것. req.url에는 쿼리 문자열이 포함되므로 /users?limit=1에 대해 req.url === '/users'는 false입니다. pathname을 비교하세요.

Express를 써야 할 때

헬스 체크, 웹훅 수신, HTTP 동작 방식을 배우는 데는 http 모듈로 충분합니다. /users/:id 같은 라우트 매개변수, JSON 본문 파싱, 미들웨어, 에러 처리가 필요해지면 결국 작은 프레임워크를 직접 쓰게 됩니다. Express가 바로 그 프레임워크이며, 같은 req와 res 객체 위에 만들어져 있습니다. Express 라우팅에서 같은 API를 app.get()과 app.post()로 보여 줍니다.

자주 묻는 질문

res.write와 res.end의 차이는 뭔가요?

res.write()는 본문 조각을 보내고 응답을 열어 두므로 여러 번 호출할 수 있습니다. res.end()는 선택적인 마지막 조각을 보내고 응답을 끝냅니다. 모든 응답에는 res.end()가 정확히 한 번 필요합니다.

Node HTTP 서버는 어떻게 멈추나요?

server.close()를 호출하세요. 새 연결은 바로 받지 않고, 열린 연결이 끝나면 콜백을 실행합니다. SIGTERM을 받았을 때 깔끔하게 종료하려면 이것이 필요합니다.

http 모듈로 HTTPS를 쓰려면?

대신 https 모듈을 쓰세요. https.createServer({ key, cert }, handler)는 같은 핸들러에 개인 키와 인증서를 더해 받습니다. 운영 환경에서는 보통 Nginx 같은 프록시나 호스팅 플랫폼이 TLS를 처리하고, Node 서버는 일반 HTTP로 계속 대기합니다.

Node 서버는 어떤 포트를 써야 하나요?

1024보다 큰 빈 포트라면 관리자 권한 없이 쓸 수 있고, 개발에서는 3000과 8080을 많이 씁니다. 호스팅 플랫폼은 보통 환경 변수로 포트를 알려 주므로 server.listen(process.env.PORT || 3000)처럼 쓰세요.

Coddy 프로그래밍 언어 일러스트

Coddy로 코딩 배우기

시작하기