PR

【AWS】LambdaとDynamoDBでサーバーレスWebアプリを構築!AWSの設定から実際のコードまで解説!

記事内に広告が含まれています。
SNSフォローボタン
z_a_k_iをフォローする

この記事では、LambdaとDynamoDBを使ったサーバレスWebアプリの作成方法を解説します。

こんな悩みをお持ちの方にオススメ!
  • サーバーレスサービスを使ったWebアプリの作成方法がわからない
  • サーバーレスサービスのWebアプリのプログラムのイメージが知りたい
  • AWSサービスの設定のイメージが知りたい

AWSなどのサーバーレスサービスは、サーバーやミドルウェアのインストールが不要で、すぐにプログラム開発、公開できて便利ですよね。

一方で、サーバーレスサービスでのWebアプリの構築経験がないと、どのような手順で開発したら良いかわかりません

わたしもサーバーレスサービスを使って開発を経験するまで、開発のイメージがわからず途方にくれていた時期がありました。

そこでこの記事では、サーバーレスサービスを使ってWebアプリを構築する手順をまとめました。

LambdaとDynamoDBといったAWSのサーバーレスサービスを利用したWebアプリの構築する手順を解説しています。

この記事を読むことで、サーバーレスサービス上で動作させるプログラムやAWSサービスの設定方法がわかります

これからAWSのサーバーレスサービスでWebアプリを開発する方の役立つ内容となってます。

ぜひ最後までご覧ください。

本記事は、AWS入門者向けにサーバーレスサービスを使ったWebアプリの構築方法を解説しています。作成したWebアプリに認証機能を設定する手順(Cognitoの設定)は解説していません。

作成するWebアプリ

本記事では、画面上からデータを登録・更新するWebアプリを紹介します。

画面のイメージはこちらです。

機能について簡単に解説します。

データ検索機能

データベースからデータを検索する機能です。

「名前」でデータを検索できます。検索のトリガーとなるボタンは用意せず、エンターキーで検索処理が実行されます。

検索結果に条件に合致したデータが表示されます。

データ更新機能

データを更新する機能です。

検索結果に表示されたデータを更新します。

検索結果の各行の「更新」ボタンをクリックすることで行単位でデータを更新することが可能です。

データ登録機能

新しくデータを登録する機能です。

最下部のデータ登録エリアにある行でデータを登録できます。

データ登録後、登録ボタンの横に処理結果が表示されます。

システム全体構成図

システムの全体構成図はこちらです。

順番に解説していきます。

S3

AWSのストレージサービスです。

Reactで作成されたフロントエンドアプリケーションを格納します。

利用者はS3のURLにアクセスすることでアプリケーションを利用できます。

詳細は、AWS公式サイト「AWS S3」を参照ください。

CloudFront

HTML、JavaScriptなどのフロントエンドアプリケーションを外部に公開するサービスです。

今回は、S3に格納されたReactで作成されたフロントエンドアプリケーションを配信します。

S3にも外部に公開する機能はありますが、HTTPS通信での配信ができないなどの制約があります。

そのため、実際の開発の現場ではS3単体では利用されず、CloudFrontでの配信が利用されます。

詳細は、AWS公式サイト「Amazon CloudFront」を参照ください。

Lambda

プログラムを実行するAWSのサーバーレスサービスです。

フロントエンドアプリケーションから呼び出されるバックエンドの処理を実行します。

バックエンドとは

利用者のパソコンで実行されるのではなく、サーバー上で実行される処理です。

バックエンドアプリケーションとも呼びます。

フロントエンドアプリケーションなど、セキュリティ的にクライアントから直接アクセスさせたくないリソースからデータを取得する処理などを行います。

例えば、DataBaseやS3といったファイルストレージからデータを取得します。

詳細は、公式サイト「AWS Lambdaとは」を参照ください、。

API Gateway

WebAPIを外部に公開するサービスです。

Lambdaで作成したプログラムをWebAPIとして外部公開するために利用します。

フロントエンドアプリケーションからAPIGatewayで公開されたWebAPIを呼び出し、DynamoDBからデータを取得します。

詳細は、AWS公式サイト「AWS API Gateway」を参照ください。

DynamoDB

AWSが提供しているNoSQL型のデータベースサービスです。アプリケーションで扱うデータを保存します。

