🔖

AWS Amplifyを使った開発で役立つ逆引きTips集(2020年版)

13 min read

本記事は AWS Amplify Advent Calendar 2020 の2日目の記事です。

昨日、Qiitaに投稿した記事に対して、@jagaimogmogさんから「Amplifyに興味のある方に届けるべく、AWS Amplify Advent Calendar 2020にもクロスポストを」とのメッセージをいただきました。

しかし、残念ながらQiitaのAdvent CalendarでQiitaの記事はクロスポストできない仕様?のようなので、Zennに転載してクロスポストしています。

はじめに

68747470733a2f2f73332e616d617a6f6e6177732e636f6d2f6177732d6d6f62696c652d6875622d696d616765732f6177732d616d706c6966792d6c6f676f2e706e67.png

個人的に今年は「Amplify元年」といっても過言ではない年でした。Amplify SNS Workshop との出会いをきっかけに、AmplifyとNext.jsを組み合わせてモダンかつスピーディな開発ができないか模索するようになりました。

Amplifyを触るうちにさまざまなTipsを蓄積できたので、1年分まとめた逆引きTips集として公開することにしました。

※普段使用しているJavaScriptフレームワークがReact/Next.jsなので、記載内容もReact寄りです。ご了承くださいm(_ _)m

目次

  1. Auth
    1. サインアップ時に電話番号を不要としたい
  2. API(GraphQL)
    1. API.graphql()の結果をTypeScriptでいい感じに型解決できない
    2. Authでログインしたユーザ限定でAPIを実行できるようにしたい
    3. 項目をソートして取得したい
    4. schema.graphqlを更新しても諸々反映されない
    5. createdAt、updatedAtをスキーマから消したい
  3. Hosting
    1. 開発、ステージング、本番のインフラを分けたい
    2. hostingしたNext.jsのSPAが「403 Access Denied」となる
    3. 先にCI/CDを構築して後からauthやapiといったカテゴリのリソースを追加するとフロントエンドでエラーが発生する
  4. Function
    1. ランタイムにPythonを指定するとCI/CDがコケる
    2. 定期的にタスクを実行したい
  5. その他
    1. Amplifyの基本的な使い方をマスターしたい
    2. Categoryとして提供されていないAWSリソースを追加したい
    3. team-provider-info.jsonって何者?
    4. 上記で解決できない・よくわからない問題が起こっている
  6. まとめ

Auth

サインアップ時に電話番号を不要としたい

Amplify UI Componentsで作ったサインアップフォームは入力項目が多くて不便です。

amplify_2020_signup_form.png

コンポーネントに対してオプション指定することで、電話番号などの入力項目を減らせるようになっています。

amplify_2020_signup_form_without_phone.png

注意点として、Reactに対応したAmplify UI Componentsは2つ存在しています。

  • aws-amplify-react
  • @aws-amplify/ui-react

どちらを使うかによって事情が変わってくるため、それぞれ解説します。

aws-amplify-reactの場合

aws-amplify-react はLegacyなnpmパッケージです。

https://docs.amplify.aws/ui-legacy/q/framework/react

こちらのパッケージではReactのHOCとして、 withAuthenticator が提供されています。Reactコンポーネントをexportする際に、このHOCでラップすることでその画面を認証必須にすることが可能です。

export default withAuthenticator(App);

この引数でオプション指定することによって、サインアップフォームから電話番号を省略できます。

export default withAuthenticator(App, {
  signUpConfig: {
    hiddenDefaults: ['phone_number']
  }
});

@aws-amplify/ui-reactの場合

@aws-amplify/ui-react はLatestなnpmパッケージです。

https://docs.amplify.aws/ui/q/framework/react

Latestなので現在ではこちらを使用することが推奨されています。こちらのパッケージでも withAuthenticator が提供されています。しかし、 aws-amplify-react とは異なり signUpConfig をオプション指定することができないようです。

https://docs.amplify.aws/ui/auth/authenticator/q/framework/react

このため、HOCではなく AmplifyAuthenticator コンポーネントを使ってフォームをカスタマイズします。

<AmplifyAuthenticator usernameAlias="email">
  <AmplifySignUp slot="sign-up" formFields={[ { type: "email" }, { type: "password" } ]} />
  <AmplifySignIn slot="sign-in" />
  <AmplifySignOut />
  <Component {...pageProps} />
</AmplifyAuthenticator>

API(GraphQL)

API.graphql()の結果をTypeScriptでいい感じに型解決できない

Amplifyを使うと、バックエンドと通信するためのクライアント(GraphQLラッパー)を自動生成してくれるので便利です。もちろん、これはTypeScriptにも対応しています。

TypeScript初心者な私は、API.graphql()の戻り値が型推論されず、対処方法に悩みました、

import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api'
import { listDevices } from '../src/graphql/queries'

