가상화 기술과 클라우드 환경이 보편화되면서 웹 애플리케이션의 구조는 거대한 모놀리식(Monolithic) 형태에서 가볍고 유연한 마이크로서비스 형태(MSA)로 변화하고 있다.
특히 Docker 컨테이너나 Kubernetes Pod 환경에서는 애플리케이션이 얼마나 '가볍게' 구동될 수 있는지가 관건이다.
이러한 트렌드 속에서 기존의 Apache/Nginx와 Tomcat 조합보다 훨씬 경량화된 Node.js와 Express.js가 백엔드 개발의 핵심 도구로 자리 잡았다.
리눅스(Ubuntu) 환경에서 Node.js 개발 환경을 구축하고, 버전 호환성 문제를 해결하며 간단한 웹 서버를 띄우는 과정을 단계별로 정리한다.
1. 환경 구축: NVM을 이용한 Node.js 설치
리눅스 시스템의 패키지 매니저(apt)를 통해 Node.js를 설치할 수도 있지만, 개발 과정에서는 프로젝트별로 요구하는 Node 버전이 다를 수 있다.
NVM(Node Version Manager)은 시스템 전역이 아닌 사용자 영역에서 Node.js 버전을 독립적으로 관리하게 해주는 도구로, 여러 프로젝트를 동시에 진행할 때 버전 충돌을 막아준다.
NVM을 사용하면 use 명령어를 사용해 원하는 Node 버전으로 옮겨가며 유연하게 사용할 수 있다.
1-1. NVM 설치 및 설정
먼저 curl 명령어를 사용하여 NVM 설치 스크립트를 다운로드하고 실행한다.
curl로 가져온 스크립트는 홈 디렉토리의 .nvm 폴더에 소스 코드를 클론하고, .bashrc 파일에 실행 경로를 저장한다.
# NVM 설치 스크립트 다운로드 및 실행
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
# 쉘 설정 파일 갱신 (터미널 재시작 없이 nvm 명령어 사용을 위해)
export NVM_DIR="$HOME/.nvm"
[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh"
1-2. Node.js 설치 및 호환성 이슈 해결 (Troubleshooting)
최신 버전의 Node.js를 설치하려다 보면, 구형 리눅스 OS의 시스템 라이브러리(glibc) 버전과 충돌이 발생할 수 있다.
# 최신 버전(v24) 설치 시도
nvm install 24
node -v
# 에러 발생:
/lib/x86_64-linux-gnu/libc.so.6: version `GLIBC_2.28' not found
위와 같은 에러는 OS의 C 라이브러리 버전이 낮아서 발생한다.
무조건 최신 버전을 쓰기보다, 현재 시스템 환경과 호환되면서도 안정적인 LTS(Long Term Support) 버전을 선택하는 것이 중요하다.
여기서는 호환성이 좋은 v16 버전을 설치하여 진행한다.
# 호환 가능한 버전(v16) 설치 및 사용 설정
nvm install 16
nvm use 16
# 버전 확인
node -v
# 출력 예시: v16.20.2
2. 프로젝트 초기화 및 패키지 관리
Node.js 환경이 준비되었다면, 웹 애플리케이션 프로젝트를 생성하고 초기화한다.
2-1. 디렉토리 생성 및 npm init
프로젝트를 위한 폴더를 만들고 npm init을 실행한다.
mkdir user-service-api
cd user-service-api
# 프로젝트 초기화 (package.json 생성)
npm init
- npm init 명령어는 프로젝트의 이름, 버전, 진입점(entry point) 등을 묻고, 그 결과를 담은 package.json 파일을 생성한다.
2-2. Express 프레임워크 설치
Express란?
Node.js를 위한 웹 애플리케이션 프레임워크다.
Java의 JSP/Spring, Python의 Django/Flask와 같은 역할을 하지만 훨씬 가볍고 설정이 간편하다.
웹 서버 기능을 구현하기 위해 Express.js를 설치한다.
npm install --save express
- --save 옵션(최신 npm에서는 생략 가능)은 package.json의 dependencies 항목에 express를 추가한다는 의미다. 이로써 이 프로젝트가 express에 의존하고 있음을 명시한다.
3. 웹 서버 구현 및 아키텍처 이해
이제 실제로 코드를 작성하여 서버를 구동해본다.
3-1. 서버 코드 작성 (index.js)
nano나 vi 에디터를 이용해 index.js 파일을 생성하고 다음 코드를 작성한다.
const express = require('express')
const app = express()
const port = 3000
app.get('/', (req, res) => {
res.send('Hello World!')
})
app.listen(port, () => {
console.log(`Example app listening on port ${port}`)
})
- require('express') : 설치된 express 모듈을 불러온다.
- app.get('/', ...) : 루트 경로(/)로 들어오는 HTTP GET 요청에 대해 "Hello World!"를 응답한다.
- app.listen(port) : 3000번 포트에서 요청 대기 상태(Listen)로 진입한다.
3-2. 소켓(Socket)과 웹 서버의 개념
여기서 port와 listen의 의미를 이해하는 것이 중요하다.
- 소켓(Socket): 네트워크상에서 데이터를 주고받기 위한 창구로, '서버 IP 주소 + 포트 번호'의 조합으로 구성된다. 외부 클라이언트는 이 소켓 주소를 통해 서버에 접속한다.
- 경량화된 웹 서버: 과거에는 Apache와 같은 무거운 웹 서버 소프트웨어를 별도로 띄우고 그 위에서 애플리케이션을 돌렸다. 하지만 Node.js 환경에서는 코드 몇 줄만으로 내장된 라이브러리를 통해 자체적인 웹 서버 기능을 수행한다.

4. 서버 실행 및 테스트
작성한 코드를 실행하여 서버를 구동한다.
# 서버 실행
node index.js
# 출력:
Example app listening on port 3000
서버가 정상적으로 실행되었다면, 다른 터미널 창을 열거나 브라우저를 통해 접속을 확인한다.
GUI 환경이 아닌 경우 터미널 기반 브라우저나 curl을 사용할 수 있다.
5. 정리
- 경량화 트렌드: 별도의 웹 서버 설치 없이 코드 몇 줄로 서버를 띄우는 방식은 클라우드 및 컨테이너(Docker) 환경에 최적화되어 있다.
- 확장성: 이 기초 위에 Jenkins와 같은 CI/CD 도구를 연동하면 코드 변경 시 자동으로 빌드 및 배포가 이루어지는 파이프라인을 구축할 수 있으며, 이는 DevOps의 핵심이다.
'AI Journey > 웹' 카테고리의 다른 글
| Jenkins와 Kubernetes를 조합한 CI/CD 파이프라인 (0) | 2026.01.24 |
|---|---|
| Spring Boot와 NestJS를 비교해보자 (0) | 2026.01.10 |
| [Spring Boot] Spring Initializr로 프로젝트 시작하기 (+인텔리제이 환경설정) (0) | 2026.01.03 |
| 실시간 웹 애플리케이션의 핵심, 웹소켓 (WebSocket) 이해하기 (3) | 2025.07.26 |
| Mocking 개념에 대해 알아보자 (0) | 2025.07.09 |