AWS AmplifyをIonic 4で使ってみる

2019.02.01

あらまし

AWSのマネージドサービスをうまく活用することで、高い可用性を持ったアプリケーションを開発できる。2019年はスマホアプリ・PWAのバックエンドにAWSのマネージドサービス群を本格的に導入していきたいので、改めて丁寧に学び直すことにした。

フロントエンドアプリケーションの開発環境でAWS上にバックエンドを構築できるJavaScriptフレームワーク「AWS Amplify」を利用してバックエンドを構築していく。

フロントエンドのJavaScriptフレームワークはAngular 7を使い、UIフレームワークとして先日正式リリースされたIonic 4を使うことにした。

目指せ1ヶ月連続毎日1記事執筆!

開発環境

  • macOS Mojave 10.14.2(18C54)
  • iTerm
  • WebStorm
  • ndenv 0.4.0-4-ga339097
  • Node.js v10.4.1
  • @ionic/cli@4.10.1
  • @aws-amplify/cli@0.1.44

必要なnpmパッケージをグローバルインストール



# Node.jsのバージョンを指定
$ ndenv local v10.4.1

# Ionic CLIをインストール
$ npm install -g ionic

# Amplify CLIをインストール
$ npm install -g @aws-amplify/cli

# rehash
$ ndenv rehash

Ionicプロジェクトを構築する



# Ionicプロジェクトをスタート
$ ionic start AwsAmplifyIonicSample

対話形式でIonicプロジェクトの設定を行う。



# テンプレートを選択:今回は「blank」を利用する
? Starter template: (Use arrow keys)
❯ blank    | A blank starter project
  sidemenu | A starting project with a side menu with navigation in the content area
  tabs     | A starting project with a simple tabbed interface

# Appflowを使うか選択:今回はAppflowは利用しない
? Install the free Ionic Appflow SDK and connect your app? (Y/n) n

ひとまずIonicアプリを起動してみる



# プロジェクトのディレクトリ
$ cd AwsAmplifyIonicSample

# 開発用HTTPサーバーを起動
$ ionic serve

自動的にブラウザでhttp://localhost:8100が開く。Google ChromeのデベロッパーツールでiPhone 6/7/8モードにして確認してみる。

AWS AmplifyをIonic 4で使ってみる

AWS Amplifyをプロジェクトに追加する

いよいよアプリケーションをAWS Amplifyで拡張していく。



$ amplify init

対話形式で初期設定を行う。



# プロジェクト名を指定
? Enter a name for the project AmplifySample

# 利用するエディタを指定
? Choose your default editor:
  Sublime Text
  Visual Studio Code
  Atom Editor
  IDEA 14 CE
  Vim (via Terminal, Mac OS only)
  Emacs (via Terminal, Mac OS only)
❯ None

# 開発するアプリケーションのプラットフォームを指定
? Choose the type of app that you're building (Use arrow keys)
  android
  ios
❯ javascript

# 利用するJavaScriptフレームワークがあれば指定
? What javascript framework are you using
  angular
  ember
❯ ionic
  react
  react-native
  vue
  none

# ソースコードのディレクトリを指定
? Source Directory Path:  (src)

# 成果物が出来上がるディレクトリを指定
? Distribution Directory Path: (www)

# ビルドコマンドを指定
? Build Command:  (npm run-script build)

# 開発用HTTPサーバー起動コマンドを指定
? Start Command: (ionic serve)

# AWSにアクセスするための認証情報を既存のプロファイルから選んで利用するか指定
? Do you want to use an AWS profile? (Y/n)

以上でAmplify Consoleの初期設定が完了した。Ionic 4では、CLIでプロジェクトを作成した時点でGit管理が開始されており、初回のコミットが行われている。せっかくなのでGitを使って差分を確認してみた。



$ git add .
$ git status

On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .amplifyrc
	new file:   amplify/#current-cloud-backend/amplify-meta.json
	new file:   amplify/.config/aws-info.json
	new file:   amplify/.config/project-config.json
	new file:   amplify/backend/amplify-meta.json

Amazon Cognitoを使ってユーザー認証を行うための設定を行う

