Cloudflare Pagesを使用したフルスタックアプリケーションの構築
2021-11-17
これは大々的に披露するしかないと思っていたため、私たちはCloudflare Pagesでのフルスタックアプリケーションのサポートを発表できることに興奮していました。Cloudflare Workersの支援を受けてサ...
\n \n
本日は、画面共有プラットフォームの例について説明します。一部の画像は秘密にしたまま、画像を友人たちと共有できるようにしたいと思います。関数を使用してJSON APIを構築(KVおよびDurable Objects上にデータを格納する)して、Cloudflare ImagesおよびCloudflare Accessと統合し、フロントエンドとしてReactを使用します。
良質のものをすぐにでも使用したい場合は、こちらにあるデモインスタンス、およびGitHub上のコードを利用できますが、ここではゆっくりしたアプローチに拘ります。
\nまだ慣れていない場合、Cloudflare Pagesはgitプロバイダーと接続(GitHubおよびGitLab)して、Cloudflareのネットワークに対する静的サイトのデプロイを自動化します。関数を使用するとこれらのアプリを動的データに散りばめて拡張することができます。まだお済みでない場合は、こちらから加入することができます。
私たちのプロジェクトで、新しい関数を作成してみましょう。
git commitと、このファイルのプッシュにより最初のPages関数の構築とデプロイがトリガーされます。/timeに対するすべてのリクエストはこの関数によって処理され、他のすべてのリクエストはユーザーのプロジェクトのスタティックアセットに戻されます。ディレクトリ内へのFunctionsファイルの配置は予想どおり動作します。./functions/api/time.jsは/api/time、./functions/some_directory/index.jsは/some_directoryで利用可能です。
\n// ./functions/time.js\n\n\nexport const onRequest = () => {\n return new Response(new Date().toISOString())\n}
\n また、TypeScript (./functions/time.tsが同様に動作する)、およびパラメータ化されたファイルもサポートされます
./functions/todos/[id].jsのように単一角括弧を使用すると/todos/123などのすべてのリクエストと一致させられます。
そして./functions/todos/[[path]].jsのように二重角括弧を使用すると、任意の数のパスセグメント(例:/todos/123/subtasks)と一致させられます。
PagesFunctionの型は@cloudflare/workers-typesライブラリで宣言しますが、これは関数の型のチェックのために使用することができます。
\nでは、私たちの画像共有アプリに戻り、いくつかの画像をアップロード済みであると仮定し、それらをホームページ上に表示したいと思います。そのフロントエンドが呼び出すことができる、これらの画像のリストを返すエンドポイントが必要です。
観察力の優れた読者であればonRequestGetをエクスポートしていることに気が付くでしょう。これによりGETリクエストのみに応答することができます。
\n// ./functions/api/images.ts\n\nexport const jsonResponse = (value: any, init: ResponseInit = {}) =>\n new Response(JSON.stringify(value), {\n headers: { "Content-Type": "application/json", ...init.headers },\n ...init,\n });\n\nconst generatePreviewURL = ({\n previewURLBase,\n imagesKey,\n isPrivate,\n}: {\n previewURLBase: string;\n imagesKey: string;\n isPrivate: boolean;\n}) => {\n // If isPrivate, generates a signed URL for the 'preview' variant\n // Else, returns the 'blurred' variant URL which never requires signed URLs\n // https://developers.cloudflare.com/images/cloudflare-images/serve-images/serve-private-images-using-signed-url-tokens\n\n return "SIGNED_URL";\n};\n\nexport const onRequestGet: PagesFunction<{\n IMAGES: KVNamespace;\n}> = async ({ env }) => {\n const { imagesKey } = (await env.IMAGES.get("setup", "json")) as Setup;\n\n const kvImagesList = await env.IMAGES.list<ImageMetadata>({\n prefix: `image:uploaded:`,\n });\n\n const images = kvImagesList.keys\n .map((kvImage) => {\n try {\n const { id, previewURLBase, name, alt, uploaded, isPrivate } =\n kvImage.metadata as ImageMetadata;\n\n const previewURL = generatePreviewURL({\n previewURLBase,\n imagesKey,\n isPrivate,\n });\n\n return {\n id,\n previewURL,\n name,\n alt,\n uploaded,\n isPrivate,\n };\n } catch {\n return undefined;\n }\n })\n .filter((image) => image !== undefined);\n\n return jsonResponse({ images });\n};
\n また、KV名前空間(env.IMAGESでアクセス)を使用して、アップロード済みの画像に関する情報を格納します。Pagesプロジェクト内にバインディングを作成するには、[Settings(設定)] タブに移動します。

