Node.js 웹 애플리케이션을 배포하는 방법은 무엇인가요?
Node.js는 브라우저 외부에서 JavaScript 코드를 실행할 수 있는 JavaScript 런타임 환경입니다.
Node.js는 Chrome V8 JavaScript 엔진을 기반으로 하며 이벤트 중심의 논블로킹 I/O 모델을 갖추고 있어 서버 측 애플리케이션을 구축하는 데 매우 효율적입니다.
이 문서에서는 서버 측 애플리케이션 개발을 위한 Node.js의 장점과 한계 및 Node.js 애플리케이션의 배포 옵션에 대해 살펴봅니다.
또한 Node.js 애플리케이션을 빌드, 도커화 및 배포하여 Back4app 컨테이너에 무료로 배포할 수 있습니다.
Contents
웹 앱 개발을 위한 Node.js의 장점
Node.js는 2009년에 출시된 이후 서버 측 웹 애플리케이션을 구축하는 데 있어 최고의 선택이었습니다. 그 이유는 다음과 같습니다.
효율성 및 확장성
앞서 언급했듯이 Node.js는 JavaScript 코드를 실행할 수 있는 Chrome의 V8 엔진에서 실행됩니다. 이 엔진은 JIT(Just-in-time) 컴파일을 사용하여 네이티브 JavaScript 코드를 머신 코드로 변환합니다.
런타임 시, V8의 Turbofan 및 Crankshaft 구성 요소는 기계어 코드를 분석하고 다시 컴파일하여 최상의 성능을 제공합니다.
또한 Node.js의 이벤트 중심 모델 덕분에 애플리케이션의 메인 스레드를 차단하지 않고 사용자 상호 작용과 같은 이벤트에 대한 응답으로 코드를 실행할 수 있어 트래픽이 많은 애플리케이션에 이상적입니다.
Node.js의 모듈식 아키텍처와 클러스터링에 대한 기본 지원 덕분에 이를 통해 개발된 애플리케이션은 쉽게 확장할 수 있습니다. 모듈식 아키텍처를 사용하면 애플리케이션을 독립적으로 확장할 수 있는 구성 요소로 나눌 수 있습니다.
클러스터 모듈을 사용하면 여러 코어 또는 서버에 걸쳐 애플리케이션의 여러 인스턴스를 생성할 수 있습니다. 이를 통해 클러스터에 서버를 추가하여 애플리케이션을 확장할 수 있는 수평적 확장이 가능합니다.
얕은 학습 곡선
Node.js는 웹 개발에 널리 사용되는 프로그래밍 언어인 JavaScript를 기반으로 합니다. Node.js에서 JavaScript를 사용함으로써 이미 JavaScript에 익숙한 개발자가 Node.js를 사용한 서버 측 개발에 더 쉽게 접근할 수 있게 되었습니다.
이를 통해 웹 개발의 복잡성을 줄이고 개발 프로세스를 간소화할 수 있습니다.
대규모 에코시스템
Node.js에는 모듈과 패키지로 구성된 방대한 생태계를 구축한 대규모의 활발한 개발자 커뮤니티가 있습니다.
Node.js 패키지 관리자 npm은 애플리케이션에 기능을 추가하는 데 사용할 수 있는 100만 개 이상의 패키지를 호스팅합니다.
이러한 패키지는 lodash와 같은 작은 유틸리티 라이브러리부터 복잡한 웹 애플리케이션을 구축하는 데 사용할 수 있는 대규모 프레임워크 Nest.js까지 다양합니다.
다양한 모듈과 패키지를 사용하면 웹 애플리케이션을 개발하는 데 필요한 시간과 노력을 크게 줄일 수 있습니다.
이러한 패키지를 활용하여 인증, 데이터베이스 통합, 서버 측 렌더링과 같은 기능을 애플리케이션에 추가할 수 있습니다.
웹 앱 개발을 위한 Node.js의 한계
웹 애플리케이션 개발과 관련하여 Node.js는 효율적인 성능, 확장성, 방대한 모듈 및 패키지 에코시스템 등 많은 이점을 제공합니다. 하지만 다른 기술과 마찬가지로 Node.js에도 몇 가지 한계가 있습니다. 몇 가지 제한 사항은 다음과 같습니다.
대용량 메모리 소비
Node.js는 비차단 I/O 모델을 사용하므로 새 스레드를 생성하지 않고도 많은 요청을 동시에 처리할 수 있습니다. 하지만 각 요청을 처리하려면 여전히 메모리를 할당해야 합니다.
즉, Node.js 애플리케이션은 특히 많은 동시 요청을 처리하는 경우 많은 메모리를 소비할 수 있습니다. 이는 메모리가 제한된 시스템에서 실행되는 애플리케이션에 문제가 될 수 있습니다.
비동기 프로그래밍 모델
Node.js의 비동기 프로그래밍 모델은 중요한 장점이지만 개발자에게는 복잡성의 원인이 될 수도 있습니다.
비동기 프로그래밍은 프로그램 흐름에 대한 다른 사고방식을 필요로 합니다. 동기식 프로그래밍에 익숙한 개발자에게는 이러한 전환이 어려울 수 있습니다.
또한 비동기 프로그래밍은 중첩된 콜백으로 인해 코드를 읽고 유지 관리하기 어려운 상황인 콜백 지옥을 초래할 수 있습니다.
단일 스레드 이벤트 루프
Node.js는 네트워크 통신, 파일 I/O, 데이터베이스 작업 등 I/O 집약적인 작업을 처리하도록 설계되었습니다.
그러나 복잡한 계산, 데이터 처리 또는 머신 러닝과 같이 CPU를 많이 사용하는 작업에는 적합하지 않을 수 있습니다.
이는 Node.js가 단일 스레드 이벤트 루프 모델을 사용하므로 한 번에 하나의 작업만 실행할 수 있기 때문입니다.
작업을 완료하는 데 시간이 오래 걸리면 이벤트 루프가 차단되어 애플리케이션이 응답하지 않을 수 있습니다.
Node.js 웹 애플리케이션 배포하기
Node.js 애플리케이션을 배포하는 방법에는 여러 가지가 있습니다. 그 중 몇 가지를 살펴보겠습니다.
클라우드 호스팅 서비스
클라우드 호스팅 서비스를 사용하면 Amazon Web Services (AWS), Google Cloud Platform(GCP) 또는 Microsoft Azure와 같은 회사에서 관리하는 서버에 Node.js 애플리케이션을 배포할 수 있습니다.
확장성, 글로벌 가용성, 간편한 배포, 종량제 요금제 등의 이점을 제공합니다. 또한 데이터베이스 및 로드 밸런싱과 같은 다른 클라우드 서비스와 통합되어 더 나은 애플리케이션을 구축하는 데 도움이 됩니다.
클라우드 호스팅 서비스를 사용하려면 클라우드 제공업체의 서버에 Node.js 애플리케이션을 배포하기만 하면 됩니다. 그런 다음 웹 브라우저나 다른 클라이언트 애플리케이션을 통해 애플리케이션에 액세스할 수 있습니다.
Node.js용 클라우드 호스팅 서비스의 몇 가지 예는 다음과 같습니다:
- AWS Elastic Beanstalk
- GCP App Engine
- Microsoft Azure App Service
이러한 플랫폼을 사용하면 기본 인프라에 대한 걱정 없이 Node.js 애플리케이션을 쉽게 배포, 확장 및 관리할 수 있습니다.
또한 자동 확장, 로드 밸런싱, 기본 제공 모니터링과 같은 기능을 제공하여 애플리케이션을 원활하게 실행할 수 있도록 도와줍니다.
가상 사설 서버(VPS)
가상 사설 서버(VPS)는 물리적 서버에서 실행되는 가상 머신으로, 전용 서버에서 실행하는 것처럼 Node.js 애플리케이션을 설치 및 실행할 수 있습니다.
가상 사설 서버는 공유 호스팅보다 더 강력한 제어 및 사용자 지정 옵션을 제공하는 동시에 전용 서버에 비해 비용 효율적인 대안을 제공합니다.
Node.js 애플리케이션에 VPS 호스팅을 사용하려면 사전 구성된 Node.js 이미지를 제공하는 호스팅 제공업체를 선택하거나 Node.js 및 기타 종속성을 직접 설치하세요.
Node.js용 VPS 호스팅 공급업체의 몇 가지 예는 다음과 같습니다:
- DigitalOcean
- Linode
- Vultr
컨테이너화
컨테이너화는 애플리케이션을 기본 인프라와 분리하여 컨테이너화된 환경에서 배포하고 실행하는 기술입니다.
컨테이너는 기존 가상 머신에 대한 가볍고 유연한 대안을 제공하므로 Node.js 애플리케이션을 배포하는 데 이상적입니다.
이식성이 뛰어나 로컬 머신에서 애플리케이션을 빌드하고 테스트한 다음 컨테이너화를 지원하는 모든 플랫폼에 배포할 수 있습니다.
또한 워크로드에 따라 컨테이너를 쉽게 확장하거나 축소할 수 있어 확장성이 향상됩니다. 다양한 플랫폼에서 일관성을 제공하므로 애플리케이션을 더 쉽게 관리하고 유지할 수 있습니다.
서비스형 컨테이너화 (CaaS)를 제공하는 플랫폼의 몇 가지 예는 다음과 같습니다:
- Back4app Containers
- AWS ECS
- Azure ACI
- Google GKE
Back4app 컨테이너를 사용하여 Back4app에 Node.js 앱 배포하기
Back4app은 직관적인 UI 또는 모든 기능을 갖춘 CLI 도구를 사용하여 웹 애플리케이션을 생성, 관리 및 배포할 수 있는 클라우드 플랫폼입니다. Back4app은 다양한 서비스를 제공하며, 그 중 하나가 컨테이너화입니다.
Back4app 컨테이너는 반복적인 작업을 자동화하고 서버 측 인프라를 관리하여 개발과 프로덕션 간의 간극을 없애므로 DevOps에 대해 걱정할 필요가 없습니다.
이 글에서는 Back4app 컨테이너를 사용하여 간단한 Node.js 애플리케이션을 빌드하고 배포합니다. 빌드할 Node.js 애플리케이션은 CRUD(만들기, 읽기, 업데이트, 삭제) 기능을 지원하는 간단한 서점 API입니다.
개발 환경 설정
새 프로젝트 디렉터리를 만들고 아래 명령을 실행하여 프로젝트 디렉터리에서 npm을 초기화합니다:
mkdir bookstoreapp && cd bookstoreapp && npm init -y
그런 다음 아래 명령을 실행하여 프로젝트에 필요한 종속성을 설치합니다:
npm install express dotenv mysql knex
위에 설치한 종속성은 다음과 같습니다:
- Express.js: Express는 Node.js 애플리케이션 개발 프로세스를 간소화하는 Node.js 프레임워크입니다.
- 닷텐브이: 닷텐브이는 환경 변수를 관리하는 데 사용할 수 있는 npm 패키지입니다.
- MySQL: MySQL 종속성은 이 애플리케이션의 데이터베이스로 사용할 MySQL용 Node.js 드라이버입니다.
- Knex: Knex는 JavaScript용 쿼리 빌더입니다. 원시 SQL 쿼리를 작성하지 않고 데이터베이스와 상호 작용하려면 이 종속성이 필요합니다.
그런 다음 프로젝트의 루트 디렉터리에 routes.js
및 index.js
파일을 만듭니다.
그런 다음 아래 코드 블록을 index.js
파일에 추가합니다:
//index.js
require("dotenv").config();
const express = require("express");
const app = express();
const port = 3000;
const router = require("./routes.js");
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use("/", router);
app.listen(port, () => {
console.log(`App listening at ${port}`);
});
위의 코드 블록은 Express 서버를 생성하고 포트 3000에서 들어오는 HTTP 요청을 수신 대기합니다. 미들웨어 함수를 사용하여 들어오는 데이터를 구문 분석하고 라우터를 루트 경로와 연결하여 들어오는 요청을 처리합니다.
마지막으로 서버를 시작하고 서버가 실행 중이며 지정된 포트에서 수신 중임을 나타내는 메시지를 콘솔에 기록합니다.
다음으로 package.json
파일에 시작
스크립트를 추가합니다. 이렇게 하세요:
"start": "node index.js",
데이터베이스에 연결
Knex에는 데이터베이스에 연결하기 위한 구성 옵션이 포함된 knex파일이 필요합니다.
아래 명령을 실행하여 knex파일을 생성합니다:
knex init
MySQL을 사용하도록 Knex를 구성하려면 knexfile.js
파일의 내용을 아래 코드 블록으로 바꾸세요:
// Update with your config settings.
require("dotenv").config()
/**
* @type { Object.<string, import("knex").Knex.Config> }
*/
module.exports = {
development: {
client: "mysql",
connection: {
host: process.env.DEV_HOST,
user: process.env.DEV_USER,
password: process.env.DEV_PASSWORD,
database: process.env.DEV_NAME,
},
migrations: {
directory: "./db/migrations",
}
},
production: {
client: "mysql",
connection: {
host: process.env.DB_HOST,
user: process.env.DB_USER,
password: process.env.DB_PASSWORD,
database: process.env.DB_NAME,
},
migrations: {
directory: "./db/migrations",
}
},
};
그런 다음 프로젝트의 루트 디렉터리에 db
폴더를 만들고 db.js
파일을 만듭니다.
아래 코드 블록을 db.js
파일에 추가합니다:
//db.js
const knex = require("knex");
const knexFile = require("../knexfile.js");
const environment = process.env.NODE_ENV || "development";
module.exports = knex(knexFile[environment]);
위의 코드 블록은 환경
변수를 NODE_ENV
환경 변수로 설정하거나 NODE_ENV가
설정되지 않은 경우 개발
환경으로 설정합니다. 따라서 개발 또는 프로덕션과 같이 환경에 따라 서로 다른 구성을 지정할 수 있습니다.
마이그레이션 파일 만들기
아래 명령을 실행하여 데이터베이스에 대한 마이그레이션 파일을 생성합니다:
knex migrate:make bookstore
위의 명령은 지정된 파일 경로(“./db/migrations”)에 마이그레이션 파일을 knexfile.js에
생성합니다.
그런 다음 마이그레이션 파일을 열고 그 안에 있는 코드를 아래 코드 블록으로 바꿉니다:
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.up = function (knex) {
return knex.schema.createTable("books", (table) => {
table.increments("id").primary();
table.string("title");
table.string("author");
table.string("genre");
table.timestamps(true, true);
});
};
/**
* @param { import("knex").Knex } knex
* @returns { Promise<void> }
*/
exports.down = function (knex) {
return knex.schema.dropTableIfExists("books");
};
그런 다음 아래 명령을 실행하여 마이그레이션 파일을 실행합니다:
knex migrate:latest
라우팅 구현
마지막으로 아래 코드 블록을 routes.js
파일에 추가합니다:
const express = require("express");
const router = express.Router();
const db = require("./db/db.js");
// GET /books
router.get("/books", async (req, res) => {
try {
const books = await db("books");
res.json(books);
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
//POST /books/new
router.post("/books/new", async (req, res) => {
try {
const { title, author, genre } = req.body;
const book = await db("books").insert({
title,
author,
genre,
});
res.status(201).json(book);
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
//PUT /books/:id
router.put("/books/:id", async (req, res) => {
const { id } = req.params;
try {
const { title, author, genre } = req.body;
const book = await db("books").where({ id }).update(
{
title,
author,
genre,
},
["id", "title", "author", "genre"]
);
if (book.length !== 0) {
res.status(201).send(book);
} else {
res.status(404).json({ error: "Book not found" });
}
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
//DELETE /books/:id
router.delete("/books/:id", async (req, res) => {
const { id } = req.params;
try {
const book = await db("books").where({ id }).del();
if (book !== 0) {
res.status(200).json({ message: "Book deleted" });
} else {
res.status(404).json({ error: "Book not found" });
}
} catch (err) {
console.error(err);
res.status(500).send("Internal Server Error");
}
});
module.exports = router;
위의 코드 블록은 모든 책 가져오기, 새 책 만들기, 기존 책 업데이트, 책 삭제 등 HTTP 요청을 처리하기 위한 여러 경로를 정의합니다.
도커파일 만들기
도커파일은 도커 이미지를 빌드하는 방법에 대한 특정 형식으로 작성된 일련의 지침이 포함된 파일입니다. Docker 이미지는 애플리케이션 코드, 런타임, 라이브러리, 시스템 도구 등 애플리케이션을 실행하는 데 필요한 모든 것을 포함하는 컨테이너의 스냅샷입니다.
Back4app 컨테이너에서 Node.js 애플리케이션을 실행하려면 도커 이미지 빌드 지침이 포함된 도커파일을 만들어야 합니다.
아래 명령을 실행하여 Docker파일을 생성합니다:
touch Dockerfile
다음으로 Node.js 애플리케이션의 기본 이미지를 선택해야 합니다. Docker의 기본 이미지는 새 Docker 이미지를 빌드하기 위한 시작점입니다. 이는 Docker 이미지가 빌드되는 기반이 됩니다.
아래 코드 블록을 도커파일에 추가하여 기본 이미지를 지정하세요:
# Specify base image
FROM node:18-alpine
이 줄은 이 Docker 이미지가 빌드될 기본 이미지를 지정합니다. 이 경우 Node.js 18 버전이 Alpine Linux 배포에서 실행되고 있습니다.
다음으로 작업 디렉터리를 지정해야 합니다. 이후 Docker파일의 모든 명령은 이 디렉터리를 기준으로 실행됩니다.
아래 코드 블록을 Docker파일에 추가하여 작업 디렉터리를 지정하세요:
# Specify working directory
WORKDIR /app
그런 다음 현재 디렉토리(즉, Docker파일이 포함된 디렉토리)에서 작업 디렉토리(/app
)로 package.json
및 package-lock.json
파일을 복사해야 합니다.
아래 코드 블록을 Docker파일에 추가하여 파일을 복사합니다:
# Copy package.json and package-lock.json
COPY package*.json ./
다음으로, 작업 디렉터리에서 npm 설치
명령을 실행하여 package.json
및 package-lock.json에
나열된 Node.js 종속 요소를 설치해야 합니다.
아래 코드를 Docker파일에 추가하여 종속 요소를 설치하세요:
# Install dependencies
RUN npm install
위의 명령은 package.json
**** 및 package-lock.json
파일에 나열된 모든 종속성을 설치하여 지정된 작업 디렉터리의 node_modules
폴더에 저장합니다.
다음으로 애플리케이션의 소스 코드를 작업 디렉토리에 복사해야 합니다.
아래 코드 블록을 Docker파일에 추가하여 소스 코드를 복사합니다:
# Copy source code
COPY . .
다음으로 호스트 머신에 포트를 노출해야 합니다. 포트를 노출하면 Docker 컨테이너를 실행할 때 노출된 포트에서 들어오는 네트워크 연결을 수신할 수 있습니다.
포트 3000을 호스트 머신에 노출하려면 아래 코드 블록을 Docker파일에 추가하세요:
# Expose port 3000
EXPOSE 3000
마지막으로 Docker 컨테이너가 시작될 때 실행할 명령을 지정해야 합니다. 일반적으로 Node.js 애플리케이션은 npm start
명령을 사용하여 시작됩니다.
아래 코드 블록을 Docker파일에 추가하여 명령을 지정합니다:
# Run the app
CMD ["npm", "start"]
완성된 도커파일은 아래 코드 블록과 같아야 합니다:
# Specify base image
FROM node:18-alpine
# Specify working directory
WORKDIR /app
# Copy package.json and package-lock.json
COPY package*.json ./
# Install dependencies
RUN npm install
# Copy source code
COPY . .
# Expose port 3000
EXPOSE 3000
# Run the app
CMD ["npm", "start"]
도커파일을 생성한 후 코드를 GitHub에 푸시합니다.
새 Back4app 애플리케이션 만들기
Back4app에 Node.js 애플리케이션을 배포하는 첫 번째 단계는 Back4app에 계정을 만드는 것입니다(계정이 없는 경우). 아래 단계에 따라 계정을 만들 수 있습니다.
- Back4app 웹사이트로 이동합니다.
- 그런 다음 랜딩 페이지의 오른쪽 상단에 있는 가입 버튼을 클릭합니다.
- 마지막으로 가입 양식을 작성하여 제출합니다.
Back4app 계정을 성공적으로 생성한 후, Back4app 계정에 로그인하고 오른쪽 상단의 새 앱 버튼을 클릭합니다.
이 버튼을 클릭하면 “새 앱을 어떻게 만들 것인가?”를 선택할 수 있는 페이지로 이동합니다. 컨테이너화를 사용하여 배포하는 것이므로 아래 이미지와 같이 서비스형 컨테이너를 선택합니다.
다음으로 GitHub 계정을 Back4app 계정에 연결합니다. Back4app에 계정의 모든 리포지토리 또는 특정 리포지토리에 대한 액세스 권한을 부여하도록 선택할 수 있습니다.
배포하려는 애플리케이션(이 경우 이 자습서에서 빌드한 애플리케이션)을 선택하고 선택을 클릭합니다.
선택 버튼을 클릭하면 이름, 브랜치, 루트 디렉터리, 환경 변수 등 앱에 대한 몇 가지 정보를 입력해야 하는 페이지로 이동합니다.
애플리케이션이 작동하는 데 필요한 모든 환경 변수를 입력해야 합니다. 필요한 세부 정보를 입력한 후 아래 이미지와 같이 앱 만들기를 클릭합니다.
앱 만들기 버튼을 클릭하면 배포 프로세스가 시작됩니다. 배포 프로세스가 완료되면 아래 이미지와 같이 배포된 애플리케이션에 액세스할 수 있는 URL이 화면 왼쪽 모서리에 지정됩니다.
배포 프로세스에 많은 시간이 걸리는 경우 로그를 확인하여 배포에 오류가 발생했는지 확인하거나 Back4app의 문제 해결 가이드를 확인할 수 있습니다.
결론
Node.js는 빠른 성능, 확장성, 유연성 등 많은 장점을 제공하는 인기 있는 프레임워크입니다. 하지만 단일 스레드 특성으로 인해 과중한 워크로드를 처리하기 어려울 수 있는 몇 가지 한계도 있습니다.
이러한 제한에도 불구하고 Node.js 앱을 호스팅하고 관리하기 위한 안정적이고 사용자 친화적인 플랫폼을 제공하는 Back4app을 비롯한 여러 배포 옵션이 Node.js 애플리케이션에 제공됩니다.
이 문서에 설명된 단계를 따라 Node.js 앱을 생성하면 Back4app에 Node.js 애플리케이션을 쉽게 배포하고 다양한 이점을 활용할 수 있습니다.
Node.js 배포 및 호스팅에 대해 더 궁금하신가요? 다음 두 튜토리얼을 확인해 보세요:
자주 묻는 질문
Node.js 웹 애플리케이션을 배포하는 방법?
– 개발 환경 설정
– 데이터베이스에 연결
– 마이그레이션 파일 생성
– 라우팅 구현
– Dockerfile 생성
– 새 Back4app 애플리케이션 생성