AWS AmplifyではAmazon Cognitoを利用したユーザー認証が提供されている。$ amplify addコマンドを使うことで数ステップでAmazon Cognitoをセットアップすることができる。セットアップは$ amplify pushコマンドを実行することでクラウド上に実際に構築される(次のAWS AppSyncをセットアップした後に実行する)。



$ amplify add auth

対話形式で初期設定を行う。デフォルトの設定も用意されているので、今回はそれを利用してみる。デフォルトの設定を利用する場合、特に追加で設定する項目はなかった。



 Do you want to use the default authentication and security configuration? (Use arrow keys)
❯ Yes, use the default configuration.
  No, I will set up my own configuration.
  I want to learn more.

これでAmazon Cognitoの設定が準備できた。

AWS AppSyncを使ってDynamoDBをデータソースとしたAPIを構築するための設定を行う



$ amplify add api

こちらも対話形式で設定を行う。



# APIのタイプを指定する
? Please select from one of the below mentioned services (Use arrow keys)
❯ GraphQL
  REST

# AWS AppSyncのAPI名を指定する
? Provide API name: (amplifysample)

# APIの認証方法を指定する
? Choose an authorization type for the API
  API key
❯ Amazon Cognito User Pool

# GraphQLスキーマを既に作ってある場合はそれを利用することもできる(今回はなし)
? Do you have an annotated GraphQL schema? (y/N)

# 案内に沿ってGraphQLスキーマを作成することができる
? Do you want a guided schema creation? (Y/n)

# 3パターンのGraphQLスキーマ例から選ぶ
? What best describes your project:
  Single object with fields (e.g., “Todo” with ID, name, description)
❯ One-to-many relationship (e.g., “Blogs” with “Posts” and “Comments”)
  Objects with fine-grained access control (e.g., a project management app with owner-based authorization)

# スキーマを編集することができる(今回はしない)
? Do you want to edit the schema now? (Y/n) n

これでAWS AppSyncの設定が準備できた。

バックエンドの状況を確認する

$ amplify addコマンドで設定したバックエンドの情報は$ amplify statusコマンドで確認することができる。Operationが「Create」になっているので、$ amplify pushするとAmazon CognitoのユーザープールとAWS AppSyncのAPIが作られる。



$ amplify status
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | cognitoXXXXXXXX | Create    | awscloudformation |
| Api      | amplifysample   | Create    | awscloudformation |

バックエンドの構築を実行する



$ amplify push

対話形式で最終確認やローカルのソース生成を行う。



# 最終確認が出る
| Category | Resource name   | Operation | Provider plugin   |
| -------- | --------------- | --------- | ----------------- |
| Auth     | cognitoXXXXXXXX | Create    | awscloudformation |
| Api      | amplifysample   | Create    | awscloudformation |
? Are you sure you want to continue? (Y/n) Y

# JavaScriptアプリケーションで利用するためのGraphQLのコードを自動生成するか指定する
? Do you want to generate code for your newly created GraphQL API (Y/n)

# コード生成の種類を指定する
? Choose the code generation language target
  angular
❯ typescript

# コードの生成先を指定する
? Enter the file name pattern of graphql queries, mutations and subscriptions (src/graphql/**/*.ts)

# 自動的に生成するコードの種類を指定する
? Do you want to generate/update all possible GraphQL operations - queries, mutations and subscriptions (Y/n)

# 生成された
? Enter the file name for the generated code (src/API.ts)

CloudFormation経由でバックエンドが設定されていく。 AWSのマネジメントコンソールを見ているとどんどんリソースが立ち上がっていくことがわかる。 $ amplify pushするとCloudFormation・DynamoDB・AppSync・Cognito・IAMロールが設定されるため、5分程度時間がかかる。

AWS Amplifyによって生成されたファイルを確認する

ここまで実行すると、アプリケーションのプロジェクトには以下のように大量のファイルが追加される。



