백엔드 개발 핵심 개념 정리: 서버부터 라우터까지
웹 서버, WAS, DB로 이루어진 백엔드 기본 구조부터 Node.js, HTTP, 라우터의 역할까지 전반적인 내용을 다룬다.
1. 백엔드 3-Tier 아키텍처
현대 웹 서비스의 백엔드는 대부분 웹 서버, 웹 애플리케이션 서버(WAS), 데이터베이스(DB)의 3계층(3-Tier) 구조로 이루어져 있다. 각 요소는 명확히 분리된 역할을 수행하며 시스템의 효율성과 안정성을 높인다.
사용자 ↔ 웹 서버 ↔ WAS ↔ 데이터베이스
1-1. 웹 서버 (Web Server)
- 역할: 사용자의 HTTP 요청을 가장 먼저 받는 '접수 창구'이다.
- 주요 기능:
- 정적 콘텐츠 제공: HTML, CSS, 이미지처럼 내용이 바뀌지 않는 파일(정적 콘텐츠)을 직접 사용자에게 전달한다.
- 요청 분배 (로드 밸런싱): 여러 대의 WAS가 있을 경우, 요청을 적절히 분배하여 서버 과부하를 방지한다.
- 보안: 외부 공격으로부터 내부 시스템을 보호하는 1차 방어선 역할을 한다.
- 예시: Nginx, Apache HTTP Server
1-2. 웹 애플리케이션 서버 (WAS, Web Application Server)
- 역할: 웹 서버로부터 전달받은 동적 요청을 처리하고 비즈니스 로직을 수행하는 '메인 주방'이다.
- 주요 기능:
- 동적 콘텐츠 생성: 사용자의 요청에 따라 데이터를 가공하여 실시간으로 새로운 페이지나 데이터를 생성한다. (예: '내 주문 내역' 페이지)
- 비즈니스 로직 수행: 로그인, 회원가입, 결제 등 서비스의 핵심 기능을 실제로 처리한다.
- 데이터베이스 연동: 로직 수행에 필요한 데이터를 DB에서 조회하거나 저장, 수정, 삭제(CRUD)한다.
- 예시: Tomcat(Java), Gunicorn(Python), Node.js
1-3. 데이터베이스 (Database, DB)
- 역할: 서비스에 필요한 모든 데이터를 저장하고 관리하는 '대형 창고'이다.
- 주요 기능:
- 데이터 영구 저장: 회원 정보, 게시글, 상품 목록 등 모든 데이터를 안전하게 보관한다.
- 데이터 관리: WAS의 요청에 따라 데이터를 생성(Create), 조회(Read), 수정(Update), 삭제(Delete)하는 작업을 수행한다.
- 예시: MySQL, PostgreSQL, MongoDB
💡 왜 구조를 나눌까?
- 효율성: 정적 파일은 웹 서버가, 동적 처리는 WAS가 전담하여 역할 분담을 통해 전체 성능이 향상된다.
- 안정성: WAS나 DB에 장애가 발생해도 웹 서버가 앞단에서 요청을 처리하여 서비스 중단을 막을 수 있다.
- 확장성: 사용자가 늘어나면 부하가 많이 걸리는 WAS만 여러 대로 증설하는 등 유연한 확장이 가능하다.
- 보안: 중요한 데이터와 로직이 담긴 WAS와 DB를 외부에 직접 노출하지 않아 보안을 강화할 수 있다.
2. Node.js란 무엇인가?
Node.js는 자바스크립트를 웹 브라우저 밖, 즉 서버 측에서도 실행할 수 있게 해주는 'JavaScript 런타임 환경'이다. Node.js를 통해 자바스크립트라는 하나의 언어로 프론트엔드와 백엔드를 모두 개발할 수 있다.
핵심 특징: 이벤트 기반과 논 블로킹 I/O
Node.js의 가장 큰 특징은 이벤트 기반(Event-driven) 비동기(Asynchronous) 논 블로킹 I/O(Non-blocking I/O) 모델을 사용한다는 점이다.
- 블로킹(Blocking): 하나의 작업이 끝날 때까지 다음 작업이 멈춰서 기다리는 방식.
- 논 블로킹(Non-blocking): 하나의 작업이 처리되는 동안 기다리지 않고 바로 다음 작업을 수행하는 방식. 작업이 완료되면 '이벤트'를 통해 결과를 알린다.
Node.js는 이 논 블로킹 방식 덕분에 데이터베이스 조회나 파일 읽기처럼 시간이 걸리는 작업이 발생해도 서버가 멈추지 않고 다른 요청을 동시에 처리할 수 있다. 이로 인해 적은 자원으로도 높은 처리 성능을 낼 수 있다.
고려할 점
- CPU 집약적인 작업에는 부적합: Node.js는 기본적으로 단일 스레드(Single-threaded)로 동작한다. 따라서 복잡한 수학 연산이나 대규모 데이터 처리 같은 CPU를 많이 사용하는 작업에는 성능이 저하될 수 있다.
3. 개발 환경의 기본: localhost와 포트
localhost란?
localhost는 "바로 이 컴퓨터"를 의미하는 특별한 호스트명(hostname)이다. 네트워크상에서 자기 자신을 가리키는 예약된 주소이며, 항상 IP 주소 127.0.0.1을 가리킨다.
개발자는 실제 서버에 배포하기 전, localhost를 통해 자신의 컴퓨터에서 만든 서버 프로그램을 테스트할 수 있다.
포트 번호(Port Number)란?
IP 주소가 '아파트 건물 주소'라면, 포트 번호는 '몇 동 몇 호'에 해당하는 상세 주소이다.
하나의 컴퓨터(하나의 IP) 안에서는 웹 서버, DB 서버, 개발 중인 서버 등 여러 프로그램이 동시에 실행될 수 있다. 포트 번호는 컴퓨터에 도착한 네트워크 요청이 이 수많은 프로그램 중 어떤 프로그램에게 전달되어야 하는지 구분하는 역할을 한다.
- IP 주소: 네트워크상에서 컴퓨터(장치)를 식별한다.
- 포트 번호: 그 컴퓨터 안에서 실행되는 특정 프로그램(서비스)을 식별한다.
4. 웹 통신의 언어: HTTP
HTTP 통신의 구조: 요청(Request)과 응답(Response)
Node.js의 http 모듈을 비롯한 모든 웹 서버의 동작을 이해하기 위해서는 먼저 HTTP에 대한 이해가 필수적이다. HTTP(HyperText Transfer Protocol)는 웹에서 클라이언트(주로 웹 브라우저)와 서버가 서로 데이터를 주고받기 위해 사용하는 통신 규칙(프로토콜)이다.
HTTP 통신은 반드시 클라이언트의 "요청(Request)"이 있고, 그에 대한 서버의 "응답(Response)"이 뒤따르는 한 쌍의 구조로 이루어진다. 이 요청과 응답은 모두 정해진 형식의 'HTTP 메시지'에 담겨 전달된다.
HTTP 메시지의 기본 구조
HTTP 요청과 응답 메시지는 공통적으로 크게 세 부분으로 구성된다.
- 시작 줄 (Start-line): 요청 또는 응답의 가장 기본적인 정보(상태)를 담는다.
- 헤더 (Headers): 해당 요청 또는 응답에 대한 추가적인 정보(메타데이터)를 담는다.
- 본문 (Body): 실제 전송하려는 데이터가 담기는 부분이다. (선택 사항)
[ Start-line ]
[ Headers ]
[ (empty line) ] <-- 헤더와 본문을 구분하는 빈 줄
[ Body ]
1. HTTP 요청 (Request) - 클라이언트가 서버에게
HTTP 요청은 클라이언트가 서버에게 특정 동작을 수행해달라고 보내는 메시지이다.
요청 메시지 예시 (GET)
GET /users/123 HTTP/1.1
Host: api.example.com
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64)
Accept: application/json
① 시작 줄 (Request Line)
- HTTP 메서드 (Method): 서버에 요청하는 동작의 종류를 나타낸다.
GET: 데이터 조회POST: 데이터 생성 (등록)PUT: 데이터 전체 수정PATCH: 데이터 일부 수정DELETE: 데이터 삭제
- 요청 대상 (Request Target): 요청하는 리소스가 서버 내 어디에 있는지 나타내는 경로(URI)이다. (예:
/users/123) - HTTP 버전: 사용된 HTTP의 버전이다. (예:
HTTP/1.1)
② 헤더 (Headers)
Host: 요청을 보내는 서버의 도메인 주소이다. (필수)User-Agent: 요청을 보낸 클라이언트(브라우저, OS 등)의 정보이다.Accept: 클라이언트가 수신하고자 하는 데이터 타입을 명시한다.
③ 본문 (Body)
POST나 PUT 요청과 같이, 서버에 새로운 데이터를 생성하거나 수정하기 위해 보낼 데이터를 담는 부분이다. GET 요청은 보통 데이터를 조회하는 목적이므로 본문이 비어있는 경우가 많다. POST나 PUT 요청 시에는 서버로 보낼 데이터를 여기에 담는다.
2. HTTP 응답 (Response) - 서버가 클라이언트에게
HTTP 응답은 서버가 클라이언트의 요청을 처리한 후, 그 결과를 전달하는 메시지이다.
응답 메시지 예시
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 85
{
"id": 123,
"name": "JHParrrk",
"email": "user@example.com"
}
① 시작 줄 (Status Line)
- HTTP 버전: 응답에 사용된 HTTP의 버전이다.
- 상태 코드 (Status Code): 요청 처리 결과를 나타내는 세 자리 숫자이다.
2xx(성공): 요청이 성공적으로 처리됨. (예:200 OK,201 Created)3xx(리다이렉션): 요청을 완료하려면 추가 동작이 필요함.4xx(클라이언트 오류): 요청 형식이 잘못되었거나 리소스를 찾을 수 없음. (예:400 Bad Request,404 Not Found)5xx(서버 오류): 서버 측에서 요청을 처리하는 중 오류가 발생함. (예:500 Internal Server Error)
- 상태 텍스트 (Status Text): 상태 코드를 사람이 이해하기 쉽게 설명하는 짧은 문구이다. (예:
OK)
② 헤더 (Headers)
Content-Type: 응답 본문에 담긴 데이터가 어떤 타입인지 알려준다. (예:application/json)Content-Length: 응답 본문의 크기(길이)를 바이트 단위로 나타낸다.
③ 본문 (Body)
서버가 클라이언트에게 전달하는 실제 데이터(HTML 문서, JSON 데이터, 이미지 등)가 들어가는 부분이다.
핵심 요약
- HTTP는 요청(Request)과 응답(Response)의 한 쌍으로 동작한다.
- 요청은 "무엇을(
Method) 어디에서(Target) 어떻게(Headers,Body) 할지"를 담아 보낸다.- 응답은 "요청 결과가 어땠는지(
Status Code) 어떤 데이터(Body)를 주는지"를 담아 보낸다.
이 구조를 이해하는 것은 웹이 어떻게 동작하는지 파악하고, 백엔드 API를 설계하고 통신하는 데 가장 기본이 되는 첫걸음이다.
Content-Type 헤더의 역할
Content-Type은 응답 본문에 담긴 데이터가 어떤 형식인지 알려주는 중요한 헤더이다.
| Content-Type | 의미 및 사용처 |
|---|---|
text/html |
HTML 문서. 브라우저가 웹 페이지로 렌더링한다. (SSR 등) |
application/json |
JSON 데이터. API 서버가 프론트엔드와 데이터를 주고받을 때 사용한다. |
text/plain |
일반 텍스트. 간단한 메시지를 형식 없이 전달할 때 사용한다. |
application/octet-stream |
바이너리 파일. 사용자가 파일을 다운로드하게 만들고 싶을 때 사용한다. |
5. Node.js로 서버 만들기: URL과 라우터
url 모듈의 역할
Node.js의 url 모듈은 URL 문자열을 다루기 위한 유틸리티를 제공한다. 특히 클라이언트가 보낸 URL에서 쿼리 스트링(Query String) 같은 유의미한 정보를 추출할 때 매우 유용하다.
예시:
https://grab-market.com/products?id=123&category=electronics
위 URL에서 id=123과 category=electronics라는 정보를 안정적으로 추출하기 위해 url 모듈을 사용한다. 현재는 웹 표준인 WHATWG URL API (new URL(...)) 사용이 권장된다.
라우터(Router)란?
라우터(Router)는 "경로를 지정해주는 것"으로, 웹 서버에 들어온 요청의 URL 경로를 보고 어떤 함수가 그 요청을 처리할지 결정하고 연결해주는 '안내원' 역할을 한다.
클라이언트가 서버에 요청을 보낼 때, URL 경로는 제각각 다르다.
/요청은 메인 페이지를 보여달라는 의미이다./login요청은 로그인을 처리해달라는 의미이다./products/123요청은 123번 상품의 상세 정보를 달라는 의미이다.
라우터는 바로 이 URL 경로를 보고, 요청을 적절한 '핸들러(Handler)' 함수로 보내주는 교통정리를 담당한다.
라우터가 없다면?
만약 라우터 패턴을 적용하지 않는다면, 서버의 메인 파일은 들어오는 모든 경로를 처리하기 위해 거대한 if-else 문으로 가득 차게 될 것이다.
라우터가 없을 경우 (나쁜 예시)
function onRequest(request, response) {
let pathname = url.parse(request.url).pathname;
if (pathname === '/') {
// 메인 페이지 처리 로직...
} else if (pathname === '/login') {
// 로그인 처리 로직...
} else if (pathname === '/upload') {
// 업로드 처리 로직...
} else {
// 404 Not Found 처리...
}
}
애플리케이션의 기능이 추가될수록 이 if-else 블록은 끝없이 길어지고, 코드를 유지보수하기가 매우 어려워진다.
라우터 패턴의 적용
이러한 문제를 해결하기 위해, 요청을 받는 부분과 경로를 해석하여 처리하는 부분을 분리하는 라우터 패턴을 적용할 수 있다.
라우터 함수를 분리한 예시
// server.js
function start(route, handle) { // <-- route 함수를 인자로 받음
function onRequest(request, response) {
// 1. URL에서 경로(pathname)를 추출한다.
let pathname = url.parse(request.url).pathname;
// 2. 서버가 직접 판단하지 않고, 'route' 함수에게 처리를 위임한다.
// "이 경로(pathname)로 요청이 왔는데, 누가 처리해야 할지 알려주고 실행해줘!"
route(pathname, handle, response);
}
http.createServer(onRequest).listen(8888);
}
위 코드에서 server.js는 모든 요청을 받는 '접수 창구' 역할만 하고, 실제 어떤 작업을 수행할지에 대한 판단은 route라는 외부 함수에 위임한다. 이로써 역할과 책임이 분리되어 코드가 훨씬 깔끔해진다.
현대적 프레임워크의 라우터 (Express.js 예시)
Express.js와 같은 현대적인 프레임워크는 이러한 라우팅 기능을 매우 직관적이고 편리하게 제공한다.
const express = require('express');
const app = express();
// GET 방식으로 '/' 경로에 요청이 오면 이 함수를 실행한다.
app.get('/', (req, res) => {
res.send('여기는 메인 페이지입니다.');
});
// 1. 모든 상품 목록을 조회하는 API
// GET 방식으로 '/products' 경로에 요청이 오면 이 함수를 실행합니다.
app.get('/products', (req, res) => {
// 데이터베이스에서 모든 상품 정보를 가져오는 로직
res.send('모든 상품 목록을 응답합니다.');
});
// 2. 특정 상품의 상세 정보를 조회하는 API
// GET 방식으로 '/products/:id' 경로에 요청이 오면 이 함수를 실행합니다.
// :id 부분은 동적으로 변하는 값(파라미터)을 의미합니다.
// 예를 들어, /products/123 으로 요청하면 id는 '123'이 됩니다.
app.get('/products/:id', (req, res) => {
const productId = req.params.id; // URL의 :id 부분에 들어온 값을 가져옵니다.
res.send(`요청하신 상품 ID는 ${productId} 입니다.`);
});
// 3. 새로운 상품을 등록하는 API
// POST 방식으로 '/products' 경로에 요청이 오면 이 함수를 실행합니다.
app.post('/products', (req, res) => {
// 클라이언트가 보낸 상품 정보를 데이터베이스에 저장하는 로직
res.send('새로운 상품이 성공적으로 등록되었습니다!');
});
app.listen(3000, () => {
console.log('서버가 3000번 포트에서 실행 중입니다.');
});
이처럼 라우터를 사용하면 역할 분리를 통해 서버의 주된 로직과 경로 처리 로직을 분리하여 코드를 훨씬 깔끔하고 체계적으로 유지할 수 있다.
'Programmers' 카테고리의 다른 글
| [10일차]웹 개발을 위한 HTTP 메서드와 Node.js (0) | 2025.09.12 |
|---|---|
| [9일차] 웹 백엔드 아키텍처와 RESTful API 설계 규칙 (0) | 2025.09.11 |
| [7일차]CSS와 JS 실습 (0) | 2025.09.05 |
| [6일차]웹 생태계의 이해와 HTML (0) | 2025.09.04 |
| [5일차]프로젝트 관리 솔루션 (0) | 2025.09.03 |