const asyncFunc = async () => {
  // これだと型推論が効かずエラーとなる
  const result = await API.graphql(graphqlOperation(listDevices))

  // 以降の処理...
}

いろいろ模索した結果、どうやら as を使って型を明示するのが正解のようです。

import API, { graphqlOperation, GraphQLResult } from '@aws-amplify/api'
import { listDevices } from '../src/graphql/queries'
import { ListDevicesQuery } from '../src/API'
const asyncFunc = async () => {
  const result = (await API.graphql(graphqlOperation(listDevices))) as GraphQLResult<ListDevicesQuery>

  // 以降の処理...
}

Authでログインしたユーザ限定でAPIを実行できるようにしたい

GraphQLスキーマにて @auth ディレクティブを指定することで、リクエスト時の権限チェックが可能です。

type Device @model
  @auth(rules: [{ allow: owner }]) {
  id: ID!
  name: String!
  logs: [Log] @connection(keyName: "byDevice", fields: ["id"])
}

type Log @model
  @auth(rules: [{ allow: owner }])
  @key(name: "byDevice", fields: ["deviceId", "timestamp"]) {
  id: ID!
  deviceId: ID!
  timestamp: AWSTimestamp!
  value: Float!
  device: Device @connection(fields: ["deviceId"])
}

上記では、allow: owner としていますが、所有者以外にも権限指定したり、readやcreate権限を部分的に付与することも可能です。詳しくはドキュメントを参照ください。

https://docs.amplify.aws/cli/graphql-transformer/auth

項目をソートして取得したい

Amplify SNS Workshopで解説されている通り、 @key ディレクティブを使ってDynamoDBにGSIを追加します。

type Post
  @model (subscriptions: { level: public })
  # パーティションキーをtype, ソートキーをtimestampに指定
  # typeには常に 'post' を格納することで、全Postをtimestampでソートして取得する
  @key(name: "SortByTimestamp", fields:["type", "timestamp"], queryField: "listPostsSortedByTimestamp")
{
  type: String!
  id: ID
  content: String!
  owner: String
  timestamp: AWSTimestamp!
}

こうしておくと、次のコードのようにソート指定をしたFetchが可能となります。

const res = await API.graphql(graphqlOperation(listPostsSortedByTimestamp, {
  type: "post", // パーティションキーとして 'post'(固定) を指定
  sortDirection: 'DESC' // 降順指定
}));

schema.graphqlを更新しても諸々反映されない

amplify add api を実行した時に、プロジェクト直下に schema.graphql を作成するケースが多いのではないでしょうか?

後々になって、テーブルを追加する必要が出てきたりディレクティブを追記したりする場合に、このファイルを更新して amplify update api しても反映されません。

なぜなら、 amplify/backend/api/{リソース名}/schema.graphql が実際に構築されたGraphQL APIのスキーマとして読み込まれているためです。更新するときはこちらを更新して、 amplify update api するようにしましょう。

schema_graphql_—_tatami-fm-music-library.png

createdAt、updatedAtをスキーマから消したい

DynamoDBにAppSync以外のリソースから値を書き込む場合など、createdAtupdatedAt が邪魔になるシチュエーションがあります。

そんなときは @model(timestamps: null) を指定することで、createdAtupdatedAt を省略できます。

type Program
  @model(timestamps: null) # createdAtとupdatedAtを削除
  @key(name: "byStation", fields: ["stationId", "startedAt"]) {
  id: ID!
  name: String!
  startedAt: AWSTimestamp!
  endAt: AWSTimestamp!
  stationId: ID!
  station: Station @connection(fields: ["stationId"])
}

Hosting

開発、ステージング、本番のインフラを分けたい

Amplifyで作成したプロジェクトは環境を複数作成して、切り替えることが可能です。環境は amplify env add することで追加できます。

AWS_Amplify_Console.png

Amplify SNS Workshop で構築手順が詳細に記載されていますので、こちらを参照ください。

hostingしたNext.jsのSPAが「403 Access Denied」となる

https _qiita-image-store.s3.ap-northeast-1.amazonaws.com_0_60996_1dc862a3-7cd8-e51b-0ad8-6b91c889fdfd.png

こちらの記事で解説しています。

AWS AmplifyでhostingしたNext.jsのSPAが「403 Access Denied」となったときの対処法

先にCI/CDを構築して後からauthやapiといったカテゴリのリソースを追加するとフロントエンドでエラーが発生する

CI/CD構築後にauthやapiといったバックエンドのリソースを追加すると、CI/CDでバックエンドリソースが構築されず、デプロイしたフロントエンド側でエラーが発生します。

推測ですが、Amplify hostingのCI/CDのビルド設定(amplify.yml)は設定時点で、バックエンドのリソースが存在するかどうかを自動判定して、記述を変更しているようです。先にCI/CDを設定すると、バックエンドリソースは不要と判断して、ビルドが省略されます。