$ git add .
$ git status
On branch master
Changes to be committed:
  (use "git reset HEAD <file>..." to unstage)

	new file:   .amplifyrc
	new file:   .graphqlconfig.yml
	new file:   amplify/#current-cloud-backend/amplify-meta.json
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Blog.posts.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Blog.posts.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Comment.post.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Comment.post.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createBlog.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createBlog.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createComment.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createComment.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createPost.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.createPost.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteComment.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deleteComment.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deletePost.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.deletePost.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateBlog.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateBlog.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateComment.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updateComment.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updatePost.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Mutation.updatePost.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.blog.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.blog.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.comments.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Post.comments.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getBlog.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getBlog.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getComment.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getComment.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getPost.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.getPost.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listBlogs.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listBlogs.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listComments.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listComments.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listPosts.request
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/resolvers/Query.listPosts.response
	new file:   amplify/#current-cloud-backend/api/amplifysample/build/schema.graphql
	new file:   amplify/#current-cloud-backend/api/amplifysample/cloudformation-template.json
	new file:   amplify/#current-cloud-backend/api/amplifysample/parameters.json
	new file:   amplify/#current-cloud-backend/api/amplifysample/schema.graphql
	new file:   amplify/#current-cloud-backend/auth/cognito1b62e7cb/cognito1b62e7cb-cloudformation-template.yml
	new file:   amplify/#current-cloud-backend/auth/cognito1b62e7cb/parameters.json
	new file:   amplify/.config/aws-info.json
	new file:   amplify/.config/project-config.json
	new file:   amplify/backend/amplify-meta.json
	new file:   amplify/backend/api/amplifysample/build/resolvers/Blog.posts.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Blog.posts.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Comment.post.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Comment.post.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createBlog.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createBlog.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createComment.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createComment.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createPost.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.createPost.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteBlog.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteComment.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deleteComment.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deletePost.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.deletePost.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updateBlog.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updateBlog.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updateComment.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updateComment.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updatePost.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Mutation.updatePost.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Post.blog.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Post.blog.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Post.comments.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Post.comments.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getBlog.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getBlog.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getComment.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getComment.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getPost.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.getPost.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listBlogs.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listBlogs.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listComments.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listComments.response
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listPosts.request
	new file:   amplify/backend/api/amplifysample/build/resolvers/Query.listPosts.response
	new file:   amplify/backend/api/amplifysample/build/schema.graphql
	new file:   amplify/backend/api/amplifysample/cloudformation-template.json
	new file:   amplify/backend/api/amplifysample/parameters.json
	new file:   amplify/backend/api/amplifysample/schema.graphql
	new file:   amplify/backend/auth/cognito1b62e7cb/cognito1b62e7cb-cloudformation-template.yml
	new file:   amplify/backend/auth/cognito1b62e7cb/parameters.json
	new file:   amplify/backend/awscloudformation/nested-cloudformation-stack.yml
	new file:   src/API.ts
	new file:   src/aws-exports.js
	new file:   src/graphql/mutations.ts
	new file:   src/graphql/queries.ts
	new file:   src/graphql/schema.json
	new file:   src/graphql/subscriptions.ts