AWSでは、RDBMS型のRDSというサービスもありますが、今回はLambdaと相性のよいDynamoDBを利用します。

詳細は、AWS公式サイト「AWS DynamoDB」を参照ください。

開発手順(バックエンド処理)

以下の順番でバックエンド処理を作成していきます。

  • DynamoDBにテーブル作成
  • Lambdaの作成
  • API Gatewayの設定(Lambdaの公開)

DynamoDBにテーブル作成

マネジメントコンソール上でデータを保管するテーブルを作成します。

DynamoDBのメニューから「テーブルの作成」ボタンをクリックします。

DynamoDBのテーブルにはキーとなるパーティションキーを設定する必要があります。

今回はテーブル名に「user-table」、パーティションキーに「id」を指定します。

DynamoDBはNoSQL型のデータベースであるため、パーティションキー以外の項目は自由に登録できます。

DynamoDBのモードとして、読み書き数を事前に設定する「プロビジョンド」と読み書き数を事前に設定しない「オンデマンド」というモードがあります。

今回は検証の間しか読み書きしないため、「オンデマンド」で作成します。

その他の項目はデフォルトのまま、「テーブルの作成」ボタンをクリックします。

以下のような結果になればテーブルの作成が成功してます。

作成したテーブルにデータを追加しておきます。

マネジメントコンソールからデータを追加します。作成したテーブルを選択し、「項目を作成」ボタンをクリックします。

今回はデータが少ないので、マネジメントコンソール上で以下のようにデータを作成していきます。

Lambdaの作成

ていきマネジメントコンソールでLambdaを作成します。

関数名は「updateUserData」、言語は「Python」で作成していきます。

プログラムの作成

Lambdaのプログラムはこちらです。AWSコンソール上で貼り付けます。

import boto3
from boto3.dynamodb.conditions import Key

# DynamoDBリソースの初期化
dynamodb = boto3.resource('dynamodb')

def lambda_handler(event, context):
    # リクエストパラメータの取得
    parameters = event['queryStringParameters']
    user_id = parameters['id']
    user_name = parameters['name']
    user_age = parameters['age']
    user_email = parameters['email']
    
    # user-tableへの参照
    table = dynamodb.Table('user-table')
    
    # DynamoDBにデータが存在するか確認
    response = table.get_item(Key={'id': user_id})
    
    # データが存在する場合は更新、存在しない場合は新規登録
    if 'Item' in response:
        # データの更新
        table.update_item(
            Key={'id': user_id},
            UpdateExpression='SET #name = :name, #age = :age, #email = :email',
            ExpressionAttributeNames={
                '#name': 'name',
                '#age': 'age',
                '#email': 'email'
            },
            ExpressionAttributeValues={
                ':name': user_name,
                ':age': user_age,
                ':email': user_email
            }
        )
    else:
        # 新規データの登録
        table.put_item(
            Item={
                'id': user_id,
                'name': user_name,
                'age': user_age,
                'email': user_email
            }
        )
    
    return {
        'statusCode': 200,
        'body': 'データの登録または更新に成功しました。'
    }

なっています。内容は単純でデータが存在しない場合はデータ登録、存在する場合は 更新を行うソースコードに

登録データはリクエストパラメータでクライアントから受け渡された値を利用します。

ロールの設定

作成するLambdaにDynamoDBのテーブルの操作を許可するロールを付与します。

今回は検証のため、DBFullAccessの権限を設定します。実際は、必要最小限の権限のみ付与してください。

Lambdaが必要なリソースにアクセスできない場合、以下のように失敗します。

その場合は、ロールの設定が正しいか確認してください。

作成したLambdaの実行確認

Lambdaが正しく動作するか確認します。

「テスト」タブをクリックし、イベントJSONに以下を貼り付け実行してみます。

{
  "queryStringParameters": {
    "id": "A00010",
    "name": "テスト",
    "age": "22",
    "email": "aaa@eaaa.aaa"
  }
}