Next.js+TypeScript+AWS Amplify+RecoilでToDoリストを作るに記載したとおり、amplify.ymlを本来の形に書き換えることで、エラーを回避できます。

Function

ランタイムにPythonを指定するとCI/CDがコケる

Amplify側で指定したPythonのバージョンと、ビルド時に使用しているAmazon Linux2のPythonバージョンが異なることが原因です。

AWS AmplifyでPythonのFunctionをCI/CDするとbuildに失敗する問題の対処方法に記載したとおり、amplify.yml を書き換えてPythonを再インストールすることで回避できます。

定期的にタスクを実行したい

amplify add function する時に質問される Do you want to invoke this function on a recurring schedule? という質問に Yes と答えることで、LambdaのトリガーにCloudWatchEventが指定されます。

$ amplify add function
Scanning for plugins...
Plugin scan successful
? Select which capability you want to add: Lambda function (serverless function)
? Provide a friendly name for your resource to be used as a label for this category in the project: test
? Provide the AWS Lambda function name: test
? Choose the runtime that you want to use: Python
Only one template found - using Hello World by default.
? Do you want to access other resources in this project from your Lambda function? No
? Do you want to invoke this function on a recurring schedule? Yes
? At which interval should the function be invoked: Minutes
? Enter the rate in minutes: 3
? Do you want to configure Lambda layers for this function? No
? Do you want to edit the local lambda function now? No
Successfully added resource test locally.

その他

Amplifyの基本的な使い方をマスターしたい

はじめてAWS Amplifyを使うのであれば、Amplify SNS Workshopを1周することをお勧めします。

一方、このWorkshopだとカバーできていない部分があったり、使用しているパッケージのバージョンが一部古かったりという問題もあるので、他のチュートリアルもお勧めします。watilde/awesome-aws-amplify-ja にて日本語で書かれたチュートリアル記事がまとめられているので参考にしてみてください。

Categoryとして提供されていないAWSリソースを追加したい

Amplifyとして提供しているカテゴリ(authやapi)だけでは、構築したいシステムの要件を満たせないことがあります。

そんなときには Custom CloudFormation stacks を定義することで、AWSのリソースを自由にAmplifyのプロジェクトに組み込むことができます。もちろん、他のカテゴリからパラメータを渡すこともできます。例えば次のスライドに書いているような、apiで作ったDynamoDBのテーブル名をCustom CloudFormation stacksに渡してDynamoDBに値を書き込むIoT CoreのACTを定義する、といった使い方ができます。

https://speakerdeck.com/yuuu/aws-amplifytomockmockdeiotbatukuendowosupideinigou-zhu-suru?slide=25
image.png

team-provider-info.jsonって何者?

team-provider-info.json にはAmplifyのプロジェクトで構築したリソースのARNなどが記載されています。IAMのSecret Access Keyなどが記載されているわけではないので、直接的にセキュリティリスクがある訳ではありませんが、AccountIdが記載されていることを踏まえるとあまりpublicにすべきではありません。

次のスライドにも記載されている通り、プライベートリポジトリの場合以外は.gitignoreに追加する ようにしましょう。

https://speakerdeck.com/jaguar_imo/amplify-cli-deep-dive-awsdevday-2020?slide=7

上記で解決できない・よくわからない問題が起こっている

残念ながら、Amplifyを使っていると上記に挙がっていない問題にぶつかることはあると思われます。自分自身は以下のような方法で対処するようにしています。

エラーメッセージを理解する

Amplifyに限った話ではないですが、Amplifyのエラーメッセージには問題の原因や対処方法が記載されています。まずはこれを読んで理解します。

内容があまり理解できなかったとしても「Amplify + エラーメッセージから抜き出したいくつかのキーワード」で検索すると、似たような現象がGitHub Issuesで見つかるケースが多いです。

Amplify CLI/Frameworkのアップグレードをする

Amplifyは開発が活発で、CLI/Frameworkが頻繁にバージョンアップされます。公式ドキュメントに書かれているような基本的な操作が上手くいかない場合は、CLI/Frameworkをバージョンアップすることで問題を解決できるかもしれません。

AmplifyのGitHub Issuesを検索する

先にも挙げた通り、AmplifyのGitHub Issuesにはさまざまな事例が挙がっています。大抵の問題はIssuesを読んで解決するケースが多いのでぜひ参照してください。

https://github.com/aws-amplify/amplify-js/issues

まとめ

記事を書いて、AWS Amplifyはバックエンドを気にせずフロントエンド開発に注力できる環境を提供してくれる嬉しいサービスだなと改めて思いました。加えて、バックエンド周りも細かいカスタマイズに対応できるようになっている点が好印象です。

2021年は弊社の業務でのAmplifyの導入事例を、もっと世の中に発信できるよう精進します。

Discussion

ログインするとコメントできます