Next.jsからAmplify DataStoreを使ってTwitter的なアプリを作る
はじめに
AWS Amplify を使えば GraphQL の API が簡単に作成出来る。さらに Amplify DataStore を使えば GraphQL のクエリを書くこと無く、この GraphQL API にリクエストを送ることが出来る。Next.js の Twitter 的なアプリを作ることを例に、その方法について解説する。
React のフロントエンド
この記事のコードは以下にも記載した。
重要な部分について説明する
ツイートの状態
index.tsx 内でツイートの状態を useReducer を用いて定義する。
const [tweets, dispatch] = useReducer(
(tweets: Tweet[], action: TweetsAction): Tweet[] => {
switch (action.type) {
case "ADD":
return [action.payload, ...tweets];
case "DELETE":
return tweets.filter((tweet) => tweet.id !== action.payload.id);
case "SET":
return action.payload;
default:
return tweets;
}
},
[]
);
ADD は新たなツイートを追加、DELETE はツイートを消す、SET はツイート全体をスキャンする action である。Tweet 型は Amplify DataStore でモデルを作成することで自動的に生成される。TweetsAction 型は自分で定義した。アクションごとに payload の型が異なるため、type TweetsAction = AddTweetAction | DeleteTweetAction | RefreshTweetsAction;
という形で型を定義した。
DataStore の subscription を追加
index.tsx にこのコンポーネントが読み込まれたときに実行される処理を記載する。
useEffect(() => {
(async () => {
const fetchedTweets = await DataStore.query(Tweet, Predicates.ALL, {
sort: (s) => s.updatedAt(SortDirection.DESCENDING),
});
dispatch({ type: "SET", payload: fetchedTweets });
})();
const subscription = DataStore.observe(Tweet).subscribe(async (msg) => {
const { element, opType } = msg;
switch (opType) {
case "INSERT":
return dispatch({ type: "ADD", payload: element });
case "DELETE":
return dispatch({ type: "DELETE", payload: element });
default:
break;
}
});
return () => {
subscription.unsubscribe();
};
}, []);
実行したい処理は 2 つあって、1 つはデータベースをスキャンし、最初のタイムラインに表示するツイートを取得する処理である。もう一つは、Amplify DataStore の subscription の定義である。subscription では他の端末からサーバー側の Tweet のデータに変化をイベントとして受け取ることが出来る。
更新ボタン
RefreshButton.tsx で更新ボタンを定義する。subscription があるのでタイムラインには最新のツイートが表示されているが、通知画面等で応用が効くので追加した。
export default function RefreshButton({
dispatch,
}: {
dispatch: (action: TweetsAction) => void;
}) {
const refresh = async () => {
const fetchedTweets = await DataStore.query(Tweet, Predicates.ALL, {
sort: (s) => s.updatedAt(SortDirection.DESCENDING),
});
dispatch({ type: "SET", payload: fetchedTweets });
};
return <Button onClick={refresh}>Refresh</Button>;
}
DataStore.query によって Tweet データモデルからデータを取得出来る。通常、GraphQL を使う際は GraphQL クエリを書いて発行するが、DataStore であれば抽象化されたオブジェクトを渡すことで情報を取得出来る。今回は Twitter のため更新時間が早い順にして取得するようにした。
タイムライン
Timeline.tsx ではツイートを並べて表示し、その削除機能を追加する。
export default function Timeline({ tweets }: { tweets: Tweet[] }) {
const deleteTweet = async (tweet: Tweet) => {
await DataStore.delete(Tweet, tweet.id);
};
return (
<Collection items={tweets} type="list">
{(tweet: Tweet) => (
<TweetCard key={tweet.id} tweet={tweet} deleteTweet={deleteTweet} />
)}
</Collection>
);
}
DataStore.delete で Tweet データモデルに対して id を指定することで削除出来る。こちらも GraphQL の知識は一切必要ない。
ツイートフォーム
TweetForm.tsx ではツイッターのフォームを定義する。
export default function TweetForm() {
const [tweet, setTweet] = useState("");
const createTweet = async (content: string) => {
await DataStore.save(new Tweet({ content }));
};
return (
<Flex
as="form"
direction="column"
onSubmit={(e) => {
e.preventDefault();
createTweet(tweet);
}}
>
<TextAreaField
onChange={(e) => {
setTweet(e.target.value);
}}
label="create tweet"
labelHidden={true}
isRequired={true}
/>
<Button type="submit">Tweet</Button>
</Flex>
);
}
このコンポーネントでは新たに tweet 状態を定義しユーザーの入力を状態として持つ。送信ボタンが押された際にその状態を DataStore を通じて投稿出来る。
おわりに
Amplify DataStore を使用すると、実際に GraphQL のクエリを書くよりは簡単に GraphQL API にアクセスが可能である。さらに、subscription を用いることでサーバーサイドのイベントをブラウザ上のイベントのように取り扱うことができ、React のコードとしては美しくまとめることが出来る。このような開発体験は RestAPI では不可能だと思う。
また、Amplify は Next.js の SSR を公式にサポートしており、その環境構築もほとんど設定せずとも構築できる。当然、CDN や CI/CD もほぼ設定不要で構築出来る。さらに、Amplify Studio を用いればデータモデルの関係は GUI で設計出来る。これらの設計は DynamoDB をバックエンドとしており無限の水平スケールが可能である。ベンチャー企業が簡単な API が必要な Web サービスを高速に開発する用途において、Amplify は最適ではないだろうか。
今後の課題として、データモデル同士が複数の関係、データに対するユーザー権限周りの設定方法、S3 に写真を保存する方法等を調査したい。
Discussion