[テスト」ボタンをクリックし、結果が以下となれば成功です。

DynamoDBのテーブルを見ると、今回のテストで実行した「A00010」のデータがちゃんと登録されています。

バックエンド処理の公開(API Gatewayの設定)

作成したLambdaのコードを外部に公開するために、API Gatewayを設定していきます。

API Gatewayは大きな枠である「REST API」、その中にAPI単位で公開する「リソース」があります。

それぞれ作成していきます。

「REST API」の作成

マネジメントコンソールで API Gatewayのメニューに遷移し、「REST API」用のAPIを作成します。

API名を「aws-lambda-web-api」という名前を入力し、「APIを作成」ボタンをクリックします。

これで「REST API」が作成されました。

リソースの作成

次はLambdaと紐づくリソースを作成します。リソースはLambda単位で作成します。

マネジメントコンソール上で「リソースを作成」ボタンをクリックします。

「update-user-data」という名前でリソースを作成します。このとき「CORS」にチェックしてください。

メソッドタイプに「POST」を選択し、上で作成したLambdaを選択します。

設定後、画面下部にある「メソッドを作成」ボタンをクリックします。

次はCORSを有効化します。

対象のリソースを選択し、「CORSを有効化にする」ボタンをクリックします。

以下の設定で「保存」ボタンをクリックします。

CORS(クロスオリジンリソース共有)とは

異なるドメイン(https://xxxxxxx←「xxxxx」の部分)からアクセスできるように許可する設定です。
サーバーレスサービスを使ってWebアプリを構築するときはオンにする必要があります。

これでLambdaとAPI Gatewayが紐付き、インターネットを介して公開する準備ができました。

API Gatewayのデプロイ(インターネットへの公開)

「APIをデプロイ」ボタンをクリックし、作成したAPIを公開します。

公開するステージは「test」としてます。

これで、作成したLambdaがAPI Gatewayを経由してインターネットへ公開されました。

開発手順(フロントエンドアプリ)

以下の順番でフロントエンドアプリを開発していきます。

  • フロントエンドアプリ(React)の作成
  • S3、CloudFrontの設定(フロントエンドアプリの公開)

フロントエンドアプリ(React)の作成

ここまでで作成したLambdaを呼び出してデータを画面に表示するフロントエンドアプリケーションを作成します。

今回はReactで作成します。コードはこちらです。(APIのURLは書き換えてください。)

import React, { useState } from 'react';
import axios from 'axios';
import './App.css';

function App() {
  const [name, setName] = useState('');
  const [results, setResults] = useState([]);
  const [updateStatus, setUpdateStatus] = useState({});
  const [registStatus, setRegistStatus] = useState('');

  const handleSearch = async () => {
    // 処理結果をクリア
    setUpdateStatus({});

    try {
      const response = await axios({
        method: 'post',
        url: 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/get-user-data',
        data: {
          queryStringParameters: {
            name: name
          }
        }
      });
      const usersData = JSON.parse(response.data.body);
      const editableUsersData = usersData.map(user => ({
        ...user,
        editId: user.id,
        editName: user.name,
        editAge: user.age,
        editEmail: user.email
      }));
      setResults(editableUsersData);
    } catch (error) {
      console.error('検索中にエラーが発生しました:', error);
    }
  };

  const handleEdit = (index, field, value) => {
    const updatedResults = [...results];
    updatedResults[index][field] = value;
    setResults(updatedResults);
  };

  // 更新ボタンの処理
  const handleUpdate = async (index) => {
    const user = results[index];
    try {
      const response = await axios({
        method: 'post',
        url: 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/update-user-data', // 実際のエンドポイントURLに置き換えてください
        data: {
          queryStringParameters: {
            id: user.editId,
            name: user.editName,
            age: user.editAge,
            email: user.editEmail
          }
        }
      });
      // 処理結果を状態にセット
      setUpdateStatus({ ...updateStatus, [index]: '処理が正常終了しました' });
    } catch (error) {
      // エラー処理
      setUpdateStatus({ ...updateStatus, [index]: 'エラーが発生しました' });
    }
  };

  // 新しいデータを登録するための状態
  const [newUser, setNewUser] = useState({
    newName: '',
    newAge: '',
    newEmail: ''
  });

  // 新しいデータの入力を処理する関数
  const handleNewUserChange = (field, value) => {
    setNewUser({ ...newUser, [field]: value });
  };

  // 新しいデータを登録する関数
  const handleRegister = async () => {
    setRegistStatus('');
    try {
      const response = await axios({
        method: 'post',
        url: 'https://xxxxxx.execute-api.ap-northeast-1.amazonaws.com/test/update-user-data', // 実際のエンドポイントURLに置き換えてください
        data: {
          queryStringParameters: {
            id: newUser.newId,
            name: newUser.newName,
            age: newUser.newAge,
            email: newUser.newEmail
          }
        }
      });
      // 登録成功時の処理
      console.log('データが正常に登録されました。', response);
      // 処理結果を状態にセット
      setRegistStatus('処理が正常終了しました');
    } catch (error) {
      // 登録失敗時のエラー処理
      console.error('データ登録中にエラーが発生しました。', error);
    }
  };

  return (
    <div>
      <div>
        <h2>検索条件</h2>
        <div>
          <label htmlFor="name">名前:</label>
          <input
            type="text"
            id="name"
            value={name}
            onChange={(e) => setName(e.target.value)}
            onKeyPress={(e) => e.key === 'Enter' && handleSearch()}
          />
        </div>
      </div>
      <div>
        <h2>検索結果</h2>
        <ul className="results-list">
        {results.map((user, index) => (
          <li key={index}>
            <label>ID:</label>
            <input
              type="text"
              value={user.editId}
              disabled
              readOnly="true"
              onChange={(e) => handleEdit(index, 'editId', e.target.value)}
            />
            <label>名前:</label>
            <input
              type="text"
              value={user.editName}
              onChange={(e) => handleEdit(index, 'editName', e.target.value)}
            />
            <label>年齢:</label>
            <input
              type="number"
              value={user.editAge}
              onChange={(e) => handleEdit(index, 'editAge', e.target.value)}
            />
            <label>メール:</label>
            <input
              type="email"
              value={user.editEmail}
              onChange={(e) => handleEdit(index, 'editEmail', e.target.value)}
            />
            <button onClick={() => handleUpdate(index)}>更新</button>
            <span>{updateStatus[index]}</span>
          </li>
        ))}
      </ul>
      </div>
      <h2>データ登録</h2>
      <div>
      <label>ID:</label>
        <input
          type="text"
          value={newUser.newId}
          onChange={(e) => handleNewUserChange('newId', e.target.value)}
        />
        <label>名前:</label>
        <input
          type="text"
          value={newUser.newName}
          onChange={(e) => handleNewUserChange('newName', e.target.value)}
        />
        <label>年齢:</label>
        <input
          type="number"
          value={newUser.newAge}
          onChange={(e) => handleNewUserChange('newAge', e.target.value)}
        />
        <label>メール:</label>
        <input
          type="email"
          value={newUser.newEmail}
          onChange={(e) => handleNewUserChange('newEmail', e.target.value)}
        />
        <button onClick={handleRegister}>登録</button>
        <span>{registStatus}</span>
      </div>
    </div>
  );
}

export default App;

npmコマンドでモジュールを作成しておきます。

npm build 

今回はReactのコードは詳しく解説しません。

コードの提示だけで終了となります。

フロントエンドアプリの公開(S3、CloudFrontの設定)

S3の作成、CloudFrontの設定は手順が多いため、別記事としています。

設定手順は、「【AWS】CloudFrontとS3を連携させてWebアプリを公開する」を参照ください。

作成したReactアプリケーションのモジュールをS3の任意のバケットにアップロードします。

設定が完了後、アプリケーションにアクセスできるURLが発行されます。

動作確認

CloudFrontから発行されたURLにブラウザでアクセスします。

画面が起動し、最初に説明した仕様通り、検索、登録、更新ができればWebアプリが完了です。

まとめ

この記事のまとめです。

  • サーバーレスでWebアプリを構築するには、複数のサービスを組み合わせる
  • バックエンドは、「Lambda」と「DynamoDB」、「API Gateway」を利用する
  • フロントエンド処理は、「CloudFront」と「S3」を利用する

本記事では、AWSサービスを使ってWebアプリを構築する方法を解説しました。

バックエンドプログラムは、「Lambda」と「DynamoDB」を利用して作成し、外部の公開に「API Gateway」を利用します。

フロントエンドプログラムは、「CloudFront」と「S3」を利用して、Reactなどで作成されたアプリケーションを外部公開します。

本記事で説明したとおり、サーバーレスサービスでWebアプリを構築するには、さまざまなサービスを組み合わせる必要があります。

最初は学習することが多く大変ですが、一度開発を経験してしまえば、その後は簡単にアプリケーションを開発することができます。

サーバーレスサービスの知識は、開発の生産性に直結しますので、ぜひ習得してみてください。

本記事が皆様の参考になりましたら幸いです。

参考書籍

コメント