量があるのはAWS AppSyncのリゾルバー(amplify/backend/api/amplifysample/build/resolvers/*)。これらのリゾルバーを全て手作業で作成するとなると非常に骨が折れる。それらを一括で作成して、プロジェクトの一部としてGitリポジトリに含めることができるのはありがたい。

IonicアプリでAmplifyを使うためのパッケージをインストールする



$ npm install --save aws-amplify aws-amplify-angular ionic-angular @types/node

Angular 7でAWS Amplifyを利用するための調整を行う

例によって$ ionic serveでローカルサーバーを起動するとエラーが多発して利用できない。

さらに、src/index.htmlに以下のような変更を行なった。



<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="utf-8" />
  <title>Ionic App</title>

  <base href="/" />

  <meta name="viewport" content="viewport-fit=cover, width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no" />
  <meta name="format-detection" content="telephone=no" />
  <meta name="msapplication-tap-highlight" content="no" />

  <link rel="icon" type="image/png" href="assets/icon/favicon.png" />

  <!-- add to homescreen for ios -->
  <meta name="apple-mobile-web-app-capable" content="yes" />
  <meta name="apple-mobile-web-app-status-bar-style" content="black" />

  <!-- ここから追加 -->
  <script>
    if (global === undefined) {
      var global = window;
    }
  </script>
  <!-- ここまで追加 -->
</head>

<body>
  <app-root></app-root>
</body>

</html>

IonicアプリでAmplifyを利用できるようにモジュールを追加する

src/app/app.module.tsに以下のような変更を行う。



import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { RouteReuseStrategy } from '@angular/router';

import { IonicModule, IonicRouteStrategy } from '@ionic/angular';
import { SplashScreen } from '@ionic-native/splash-screen/ngx';
import { StatusBar } from '@ionic-native/status-bar/ngx';

import { AppComponent } from './app.component';
import { AppRoutingModule } from './app-routing.module';

import { AmplifyAngularModule, AmplifyService } from 'aws-amplify-angular'; // この行を追加

@NgModule({
  declarations: [AppComponent],
  entryComponents: [],
  imports: [
    BrowserModule,
    IonicModule.forRoot(),
    AppRoutingModule,
    AmplifyAngularModule,  // この行を追加
  ],
  providers: [
    StatusBar,
    SplashScreen,
    AmplifyService,        // この行を追加
    { provide: RouteReuseStrategy, useClass: IonicRouteStrategy }
  ],
  bootstrap: [AppComponent]
})
export class AppModule {}

IonicアプリでAmplifyの設定情報を読み込む

src/main.tsにAWS Amplifyの初期化処理を行うコードを追加する。



import { enableProdMode } from '@angular/core';
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';

import { AppModule } from './app/app.module';
import { environment } from './environments/environment';

// ---------- ここから追加  ----------
import Amplify from 'aws-amplify';
import amplify from './aws-exports';
Amplify.configure(amplify);
// ---------- ここまで追加  ----------

// ついでにAmplifyのログレベルをDEBUGにしてわかりやすくする
(<any>window).LOG_LEVEL = 'DEBUG';

if (environment.production) {
  enableProdMode();
}

platformBrowserDynamic().bootstrapModule(AppModule)
  .catch(err => console.log(err));

ここまで準備して$ ionic serveコマンドを実行すると、Chromeのデベロッパーツールのコンソールに以下のようなログが出た。これで無事にAWS Amplifyの機能を初期化することができた。

AWS AmplifyをIonic 4で使ってみる

AWS Amplifyで用意されているログイン認証コンポーネントを配置して試してみる

$ ionic startコマンドで自動的に生成されるHomePageコンポーネントにAWS Amplifyで提供される<amplify-authenticator>コンポーネントを配置してみる。



<ion-header>
  <ion-toolbar>
    <ion-title>
      Ionic Blank
    </ion-title>
  </ion-toolbar>
</ion-header>

<ion-content padding>
  <amplify-authenticator framework="Ionic"></amplify-authenticator>
</ion-content>

Ionic 4ではデフォルトで生成されるHomePageコンポーネントはHomePageModuleとしてメインのAppModuleから分けられているため、HomePageModuleでも必要なモジュールを読み込む必要がある。



import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { IonicModule } from '@ionic/angular';
import { FormsModule } from '@angular/forms';
import { RouterModule } from '@angular/router';

import { HomePage } from './home.page';
import { AmplifyAngularModule, AmplifyIonicModule } from 'aws-amplify-angular'; // この行を追加

@NgModule({
  imports: [
    CommonModule,
    FormsModule,
    IonicModule,
    RouterModule.forChild([
      {
        path: '',
        component: HomePage
      }
    ]),
    AmplifyIonicModule,    // この行を追加
    AmplifyAngularModule,  // この行を追加
  ],
  declarations: [HomePage]
})
export class HomePageModule {}

この状態で$ ionic serveを実行すると以下のようなログインフォームが表示される。

AWS AmplifyをIonic 4で使ってみる

これだけで、

  • ログイン
  • 新規登録
  • パスワードリマインダー
  • ログアウト

の画面を一気に作ることができる。

明日の目標

明日はこれをベースとして、Ionic 4で複数の画面を追加する。AppModule以外のモジュールで毎度AmplifyIonicModuleやAmplifyAngularModuleを読み込まなくても済むように共通モジュールも作成したい。