React.js 앱을 서버리스로 만드는 방법

이 예제에서는 React.js서버리스 API를 사용해 간단한 클릭 카운터 앱을 만드는 방법을 살펴보겠습니다. SST와 SST의 StaticSite 구성을 사용해 앱을 AWS에 배포할 것입니다.

요구사항

SST 앱 만들기

Change indicator 먼저 SST 앱을 만들어 보겠습니다.

$ npx create-sst@latest --template=base/example react-app
$ cd react-app
$ npm install

기본적으로 앱은 AWS us-east-1 리전에 배포됩니다. 이 설정은 프로젝트 루트의 sst.config.ts 파일에서 변경할 수 있습니다.

import { SSTConfig } from "sst";

export default {
  config(_input) {
    return {
      name: "react-app",
      region: "us-east-1",
    };
  },
} satisfies SSTConfig;

프로젝트 구조

SST 앱은 몇 가지 부분으로 구성됩니다.

  1. stacks/ — 앱 인프라

    서버리스 앱의 인프라를 정의하는 코드는 프로젝트의 stacks/ 디렉토리에 위치합니다. SST는 AWS CDK를 사용하여 인프라를 생성합니다.

  2. packages/functions/ — 앱 코드

    API가 호출될 때 실행되는 코드는 프로젝트의 packages/functions/ 디렉토리에 위치합니다.

  3. packages/frontend/ — React 앱

    프론트엔드 React.js 앱의 코드입니다.

인프라 구축하기

우리 앱은 간단한 API와 React.js 앱으로 구성됩니다. API는 데이터베이스와 통신하여 클릭 횟수를 저장합니다. 데이터베이스부터 만들어 보겠습니다.

테이블 추가하기

Amazon DynamoDB를 사용할 예정입니다. 이는 신뢰할 수 있고 고성능의 NoSQL 데이터베이스로, 진정한 서버리스 데이터베이스로 구성할 수 있습니다. 즉, 자동으로 확장 및 축소되며, 사용하지 않을 때는 요금이 부과되지 않습니다.

Change indicator stacks/ExampleStack.ts를 다음 코드로 교체하세요.

import { Api, StaticSite, StackContext, Table } from "sst/constructs";

export function ExampleStack({ stack }: StackContext) {
  // 테이블 생성
  const table = new Table(stack, "Counter", {
    fields: {
      counter: "string",
    },
    primaryIndex: { partitionKey: "counter" },
  });
}

이 코드는 SST의 Table 구성을 사용하여 서버리스 DynamoDB 테이블을 생성합니다. counter라는 기본 키를 가지고 있습니다. 우리의 테이블은 다음과 같이 생겼을 것입니다:

counter tally
clicks 123

API 생성하기

이제 API를 추가해 보겠습니다.

Change indicator stacks/ExampleStack.ts 파일에서 Table 정의 아래에 다음 코드를 추가하세요.

// HTTP API 생성
const api = new Api(stack, "Api", {
  defaults: {
    function: {
      // 테이블 이름을 API에 바인딩
      bind: [table],
    },
  },
  routes: {
    "POST /": "packages/functions/src/lambda.handler",
  },
});

// 출력에 URL 표시
stack.addOutputs({
  ApiEndpoint: api.url,
});

여기서는 SST의 Api 구문을 사용해 API를 생성합니다. 이 API는 단일 엔드포인트(루트)를 가지고 있습니다. 이 엔드포인트에 POST 요청을 보내면 packages/functions/src/lambda.ts 파일에 있는 handler라는 Lambda 함수가 호출됩니다.

또한, 방금 생성한 테이블을 API에 바인딩합니다. 이를 통해 API가 테이블에 접근(읽기 및 쓰기)할 수 있게 됩니다.

React 앱 설정하기

React.js 앱을 AWS에 배포하기 위해 SST의 StaticSite 구성을 사용할 것입니다.

Change indicator stacks/ExampleStack.ts에서 다음 부분을 교체하세요:

// API 엔드포인트를 출력에 표시
stack.addOutputs({
  ApiEndpoint: api.url,
});

Change indicator 다음으로 교체하세요:

// React 앱 배포
const site = new StaticSite(stack, "ReactSite", {
  path: "packages/frontend",
  buildCommand: "npm run build",
  buildOutput: "build",
  environment: {
    REACT_APP_API_URL: api.url,
  },
});

// URL을 출력에 표시
stack.addOutputs({
  SiteUrl: site.url,
  ApiEndpoint: api.url,
});

이 구성은 React.js 앱이 위치한 디렉토리를 가리킵니다. 아직 앱을 만들지 않았지만, 지금은 packages/frontend 디렉토리를 가리키도록 설정합니다.

