概要
こんにちは、先日AWSのre:Inventで発表されたAWS Amplify DataStoreがめちゃくちゃ便利すぎて驚きを隠せない、都内でフロントエンドエンジニアをしていますかめぽんです。
今まで、VueやNuxtでの開発がメインでやってきておりましてReact、Nextでの開発経験が少なくまたTypescriptでなにか出来ないかなと探しておりました。
最近だとモバイル開発のためのAWSのサービスをインテグレートしたAWS版firebaseのようなAmplifyというサービスが出ています。内容をみてみると、何やら爆速でサーバーレスアプリケーションが出来そうだなと感じたので、Next.js + AmplifyでTodoアプリを作ってみました。巷では、Nuxt.js + firebaseの組み合わせの記事がかなり多かったですが、こちらのアーキテクチャでも実装出来たのでその方法を体系的にまとめて見ました。
Next + Amplifyでの開発がなんとなくわかるようになると思うので、ぜひ最後まで読んでいただけると嬉しいです。
Next.jsとは
こちらはもはや説明不要かもしれませんが、ZEIT社が開発したサーバーサイドレンダリング対応のwebアプリケーションを構築できるReact製フレームワークです。
pages配下の自動ルーティングやダイナミックルート、SPA/SSRに始まり静的サイトジェネレートなアプリはもちろん、最近だとゼロコンフィグでTypescriptがそのまま使えたり、AMP対応、api
ディレクトリによるapiの実装などかなりDXがよくなってきています。もちろん導入も手軽にできるので開発スピードを格段に高めることが出来ます。
AWS Amplifyとは
AWS AmplifyはAWSのサービスを仕様したmBaasの一種で、webアプリケーション作成、設定、開発をかなり簡単にすることが出来、スケーラブルでもあるためサービスの規模に応じてオートスケールさせることも可能です。似たようなサービスではgoogle社のfirebaseがあります。バックエンドの資材を自分で準備しなくてもAmplifyのコマンドで必要なソースコードやモデルなどを準備してくれます。本来であれば、設定やプロビジョニング、分析などを全て自前で行っていかなければいけませんが、選択したもに関してAmplifyはそれらを管理してくれます。認証、オフラインデータ、解析、プッシュ通知、AR/VR、botなどを必要に応じてAWSサービスをアプリケーションに対してインテグレートします。
Next.js + Amplifyは何が嬉しいのか
初期開発スピードの爆速化
昨今では、マーケットの変化が非常に早くユーザーニーズも目まぐるしく変わっています。それに加えて、サービスやプロダクトにおける提供すべきUXも何が適切かわかりにくくなってきている中で、ビジネスサイドと開発サイドで共通認識として持っていなければならないのが仮説検証を高速に回し、フィードバックをUXに還元することです。DX時代とその未来における「ユーザーエクスペリエンス」についての基本を抑えるという記事を書かせていただいたのですが、サービスのローンチだけでなくそこに至る検証もスピード感を持って行うことが重要です。そこで、技術選定やアーキテクチャをどうするのかは悩む部分だと思いますが、いかに早く社会実装するかという観点で見るのそれ自体は早く解決すべき問題です。もちろん軽視すべき問題ではないというのが前提です。
そういった中で、Next.js + Amplifyの組み合わせはそれを解決することができると考えています。両者の環境構築で必要な時間は、独自で設計に応じてAWSサービスの選定をしたり構築することに比べても時間がかからずすぐにlocalhostなどで確認出来ますし、必要に応じてバックエンドサービスを提供してくれます。
適切な型やモデルを定義した上でのオートスケール
Next.jsではゼロコンフィグでTypescriptを動かせますし、AmplifyではAppSyncというサービスを含んでいてGraphqlでのデータのやりとりをします。そこで、必要なデータの型を定義してくれるのでフロントエンドとバックエンドで共通で型を使用することが容易です。
フレームワークが何かよりも最重要ビジネスルールは何かを抑える方が大事で、それを定義した上でオートスケールできるのでデータの保全性を担保しつつ希望に応じて対応することが出来ます。
Next.jsのセットアップ
早速Next.jsのセットアップを始めていきます。
ディレクトリ構成
最終的なディレクトリ構成は以下のようになっています。実際のディレクトリから必要な部分だけ掲載してますので、全ファイルを確認したい場合はGithubにコードを準備してますのでぜひ参考にしてみてください。amplify
とgraphql
ディレクトリに関しては以降のAWS Amplifyのセットアップ
にて自動生成されます。
├─ amplify
│ ├─ #current-cloud-backend
│ │ ├─ amplify-meta.json
│ │ ├─ api
│ │ │ └ todo
│ │ │ ├─ build
│ │ │ ├─ parameters.json
│ │ │ ├─ resolvers
│ │ │ ├─ schema.graphql
│ │ │ ├─ stacks
│ │ │ └─ transform.conf.json
│ │ └─ backend-config.json
│ ├─ backend
│ │ ├─ amplify-meta.json
│ │ ├─ api
│ │ │ └ todo
│ │ │ ├─ build
│ │ │ ├─ parameters.json
│ │ │ ├─ resolvers
│ │ │ ├─ schema.graphql
│ │ │ ├─ stacks
│ │ │ └─ transform.conf.json
│ │ ├─ awscloudformation
│ │ │ └ nested-cloudformation-stack.yml
│ │ └─ backend-config.json
│ └─ team-provider-info.json
├─graphql/
│ ├─queries.ts
│ ├─mutations.ts
│ ├─subscriptions.ts
│ └─schema.json
├─pages/
│ ├─index.tsx
│ └─todo.tsx
├─components/
│ └─templates/
│ ├─head.tsx
│ └─navigation.tsx
├─store/
├─aws-exports.js
├─package.json
├─.gitignore
├─next.config.js
├─next-env.d.ts
└─tsconfig.json
必要なモジュールのインストールとpackage.jsonの編集
作業ディレクトリが出来たら、以下コマンドで必要なモジュールの準備をしましょう。
npm install --save react react-dom next
npm install --save-dev @types/node @types/react
インストール出来きたら、package.jsonのscriptsを以下のように編集します。
"scripts": {
"dev": "next",
"build": "next build",
"start": "next start"
}
各種コンポーネントの準備
ここでは、共通で使うコンポーネントの定義をします。ナビゲーション用とhead部用のコンポーネントを準備します。
import * as React from 'react';
import Link from 'next/link'
const Navigation: React.FC = () => {
return (
<div>
<Link href="/">
<p>Index</p>
</Link>
<Link href="/about">
<p>About</p>
</Link>
<Link href="/todo">
<p>Todo</p>
</Link>
</div>
)
}
export default Navigation
head用のコンポーネントに関してはお好みで設定してみてください。
import * as React from 'react'
import Head from 'next/head'
import info from '../../package.json'
const defaultOGURL = ''
const defaultOGImage = ''
interface Props {
title: string,
description?: string,
url?: string,
ogImage?: string,
}
const head: React.FC<Props> = props => {
return (
<Head>
<meta charSet="UTF-8" />
<title>{props.title || ''}</title>
<meta name="description" content={props.description || info.description} />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta property="og:url" content={props.url || defaultOGURL} />
<meta property="og:title" content={props.title || ''} />
<meta
property="og:description"
content={props.description || info.description}
/>
<meta name="twitter:site" content={props.url || defaultOGURL} />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:image" content={props.ogImage || defaultOGImage} />
<meta property="og:image" content={props.ogImage || defaultOGImage} />
<meta property="og:image:width" content="1200" />
<meta property="og:image:height" content="630" />
<link rel="icon" sizes="192x192" href="/static/touch-icon.png" />
<link rel="apple-touch-icon" href="/static/touch-icon.png" />
<link rel="mask-icon" href="/static/favicon-mask.svg" color="#49B882" />
<link rel="icon" href="/static/favicon.ico" />
</Head>
)
};
export default head
pagesの準備
次にpagesコンポーネントの準備です。
import * as React from 'react';
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
const Index: React.FC = () => {
return (
<div>
<Head title="Index page" />
<Navigation />
<p>Hello world</p>
<p>Index</p>
</div>
)
}
export default Index
import * as React from "react";
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
const Todo = () => {
return (
<div>
<Head title="todo" />
<Navigation />
<h2>Todo with amplify</h2>
</div>
)
};
export default Todo;
この状態で npm run dev
のコマンドを実行し、http://localhost:3000/
にアクセスしてみましょう。以下のような画面にが表示されたら成功です。試しに/todo
にもアクセスしてみてtodoページが表示されるかもみてみましょう。
Next.jsの準備は一旦以上です。
AWS Amplifyのセットアップ
Amplifyのインストールと初期セットアップ
なにはともあれ、amplify/cliのグローバルインストールをします。
npm install -g @aws-amplify/cli
インストールが完了したら、amplify -v
でバージョンを確認しましょう。以下のような表記になっていたらインストール完了です。
Scanning for plugins...
Plugin scan successful
3.17.0
次にAWSアカウントの紐付けを行います。コンソールにて
amplify configure
とコマンドを打つとIAMユーザーを作成するためにブラウザが立ち上がります。アカウントがあればログイン、なければ新規作成を行いましょう。
accessKeyId
とsecretAccessKeyId
が順番に出るので、それをIAMユーザーの作成画面に貼ります。
画面を進めて行くとIAMユーザー作成画面側にアクセスキーIDが表示されるので、コンソール側に貼り付けてEnterを押します。Profile nameを決めた後、Enterを押しSuccessfully set up the new user.
のメッセージが出たら完了です。
Amplifyをプロジェクトで扱えるようにする
ここからは実際にNext.js上でamplifyを使っていく流れを説明していきます。
amplifyバックエンドの様々なサービスを扱えるようにするため、各種リソースをプロジェクトフォルダ内に作成します。
以下コマンドを打ってみましょう。
amplify init
以下のように、使用言語やフレームワーク、ディレクトリ情報などをインタラクティブ形式で進めていきます。
Initializing project in the cloud...
のメッセージが出るとバックエンドの資材を初期化&準備し始めるので待ちましょう。
Your project has been successfully initialized and connected to the cloud!
が出たら準備が整う合図になります。
バックエンドAPI(Graphql)の準備
次にAPIの準備をするために、以下コマンドを打ちます。
amplify add api
そうすると、プロジェクト内で扱うapiの種類や名前、スキーマの設定をインタラクティブに決めていきます。今回はGraphqlを使用していきます。
以下、質問例になります
GraphQL schema compiled successfully
のメッセージが出たら、Graphqlでのapi実行に必要なファイル等が自動生成されます。そのあと、schemaファイルに変更をかけたいならば自分で編集します。今回は以下の形式のスキーマにします。
type Todo @model {
id: ID!
description: String
isDone: Boolean
}
次に以下コマンドでデプロイをします。
amplify push
ここでも以下のようにインタラクティブに質問を進めていきます。
デプロイが完了すると、バックエンドのリソースが自動生成されます。
Next.jsとamplifyの結合
必要なバックエンドリソースが揃ったら、いよいよNext.jsの実装に入っていきます。
まずは必要なnpmモジュールをインストールします。
npm install --save @aws-amplify/api @aws-amplify/core @aws-amplify/pubsub
次にNext.jsのセットアップの時に作ったpages/todo.tsx
を編集します。
基本的にamplifyモジュールのインポートとconfigの処理をかけます。amplifyの処理を書きたいときは基本的にpagesで以下の処理をかけておきます。
import Amplify from '@aws-amplify/core';
import PubSub from '@aws-amplify/pubsub';
import API, { graphqlOperation } from '@aws-amplify/api';
import awsmobile from '../aws-exports';
Amplify.configure(awsmobile);
API.configure(awsmobile);
PubSub.configure(awsmobile);
次に使用したバックエンドapiを使えるようにします。graphqlで作っているので、そこからquery、mutations, subscriptionsをimportします。ここでは全てインポートしてますが、実際には使う分だけで大丈夫です。
import { createTodo, deleteTodo, updateTodo } from '../graphql/mutations';
import { getTodo, listTodos } from '../graphql/queries';
import { onCreateTodo, onUpdateTodo, onDeleteTodo } from '../graphql/subscriptions';
少しだけ説明すると、QueryとMutaionsは従来のCRUDに対応させると以下のようになります。
昨日 | CRUD | graphql |
---|---|---|
作成 | CREATE | Mutation |
取得 | READ | Query |
更新 | UPDATE | Mutation |
削除 | DELETE | Mutation |
加えてgraphqlではSubscription(購読)というものがあります。これは、端的にいうとサーバー側からのPushのようなものです。バックエンド側のデータに変更がかかった場合などに検知をして値を知らせてくれるものです。
Queryの使い方
export const getTodo = `query GetTodo($id: ID!) {
getTodo(id: $id) {
id
description
isDone
}
}
`;
importしたlistTodosのクエリを以下のようにAPI.graphql(graphqlOperation)
を使ってアクセスします。通信が成功するとdataに取得した値が入ってくるのでそれをpropsで渡したり、リストレンダリング用のローカルステートに渡してあげるとReact側でレンダリングすることが出来ます。
Todo.getInitialProps = async (props) => {
const data = await API.graphql(graphqlOperation(listTodos));
return {...props, ...data};
};
Mutationsの使い方
export const createTodo = `mutation CreateTodo($input: CreateTodoInput!) {
createTodo(input: $input) {
id
description
isDone
}
}
`;
こちらは登録用のファンクションです。API.graphql(graphqlOperation(createTodo, inputData));
にて第一引数にMutatio、で第二引数で登録するデータを入れます。
const submitTodo = async (list: Array<string>, todo: string) => {
const id = Math.floor(Math.random() * Math.floor(1000))
const inputData = {
input: {
id,
description: todo,
isDone: false
}
}
try {
await API.graphql(graphqlOperation(createTodo, inputData));
} catch (e) {
console.log(e);
}
};
Subscriptionの使い方
export const onCreateTodo = `subscription OnCreateTodo {
onCreateTodo {
id
description
isDone
}
}
`;
API.graphql(graphqlOperation(onCreateTodo)).subscribe({
next: e => {
// 購読する値の取得
const todo = e.value.data.onCreateTodo
// 値をセットする処理を書く
...
}
})
Todoアプリの実装
以下に今回の実装例を載せておきます。
import * as React from "react";
import { useState } from 'react';
import Amplify from '@aws-amplify/core';
import PubSub from '@aws-amplify/pubsub';
import API, { graphqlOperation } from '@aws-amplify/api';
import awsmobile from '../aws-exports';
import {
createTodo,
deleteTodo,
updateTodo
} from '../graphql/mutations';
import { getTodo, listTodos } from '../graphql/queries';
import { onCreateTodo, onUpdateTodo, onDeleteTodo } from '../graphql/subscriptions';
import Head from '../components/templates/head'
import Navigation from '../components/templates/navigation'
Amplify.configure(awsmobile);
API.configure(awsmobile);
PubSub.configure(awsmobile);
interface TodoType {
id: number,
description: string
isDone: boolean
}
interface DataProp {
data: {
listTodos?: {
items: Array<TodoType>
}
}
}
const Todo = (props: DataProp) => {
const { items: todoItems } = props.data.listTodos;
const [todo, setTodo] = useState('');
const [list, setList] = useState([]);
// 新規追加でTodoを追加する
const submitTodo = async (list: Array<string>, todo: string) => {
const id = Math.floor(Math.random() * Math.floor(1000))
const inputData = {
input: {
id,
description: todo,
isDone: false
}
}
try {
await API.graphql(graphqlOperation(createTodo, inputData));
} catch (e) {
console.log(e);
}
};
// 既存のTodoを削除する
const deleteItem = async (id) => {
const deleteData = {
input: {
id
}
}
try {
await API.graphql(graphqlOperation(deleteTodo, deleteData));
} catch (e) {
console.log(e);
}
};
return (
<div>
<Head title="todo" />
<Navigation />
<h2>Todo with amplify</h2>
<input style={{
border: 'solid 1px #ddd',
padding: 10,
borderRadius: 4,
fontSize: 18,
WebkitAppearance: 'none',
color: '#333'
}} value={todo} type="text" placeholder="please write todo" onChange={e => setTodo(e.target.value)} />
<button style={{
padding: 10,
background: '#F06292',
color: '#eee',
borderRadius: 4,
fontSize: 18,
WebkitAppearance: 'none'
}} onClick={() => submitTodo(list, todo)}>add Todo</button>
<ul className="ListContainer">{
todoItems.map( item => (
<li key={item.id} className="ListItem">
<span className="title">{item.description}</span>
<span>{item.isDone}</span>
<input type="button" value="delete" onClick={() => deleteItem(item.id)} />
</li>
))
}</ul>
</div>
)
};
Todo.getInitialProps = async (props) => {
const data = await API.graphql(graphqlOperation(listTodos));
try {
const client = API.graphql(graphqlOperation(onCreateTodo));
if ("subscribe" in client) {
client.subscribe({
next: e => {
console.log(e);
}
});
}
} catch (e) {
console.error(e);
}
return {...props, ...data};
};
export default Todo;
試しにあらかじめ以下のようにデータをセットしておきます。(このデータ自体はGraphqlでポストしておいたデータになります。)
その状態で、npm run dev
でサーバーを起動して/todo
にアクセスしてみてTodoが表示されていれば成功です。
まとめ&感想
ここまで最後ま目を通していただいてありがとうございます。
巷ではサーバーレスWebアプリケーション開発はおそらく非常に人気で、Nuxt.js + Firebaseの情報が非常に多く見受けられます。非常に便利で僕も好きな技術の一つですが、逆にNext.jsがあまり無くバージョンアップにより魅力的な機能が増えてきています。また、React製なのでVueに比べると 壊しやすい
と感じていまして、それでいうとNext.jsもかなりアリかなと思っております。
AWS Amplify自体も元は一つ一つのAWSサービスから成り立っているため使いやすさだけでなくスケーリングやSLAの面でも非常におすすめかと思います。
しかしながらどの技術選定においても、仮説検証を高速に回し、フィードバックをUXに還元することが大事かなと思っています。その選択肢の一つとして非常に魅力的なので今後少しづつ使っていければと思います。
参考
https://aws-amplify.github.io/docs/js/api
https://qiita.com/G-awa/items/a5b2cc7017b1eceeb002