\nCloudflare Imagesは画像のホスティングおよび変換のための、低価格で、ハイパフォーマンス、高機能のサービスです。複数のバリアントを作成して、様々な方法で画像をレンダリングして署名付きURLでアクセスを制御することができます。このサービスのAPIを使用するインターフェースに以下の関数を追加して、受信ファイルをCloudflare Imagesにアップロードします。
\n// ./functions/api/admin/upload.ts\n\nexport const onRequestPost: PagesFunction<{\n IMAGES: KVNamespace;\n}> = async ({ request, env }) => {\n const { apiToken, accountId } = (await env.IMAGES.get(\n "setup",\n "json"\n )) as Setup;\n\n // Prepare the Cloudflare Images API request body\n const formData = await request.formData();\n formData.set("requireSignedURLs", "true");\n const alt = formData.get("alt") as string;\n formData.delete("alt");\n const isPrivate = formData.get("isPrivate") === "on";\n formData.delete("isPrivate");\n\n // Upload the image to Cloudflare Images\n const response = await fetch(\n `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v1`,\n {\n method: "POST",\n body: formData,\n headers: {\n Authorization: `Bearer ${apiToken}`,\n },\n }\n );\n\n // Store the image metadata in KV\n const {\n result: {\n id,\n filename: name,\n uploaded,\n variants: [url],\n },\n } = await response.json<{\n result: {\n id: string;\n filename: string;\n uploaded: string;\n requireSignedURLs: boolean;\n variants: string[];\n };\n }>();\n\n const metadata: ImageMetadata = {\n id,\n previewURLBase: url.split("/").slice(0, -1).join("/"),\n name,\n alt,\n uploaded,\n isPrivate,\n };\n\n await env.IMAGES.put(\n `image:uploaded:${uploaded}`,\n "Values stored in metadata.",\n { metadata }\n );\n await env.IMAGES.put(`image:${id}`, JSON.stringify(metadata));\n\n return jsonResponse(true);\n};
\n 頻繁に読み取られてもまれにしか書き込まれない情報を格納するため、既にKVを使用しています。もう少し同機が必要な機能の場合はどうでしょうか。
各画像にダウンロードカウンターを追加しましょう。highresバリアントをCloudflare Images内に作成すると、ユーザーがリンクをリクエストするたびにカウンターをインクリメントすることができます。これにはもう少しセットアップが必要ですが、プロジェクト内のDurable Objectsの機能を解き放つだけの価値が絶対にあります。
このダウンロードカウントを保持できる、以下のDurable Objectクラスを作成して公開する必要があります。
\n// ./durable_objects/downloadCounter.js\nts#example---counter\n\nexport class DownloadCounter {\n constructor(state) {\n this.state = state;\n // `blockConcurrencyWhile()` ensures no requests are delivered until initialization completes.\n this.state.blockConcurrencyWhile(async () => {\n let stored = await this.state.storage.get("value");\n this.value = stored || 0;\n });\n }\n\n async fetch(request) {\n const url = new URL(request.url);\n let currentValue = this.value;\n\n if (url.pathname === "/increment") {\n currentValue = ++this.value;\n await this.state.storage.put("value", currentValue);\n }\n\n return jsonResponse(currentValue);\n }\n}
\n 関数を実行する前に一部のコード(認証またはロギングなど)を実行する必要がある場合のために、Pagesには使いやすいミドルウェアが用意されており、これはファイルベースのルーティングの任意のレベルで適用することができます。ディレクトリに_middleware.tsファイルを作成することで、このファイルが最初に実行され、next()が呼び出されるときにユーザーの関数を実行することがわかります。
私たちのアプリケーションでは、未認証ユーザーが画像をアップロード(/api/admin/upload)または画像を削除(/api/admin/delete)できないようにしたいと思います。Cloudflare Accessではすべてまたは一部のアプリケーションに対するロールベースのアクセス制御を適用することができ、ユーザーが必要なのはサーバーレス関数に統合する単一ファイルだけです。私たちは./functions/api/admin/_middleware.tsを作成し、これは/api/admin/*の受信リクエストすべてに適用されます:
ミドルウェアは自由に使える強力なツールで、Cloudflare Accessを使用するアプリケーションのパーツを容易に保護、またはHoneycombおよびSentryなどの可観測性およびエラーロギングプラットフォームと素早く統合することができます。
\n// ./functions/api/admin/_middleware.ts\n\nconst validateJWT = async (jwtAssertion: string | null, aud: string) => {\n // If the JWT is valid, return the JWT payload\n // Else, return false\n // https://developers.cloudflare.com/cloudflare-one/identity/users/validating-json\n\n return jwtPayload;\n};\n\nconst cloudflareAccessMiddleware: PagesFunction<{ IMAGES: KVNamespace }> =\n async ({ request, env, next, data }) => {\n const { aud } = (await env.IMAGES.get("setup", "json")) as Setup;\n\n const jwtPayload = await validateJWT(\n request.headers.get("CF-Access-JWT-Assertion"),\n aud\n );\n\n if (jwtPayload === false)\n return new Response("Access denied.", { status: 403 });\n\n // We could also use the data object to pass information between middlewares\n data.user = jwtPayload.email;\n\n return await next();\n };\n\nexport const onRequest = [cloudflareAccessMiddleware];
\n \n "Jamstack"の"Jam"はJavaScript、API、Markupを表します。Cloudflare Pagesは以前からこの「J」と「M」を提供していましたが、中間にWorkersがあると、本当にフルスタックのJamstackを使用することができます。
取り組みやすい例として、Create Reactアプリでこの画像共有プラットフォームのフロントエンドを構築しましたが、Cloudflare Pagesは増加の一途をたどるフレームワークと自然に統合し(現在は23)、いつでも独自の完全なカスタムビルドコマンドを構成することができます。
ユーザーのフロントエンドは私たちが構成済みの関数を呼び出して、そのデータをレンダリングするだけです。私たちはSWRを使用して物事を単純化しますが、ユーザーが望めば、全くありきたりなJavaScriptの fetchを使用してこれを実行することができます。
\n// ./src/components/ImageGrid.tsx\n\nexport const ImageGrid = () => {\n const { data, error } = useSWR<{ images: Image[] }>("/api/images");\n\n if (error || data === undefined) {\n return <div>An unexpected error has occurred when fetching the list of images. Please try again.</div>;\n }\n\n\n return (\n <div>\n {data.images.map((image) => (\n <ImageCard image={image} key={image.id} />\n ))}\n </div>\n );\n\n}
\n 動作状況をテストするためにすべての変更をプッシュする必要がある場合、それが高速であるとしても、このようなプロジェクト上の反復は手間がかかります。関数、Workers、機密情報、環境変数、KVに対する完全なサポートを含む、Pagesプロジェクトのローカルデプロイのための、Wranglerとの素晴らしい統合がリリースされました。Durable Objectsのサポートは間もなく開始されます。
npmからのインストール:
静的アセットのフォルダの提供をするか、既存のツールをプロキシします:
\nnpm install wrangler@beta
\n \n # Serve a directory\nnpx wrangler pages dev ./public\n\n# or integrate with your other tools\nnpx wrangler pages dev -- npx react-scripts start
\n 子犬がお好きでしたら、こちらに画像共有アプリケーションを展開してあります。コードがお好きでしたら、GitHub上に用意されています。ご自分で自由にフォークしてデプロイしてください! 5分間のセットアップウィザードがありますが、Cloudflare Images、Access、Workers、Durable Objectsが必要になります。
私たちはPagesプラットフォームの将来に胸を高鳴らせており、あなたが構築しているものについて聞きたいです!当社のDiscordサーバー上の#what-i-builtチャンネルであなたのフルスタックアプリケーションを公開するか、#pages-helpチャンネルでサポートを受けてください。
\n2021-11-17
これは大々的に披露するしかないと思っていたため、私たちはCloudflare Pagesでのフルスタックアプリケーションのサポートを発表できることに興奮していました。Cloudflare Workersの支援を受けてサ...