또한 API의 엔드포인트를 사용하여 빌드 시점 React 환경 변수 REACT_APP_API_URL를 설정합니다. StaticSite를 사용하면 프론트엔드에서 하드 코딩하지 않고도 백엔드에서 자동으로 환경 변수를 설정할 수 있습니다. 이에 대한 자세한 내용은 React 앱에서 서버리스 환경 변수 설정하기 챕터에서 확인할 수 있습니다.

선택적으로 커스텀 도메인을 설정할 수도 있습니다.

// React 앱 배포
const site = new StaticSite(stack, "ReactSite", {
  // ...
  customDomain: "www.my-react-app.com",
});

하지만 지금은 이 부분을 건너뛰겠습니다.

테이블에서 데이터 읽기

우리 API는 Lambda 함수로 동작합니다. 이 함수에서 DynamoDB 테이블에서 데이터를 읽어올 것입니다.

Change indicator packages/functions/src/lambda.ts 파일을 다음 코드로 교체하세요.

import { DynamoDB } from "aws-sdk";
import { Table } from "sst/node/table";

const dynamoDb = new DynamoDB.DocumentClient();

export async function handler() {
  const getParams = {
    // 환경 변수에서 테이블 이름을 가져옵니다.
    TableName: Table.Counter.tableName,
    // "clicks"라는 카운터가 있는 행을 가져옵니다.
    Key: {
      counter: "clicks",
    },
  };
  const results = await dynamoDb.get(getParams).promise();

  // 행이 존재하면 "tally" 컬럼의 값을 가져옵니다.
  let count = results.Item ? results.Item.tally : 0;

  return {
    statusCode: 200,
    body: count,
  };
}

DynamoDB 테이블에 get 요청을 보내고, counter 컬럼의 값이 clicks인 행의 값을 가져옵니다. 아직 이 컬럼에 값을 쓰지 않았기 때문에, 0을 반환합니다.

Change indicator packages/functions/ 폴더에서 aws-sdk 패키지를 설치하세요.

$ npm install aws-sdk

그리고 지금까지 작업한 내용을 테스트해 보세요.

개발 환경 시작하기

Change indicator SST는 Live Lambda Development 환경을 제공합니다. 이를 통해 여러분은 서버리스 앱을 실시간으로 작업할 수 있습니다.

$ npm run dev

이 명령어를 처음 실행하면 앱과 Live Lambda Development 환경을 지원하기 위한 디버그 스택을 배포하는 데 몇 분 정도 걸립니다.

===============
 앱 배포 중
===============

SST 앱 준비 중
소스 코드 변환 중
소스 코드 린팅 중
스택 배포 중
dev-react-app-ExampleStack: 배포 중...

 ✅  dev-react-app-ExampleStack


스택 dev-react-app-ExampleStack
  상태: 배포 완료
  출력:
    ApiEndpoint: https://51q98mf39e.execute-api.us-east-1.amazonaws.com
    SiteUrl: http://localhost:5173

ApiEndpoint는 방금 생성한 API입니다. SiteUrl은 React 앱이 로컬에서 실행될 주소입니다.

SST Console을 사용해 엔드포인트를 테스트해 보겠습니다. SST Console은 SST 앱을 관리할 수 있는 웹 기반 대시보드입니다. 문서에서 자세히 알아보세요.

API 탭으로 이동한 후 Send 버튼을 클릭해 POST 요청을 보냅니다.

API 탐색기를 사용하면 Api 구조체의 모든 라우트에 HTTP 요청을 보낼 수 있습니다. 헤더, 쿼리 파라미터, 요청 본문을 설정하고 응답과 함께 함수 로그를 확인할 수 있습니다.

API 탐색기 호출 응답

응답 본문에 0이 표시되는 것을 확인할 수 있습니다.

React 앱 설정하기

이제 방금 만든 API를 사용할 준비가 되었습니다. Create React App을 사용해 React.js 앱을 설정해 보겠습니다.

Change indicator 프로젝트 루트에서 다음 명령어를 실행하세요.

$ npx create-react-app packages/frontend --use-npm
$ cd frontend

이 명령어는 packages/frontend/ 디렉토리에 React 앱을 설정합니다. 이전 가이드에서 StaticSite 구성을 이 경로로 지정했던 것을 기억하세요.

Create React App은 Jest를 사용하는 리포지토리 내부에 설치될 경우 경고를 발생시킵니다. 이를 비활성화하려면 환경 변수를 설정해야 합니다.

Change indicator frontend/.env 파일에 다음을 추가하세요.

SKIP_PREFLIGHT_CHECK=true

또한 SST 앱의 환경 변수를 불러와야 합니다. 이를 위해 sst bind 커맨드를 사용할 것입니다.

Change indicator frontend/package.jsonstart 스크립트를 다음으로 교체하세요.

"start": "react-scripts start",

Change indicator 다음으로 교체합니다:

"start": "sst bind react-scripts start",

이제 React 개발 환경을 시작해 보겠습니다.

Change indicator packages/frontend/ 디렉토리에서 다음을 실행하세요.

