Phoenix 웹 애플리케이션에서 react.js 사용하기
Elixir 기반의 Phoenix 프레임워크를 사용해 웹 애플리케이션을 개발하는 경우, 기본적으로 LiveView 를 사용하도록 코드가 생성됩니다. LiveView 를 사용해 개발하는 것도 좋지만, React.js, solid.js 와 같은 라이브러리에 익숙하다면 같이 사용하는 것이 더 편할 수 있습니다.
Phoenix 를 사용해 웹 페이지를 만드는 경우, 각 웹 페이지는 서버에서 렌더링되어 클라이언트로 전달됩니다.
만일 CSR 방식을 사용해 react-router
와 같은 라이브러리를 사용해 클라이언트 라우팅을 사용하려면, Phoenix 로 생성한 프로젝트는 API 서버용으로만 사용하고 react 프로젝트를 별도로 생성하는 방법을 사용해야 합니다. 이 방법은 이 글에서는 다루지 않습니다.
이 글에서는 Phoenix 프레임워크를 사용해 웹 애플리케이션을 생성하고, react.js 를 같이 사용하는 방법과 배포하는 방법까지 알아보겠습니다.
- Phoenix 프로젝트 생성
- React.js 사용하기
- 도커 이미지 생성 및 실행
1. Phoenix 프로젝트 생성
Phoenix 프로젝트를 생성하려면, phoenix 개발에 필요한 프로그램들이 설치되어 있어야 합니다. 설치되어 있지 않다면 아래 섹션을 열어 필요한 프로그램을 먼저 설치해야 합니다.
필요한 프로그램 설치
# Elixir 설치
$ brew install elixir
# HEX (패키지 매니저) 설치
$ mix local.hex
# rebar (Erlang 패키지 매니저) 설치
$ mix local.rebar --force
# Phoenix 설치
$ mix archive.install hex phx_new
먼저 Phoenix 프레임워크를 사용해 프로젝트를 생성해 보겠습니다.
--no-ecto
: 기본 생성되는 코드는 DB에 연결하도록 되어 있지만, 여기서는 편의상 DB 연결을 하지 않도록 코드를 생성하기 위해서 이 옵션을 사용합니다. DB 연결이 필요한 경우 이 옵션을 사용하지 않으면 됩니다.--umbrella
: 엄브렐러 프로젝트 구조를 사용합니다. 프로젝트가 커질수록 비즈니스 로직을 담당하는 애플리케이션과과 웹 애플리케이션을 분리하는 것이 좋습니다. 나중에 여러 애플리케이션을 하나로 합치는 것은 쉽지만, 하나의 애플리케이션을 여러 애플리케이션으로 분리하는 것은 힘들기 때문에 처음부터 엄브렐러 프로젝트로 생성하는 것을 추천합니다.
여기서는 Phoenix v1.7.2 를 기준으로 생성했습니다.
다음과 같이 실행하면 hello_umbrella/
디렉터리에 hello
라는 이름의 프로젝트를 생성합니다.
$ mix phx.new hello --app hello --no-ecto --umbrella
생성된 프로젝트를 실행해보겠습니다.
$ mix phx.server
이제 http://localhost:4000 으로 접속하면 페이지가 뜨는 것을 확인할 수 있습니다.
애플리케이션을 IEx (Interactive Elixir)에서 실행할 수도 있습니다:
$ iex -S mix phx.server
디렉터리 구조
생성된 디렉터리 구조는 다음과 같습니다.
├── _build
├── apps
│ ├── hello
│ │ ├── lib
│ │ └── test
│ └── hello_web
│ ├── assets
│ ├── lib
│ ├── priv
│ └── test
├── assets
├── config
└── deps
_build
-mix
로 생성된 컴파일 아티팩트를 보관하는 디렉터리이며, 삭제하면 애플리케이션을 처음부터 다시 빌드해야 합니다.apps
- 엄브렐러 프로젝트에 속한 애플리케이션들이 저장됩니다.hello
- 모든 비즈니스 로직과 비즈니스 도메인을 호스팅하는 역할을 합니다. 일반적으로 직접 DB와 연결하며, MVC 아키텍처에서 모델에 해당합니다.hello_web
- 웹 애플리케이션을 통해 비즈니스 도메인을 외부에 노출하는 역할을 담당합니다. 여기에는 MVC의 뷰와 컨트롤러가 모두 들어 포함되어 있습니다.*/lib
- 애플리케이션 소스 코드가 있는 디렉터리입니다.*/test
- 모든 애플리케이션 테스트를 저장하며,lib
와 동일한 구조를 사용하는 것을 추천합니다.hello_web/assets
- FE 소스 코드(JS, CSS)를 보관하는 디렉터리이며, 이 소스는esbuild
에 의해 자동으로 번들로 제공됩니다. 이미지, 글꼴과 같은 정적 파일은priv/static
에 저장됩니다.hello_web/priv
- 프로덕션에 필요하지만 소스 코드에 직접 포함되지 않은 모든 리소스를 보관하는 디렉터리입니다. 일반적으로 DB 스크립트, i18n 파일, 이미지 등이 여기에 보관됩니다.assets
디렉터리에 있는 파일에서 생성된 파일들은 기본적으로priv/static/assets
에 저장됩니다.
config
- 프로젝트 설정을 저장하는 디렉터리입니다.config/config.exs
- 기본 설정 파일.config/dev.exs
,config/test.exs
,config/prod.exs
-config/config.exs
의 끝에서 블러오는 환경별 설정파일.config/runtime.exs
- 마지막으로 실행되며, 시크릿 및 기타 동적 설정을 읽기에 가장 좋은 곳입니다.
deps
- 모든 디펜던시들이 설치되는 디렉터리입니다. 모든 디펜던시 목록은mix.exs
파일의defp deps do
함수 블럭 안에 정의되어 있습니다. 이 디렉터리는.gitignore
에 추가되어 있어야 하며, 이 디렉토리를 삭제하면mix setup
명령을 사용해 다시 설치할 수 있습니다.
2. React.js 사용하기
phoenix v1.7 부터 webpack 대신 esbuild
와 tailwind
를 사용하도록 변경되었습니다.
esbuild
는 assets/js/app.js
파일을 빌드한 결과물을 priv/static/assets/app.js
파일로 저장합니다.
개발 환경에서는 esbuild
watcher가 이를 담당하며, 프로덕션 빌드에서는 mix assets.deply
를 실행하면 생성됩니다.
react 디펜던시 추가
리액트를 사용하리면 먼저 리액트 관련 디펜던시를 설치해야 합니다.
$ cd apps/hello_web/assets
$ pnpm add --save-prod --save-exact react react-dom
$ pnpm add --save-dev --save-exact @types/react @types/react-dom
React 컴포넌트
apps/hello_web/assets/js/greeter.tsx
파일에 다음과 같이 간단한 리액트 컴포넌트를 생성합니다.
import React from "react";
interface GreeterProps {
name: string;
}
const Greeter = (props: GreeterProps) => {
return (
<div>
<h1>Hello {props.name}!</h1>
<h1>Peace of mind with React.js and TypeScript.</h1>
</div>
);
};
export default Greeter;
리액트를 사용하려면 리액트 컴포넌트를 마운트하기 위한 컨테이너가 필요합니다.
apps/hello_web/lib/hello_web/components/layouts/root.html.heex
파일을 열고 <body>
부분에 greeting
id 를 가진 <div>
엘리먼트를 추가합니다.
<body>
<div id="greeting" />
</body>
마지막으로 컨테이너에 리액트 컴포넌트를 렌더링하도록 설정합니다.
apps/hello_web/assets/js/app.js
파일을 app.tsx
파일로 이름을 변경한 다음, 마지막에 다음과 같이 추가합니다.
import React from "react";
import { createRoot } from "react-dom/client";
import Greeter from "./greeter";
const container = document.getElementById("greeting");
if (container) {
const root = createRoot(container);
root.render(<Greeter name="Phoenix" />);
}
app.js
파일의 이름을 변경했기 때문에, 이를 참조하고 있는 esbuild
설정도 변경해야 합니다.
config/config.exs
파일을 열고 # Configure esbuild
섹션을 찾아서 아래 라인을 변경합니다.
# 변경 전
~w(js/app.js --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
# 변경 후
~w(js/app.tsx --bundle --target=es2017 --outdir=../priv/static/assets --external:/fonts/* --external:/images/*),
마지막으로 개발 서버를 실행해 리액트 컴포넌트가 표시되는지, 파일 변경시 워치 모드가 잘 작동하는지 확인합니다.
$ mix phx.server
3. 배포용 도커 이미지 생성
Phoenix 애플리케이션 배포 준비는 크게 세 가지 단계가 있습니다.
- 애플리케이션 시크릿 처리
- 애플리케이션 asset 컴파일
- 프로덕션 환경으로 서버 시작하기
3.1 애플리케이션 시크릿 처리
DB 사용자 이름과 패스워드와 같은 시크릿 정보는 일반적으로 환경 변수에 보관하고, 애플리케이션에서 로드하는 것이 좋습니다.
이 작업은 환경 변수에서 시크릿 구성 및 설정을 로드하는 역할을 하는 config/runtime.exs
파일에서 수행됩니다.
따라서, 프로덕션 환경에서는 적절한 관련 변수가 설정되어 있어야 합니다.
# 시크릿 키 생성
$ mix phx.gen.secret
<생성된 시크릿 키>
$ export SECRET_KEY_BASE=<생성된 시크릿 키>
# DB 사용시 DB 연결 정보 설정
$ export DATABASE_URL=ecto://USER:PASS@HOST/database
좀 더 자세한 내용은 Handling of your application secrets 문서를 참고하세요.
다음 단계를 수행하기 전에 프로덕션 환경에 필요한 디펜던시를 설치하고 컴파일하는 작업을 먼저 수행해야 합니다.
$ mix deps.get --only prod
$ MIX_ENV=prod mix compile
3.2 애플리케이션 asset 컴파일
이 단계에서는 esbuild
를 사용해 JS, CSS 과 같은 애셋들을 컴파일하고 번들링하는 작업을 수행합니다.
이 모든 작업은 mix assets.deploy
태스크 안에 캡슐화되어 진행됩니다.
$ MIX_ENV=prod mix assets.deploy
위와 같이 실행하면 apps/hello_web/priv/static
디렉토리에 배포용 번들 파일들이 생성됩니다.
생성된 애셋 파일들은 .gitignore
에 추가되어 있지 않으며, 개발자가 실수로 커밋해서 푸시하는 경우가 발생할 수 있기 때문에 apps/hello_web/.gitignore
파일을 열고 다음과 같이 추가하는 것을 추천합니다.
/priv/static/favicon-*.ico
/priv/static/robots-*
/priv/static/robots.txt.gz
혹은 mix phx.digest.clean --all
을 실행하면 apps/hello_web/priv/static
에 생성된 파일들을 모두 삭제할 수 있습니다.
3.3 프로덕션 환경으로 서버 시작하기
프로덕션 환경에서 서버를 실행하려면 mix phx.server
를 호출할 때 PORT
, MIX_ENV
환경 변수를 설정해야 합니다:
$ PORT=4001 MIX_ENV=prod SECRET_KEY_BASE=$(mix phx.gen.secret) mix phx.server
http://localhost:4001 로 접속해 페이지가 잘 뜨는지 확인합니다.
3.4 도커 이미지 빌드
Phoenix 에서는 도커 이미지 빌드에 필요한 Dockerfile
을 자동으로 생성하는 기능을 제공합니다.
하지만, 1.7.2 버전 현재 엄브렐러 프로젝트를 지원하지 않고 있어서 몇 가지 수정 사항이 필요합니다.
-
apps/hello_web
디렉터리로 이동 후, 다음과 같이 실행하면Dockerfile
,.dockerignore
파일이 생성됩니다.$ cd apps/hello_web $ mix phx.gen.release --docker
-
실행시에는 엄브렐러 프로젝트 전체가 필요하므로 생성된 파일들을 엄브렐러 프로젝트 루트로 이동합니다.
$ mv Dockerfile .dockerignore ../../
-
엄브렐러 프로젝트는 여러개의 애플리케이션을 포함하고 있기 때문에, 애플리케이션 별 릴리즈 설정을 추가해야 합니다.
mix.exs
파일을 열고 project 정의 블럭안에releases
항목을 추가합니다.def project do [ apps_path: "apps", version: "0.1.0", start_permanent: Mix.env() == :prod, deps: deps(), aliases: aliases(), releases: [ hello_umbrella: [ applications: [ hello: :permanent, hello_web: :permanent ] ] ] ] end
-
config/runtime.exs
파일을 열고, 다음 항목을 찾아 주석을 해제합니다.config :hello_web, HelloWeb.Endpoint, server: true
-
프로덕션 배포용 릴리즈를 빌드합니다.
$ MIX_ENV=prod mix release
-
생성된
Dockerfile
은 루트 디렉토리에 있는mix.exs
,mix.lock
파일만 복사하고 있습니다. 엄브렐러 프로젝트에서는 각 애플리케이션의mix.exs
파일도 같이 복사해야 합니다.COPY apps/hello/mix.exs ./apps/hello/mix.exs COPY apps/hello_web/mix.exs ./apps/hello_web/mix.exs
-
표준 Phoenix 프로젝트와 달리 우리는 리액트를 사용하기 때문에,
npm
패키지가 설치되어 있어야 합니다.# npm 추가 RUN apt-get update -y && apt-get install -y build-essential git npm \ && apt-get clean && rm -f /var/lib/apt/lists/*_*
-
Dockerfile
은 루트 디렉토리에 있는priv
,assets
,lib
,rel
디렉토리를 복사하고 있기 때문에, 이를 변경해야 합니다. 또한, 빌드 전에 디펜던시 설치를 위해npm install
을 실행해야 합니다.# 변경 전: COPY priv priv COPY apps/hello_web/priv apps/hello_web/priv # 변경 전: COPY lib lib COPY apps/hello/lib apps/hello/lib COPY apps/hello_web/lib apps/hello_web/lib # 변경 전: COPY assets assets COPY apps/hello_web/assets apps/hello_web/assets # 추가 RUN cd apps/hello_web/assets && npm install # 변경 전: RUN mix assets.deploy RUN cd apps/hello_web && mix assets.deploy # 변경 전: COPY rel rel COPY apps/hello_web/rel apps/hello_web/rel
-
Dockerfile
에서 릴리즈된 파일들의 디렉토리 이름이hello_web
으로 되어 있는데, 실제로는hello_umbrella
로 생성되므로 이를 변경합니다.# COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/hello_web ./ COPY --from=builder --chown=nobody:root /app/_build/${MIX_ENV}/rel/hello_umbrella ./
-
도커 컨테이너 실행시 런타임에 실행 인자를 전달할 수 있도록 하려면,
CMD
대신ENTRYPOINT
를 사용하도록 변경합니다.# CMD ["/app/bin/server"] ENTRYPOINT ["/app/bin/hello_umbrella"]
-
.dockerignore
파일에서 컴파일된 애셋 경로들을 변경합니다./apps/hello_web/assets/node_modules/ /apps/hello_web/priv/static/assets/ /apps/hello_web/priv/static/cache_manifest.json
-
마지막으로 도커 이미지를 생성합니다.
$ docker build -t hello .
3.5 도커 이미지 실행
실행시 SECRET_KEY_BASE
환경 변수를 주입해서 실행합니다.
$ docker run --rm -e SECRET_KEY_BASE=$(mix phx.gen.secret) -p 4002:4000 hello start
로컬에서는 mix
가 설치되어 있어서 위와 같이 시크릿 키를 생성했지만, 실제 프로덕션 환경에서는 시크릿 키를 별도의 위치(예: k8s secret)에서
불러와 사용하는 경우가 일반적입니다.
http://localhost:4002 로 접속해 페이지가 잘 뜨는지 확인합니다.