$ npm run start

이 명령어는 브라우저에서 React.js 앱을 열어줄 것입니다.

클릭 버튼 추가하기

이제 앱의 UI를 추가하고 서버리스 API와 연결할 준비가 되었습니다.

Change indicator packages/frontend/src/App.js를 다음 코드로 교체하세요.

import { useState } from "react";
import "./App.css";

export default function App() {
  const [count, setCount] = useState(null);

  function onClick() {
    fetch(process.env.REACT_APP_API_URL, {
      method: "POST",
    })
      .then((response) => response.text())
      .then(setCount);
  }

  return (
    <div className="App">
      {count && <p>You clicked me {count} times.</p>}
      <button onClick={onClick}>Click Me!</button>
    </div>
  );
}

여기서는 클릭 시 API에 요청을 보내는 간단한 버튼을 추가했습니다. API 엔드포인트는 환경 변수 process.env.REACT_APP_API_URL에서 가져옵니다.

API의 응답은 앱의 상태에 저장됩니다. 이를 사용하여 버튼이 클릭된 횟수를 표시합니다.

이제 스타일을 추가해 보겠습니다.

Change indicator packages/frontend/src/App.css를 다음 코드로 교체하세요.

body,
html {
  height: 100%;
  display: grid;
}
#root {
  margin: auto;
}
.App {
  text-align: center;
}
p {
  margin-top: 0;
  font-size: 20px;
}
button {
  font-size: 48px;
}

이제 브라우저로 이동하면 React 앱이 다음과 같이 보일 것입니다.

React 앱의 클릭 카운터 UI

물론 버튼을 여러 번 클릭해도 카운트가 변경되지 않습니다. 이는 API에서 카운트를 업데이트하지 않기 때문입니다. 다음 단계에서 이를 처리할 것입니다.

변경 사항 적용하기

클릭 횟수로 테이블을 업데이트해 보겠습니다.

Change indicator packages/functions/src/lambda.ts 파일의 return 문 위에 다음 코드를 추가하세요.

const putParams = {
  TableName: Table.Counter.tableName,
  Key: {
    counter: "clicks",
  },
  // "tally" 컬럼 업데이트
  UpdateExpression: "SET tally = :count",
  ExpressionAttributeValues: {
    // 카운트 증가
    ":count": ++count,
  },
};
await dynamoDb.update(putParams).promise();

여기서는 clicks 행의 tally 컬럼을 증가된 카운트로 업데이트합니다.

이제 브라우저로 이동해 버튼을 다시 클릭하면 카운트가 증가하는 것을 확인할 수 있습니다!

React 앱에서 클릭 카운터 업데이트

또한 SST 콘솔의 DynamoDB 탭으로 이동해 테이블의 값이 업데이트되었는지 확인해 보세요.

참고로, DynamoDB 탐색기를 사용하면 앱의 Table 구성 요소에 있는 DynamoDB 테이블을 쿼리할 수 있습니다. 테이블을 스캔하거나 특정 키를 쿼리하고, 아이템을 생성 및 편집할 수 있습니다.

카운터 테이블의 DynamoDB 테이블 뷰

프로덕션 환경에 배포하기

Change indicator 마지막으로 앱을 프로덕션 환경에 배포해 보겠습니다.

$ npx sst deploy --stage prod

이 명령어를 사용하면 환경을 분리할 수 있습니다. 따라서 dev 환경에서 작업할 때 사용자에게 영향을 주지 않습니다.

배포가 완료되면 다음과 같은 결과를 확인할 수 있습니다.

 ✅  prod-react-app-ExampleStack


Stack prod-react-app-ExampleStack
  Status: deployed
  Outputs:
    ApiEndpoint: https://ck198mfop1.execute-api.us-east-1.amazonaws.com
    SiteUrl: https://d1wuzrecqjflrh.cloudfront.net

아래 명령어를 실행하여 prod 스테이지에서 SST 콘솔을 열고 프로덕션 엔드포인트를 테스트할 수 있습니다.

npx sst console --stage prod

API 탭으로 이동한 후 Send 버튼을 클릭하여 POST 요청을 보냅니다.

API explorer prod invocation response

브라우저에서 SiteUrl로 이동하면 새로운 React 앱이 동작하는 모습을 확인할 수 있습니다!

React app deployed to AWS

정리하기

마지막으로, 이 예제에서 생성한 리소스들을 다음 명령어로 제거할 수 있습니다.

$ npx sst remove
$ npx sst remove --stage prod

결론

이제 여러분은 React.js로 완전한 서버리스 클릭 카운터를 만들었습니다. 로컬 개발 환경에서 테스트하고 변경할 수 있으며, 프로덕션에도 배포되어 사용자와 공유할 수 있습니다. 아래 레포지토리에서 이 예제에 사용된 코드를 확인해 보세요. 궁금한 점이 있다면 댓글을 남겨 주세요!