Subscribe to receive notifications of new posts:

Subscription confirmed. Thank you for subscribing!

Cloudflare Pagesを使用したフルスタックアプリケーションの構築

Loading...

6 min read
Building a full-stack application with Cloudflare Pages

これは大々的に披露するしかないと思っていたため、私たちはCloudflare Pagesでのフルスタックアプリケーションのサポートを発表できることに興奮していました。Cloudflare Workersの支援を受けてサーバーレス関数をPages内から追加する方法を示すための、実演用の画像共有プラットフォームを構築しました。プロジェクトに1つのファイルを新たに追加するだけで、ダイナミックレンダリング、他のAPIとのやりとり、KVおよびDurable Objectsを使用したpersistデータを追加することができます。フルスタックアプリケーションの可能性とPagesの高速開発サイクルおよび無制限プレビュー環境を組み合わせることで、ほとんどすべてのアプリケーションを作成する能力が得られます。

本日は、画面共有プラットフォームの例について説明します。一部の画像は秘密にしたまま、画像を友人たちと共有できるようにしたいと思います。関数を使用してJSON APIを構築(KVおよびDurable Objects上にデータを格納する)して、Cloudflare ImagesおよびCloudflare Accessと統合し、フロントエンドとしてReactを使用します。

良質のものをすぐにでも使用したい場合は、こちらにあるデモインスタンス、およびGitHub上のコードを利用できますが、ここではゆっくりしたアプローチに拘ります。

Cloudflare Pagesを使用したサーバーレス関数の構築

ファイル-ベースのルーティング

まだ慣れていない場合、Cloudflare Pagesはgitプロバイダーと接続(GitHubおよびGitLab)して、Cloudflareのネットワークに対する静的サイトのデプロイを自動化します。関数を使用するとこれらのアプリを動的データに散りばめて拡張することができます。まだお済みでない場合は、こちらから加入することができます

私たちのプロジェクトで、新しい関数を作成してみましょう。

// ./functions/time.js


export const onRequest = () => {
  return new Response(new Date().toISOString())
}

git commitと、このファイルのプッシュにより最初のPages関数の構築とデプロイがトリガーされます。/timeに対するすべてのリクエストはこの関数によって処理され、他のすべてのリクエストはユーザーのプロジェクトのスタティックアセットに戻されます。ディレクトリ内へのFunctionsファイルの配置は予想どおり動作します。./functions/api/time.jsは/api/time、./functions/some_directory/index.jsは/some_directoryで利用可能です。

また、TypeScript (./functions/time.tsが同様に動作する)、およびパラメータ化されたファイルもサポートされます

  • ./functions/todos/[id].jsのように単一角括弧を使用すると/todos/123などのすべてのリクエストと一致させられます。
  • そして./functions/todos/[[path]].jsのように二重角括弧を使用すると、任意の数のパスセグメント(例:/todos/123/subtasks)と一致させられます。

PagesFunctionの型は@cloudflare/workers-typesライブラリで宣言しますが、これは関数の型のチェックのために使用することができます。

動的データ

では、私たちの画像共有アプリに戻り、いくつかの画像をアップロード済みであると仮定し、それらをホームページ上に表示したいと思います。そのフロントエンドが呼び出すことができる、これらの画像のリストを返すエンドポイントが必要です。

// ./functions/api/images.ts

export const jsonResponse = (value: any, init: ResponseInit = {}) =>
  new Response(JSON.stringify(value), {
    headers: { "Content-Type": "application/json", ...init.headers },
    ...init,
  });

const generatePreviewURL = ({
  previewURLBase,
  imagesKey,
  isPrivate,
}: {
  previewURLBase: string;
  imagesKey: string;
  isPrivate: boolean;
}) => {
  // If isPrivate, generates a signed URL for the 'preview' variant
  // Else, returns the 'blurred' variant URL which never requires signed URLs
  // https://developers.cloudflare.com/images/cloudflare-images/serve-images/serve-private-images-using-signed-url-tokens

  return "SIGNED_URL";
};

export const onRequestGet: PagesFunction<{
  IMAGES: KVNamespace;
}> = async ({ env }) => {
  const { imagesKey } = (await env.IMAGES.get("setup", "json")) as Setup;

  const kvImagesList = await env.IMAGES.list<ImageMetadata>({
    prefix: `image:uploaded:`,
  });

  const images = kvImagesList.keys
    .map((kvImage) => {
      try {
        const { id, previewURLBase, name, alt, uploaded, isPrivate } =
          kvImage.metadata as ImageMetadata;

        const previewURL = generatePreviewURL({
          previewURLBase,
          imagesKey,
          isPrivate,
        });

        return {
          id,
          previewURL,
          name,
          alt,
          uploaded,
          isPrivate,
        };
      } catch {
        return undefined;
      }
    })
    .filter((image) => image !== undefined);

  return jsonResponse({ images });
};

観察力の優れた読者であればonRequestGetをエクスポートしていることに気が付くでしょう。これによりGETリクエストのみに応答することができます。

また、KV名前空間(env.IMAGESでアクセス)を使用して、アップロード済みの画像に関する情報を格納します。Pagesプロジェクト内にバインディングを作成するには、[Settings(設定)] タブに移動します。

他のAPIとのインターフェース

Cloudflare Imagesは画像のホスティングおよび変換のための、低価格で、ハイパフォーマンス、高機能のサービスです。複数のバリアントを作成して、様々な方法で画像をレンダリングして署名付きURLでアクセスを制御することができます。このサービスのAPIを使用するインターフェースに以下の関数を追加して、受信ファイルをCloudflare Imagesにアップロードします。

// ./functions/api/admin/upload.ts

export const onRequestPost: PagesFunction<{
  IMAGES: KVNamespace;
}> = async ({ request, env }) => {
  const { apiToken, accountId } = (await env.IMAGES.get(
    "setup",
    "json"
  )) as Setup;

  // Prepare the Cloudflare Images API request body
  const formData = await request.formData();
  formData.set("requireSignedURLs", "true");
  const alt = formData.get("alt") as string;
  formData.delete("alt");
  const isPrivate = formData.get("isPrivate") === "on";
  formData.delete("isPrivate");

  // Upload the image to Cloudflare Images
  const response = await fetch(
    `https://api.cloudflare.com/client/v4/accounts/${accountId}/images/v1`,
    {
      method: "POST",
      body: formData,
      headers: {
        Authorization: `Bearer ${apiToken}`,
      },
    }
  );

  // Store the image metadata in KV
  const {
    result: {
      id,
      filename: name,
      uploaded,
      variants: [url],
    },
  } = await response.json<{
    result: {
      id: string;
      filename: string;
      uploaded: string;
      requireSignedURLs: boolean;
      variants: string[];
    };
  }>();

  const metadata: ImageMetadata = {
    id,
    previewURLBase: url.split("/").slice(0, -1).join("/"),
    name,
    alt,
    uploaded,
    isPrivate,
  };

  await env.IMAGES.put(
    `image:uploaded:${uploaded}`,
    "Values stored in metadata.",
    { metadata }
  );
  await env.IMAGES.put(`image:${id}`, JSON.stringify(metadata));

  return jsonResponse(true);
};

永続性データ

頻繁に読み取られてもまれにしか書き込まれない情報を格納するため、既にKVを使用しています。もう少し同機が必要な機能の場合はどうでしょうか。

各画像にダウンロードカウンターを追加しましょう。highresバリアントをCloudflare Images内に作成すると、ユーザーがリンクをリクエストするたびにカウンターをインクリメントすることができます。これにはもう少しセットアップが必要ですが、プロジェクト内のDurable Objectsの機能を解き放つだけの価値が絶対にあります。

このダウンロードカウントを保持できる、以下のDurable Objectクラスを作成して公開する必要があります。

// ./durable_objects/downloadCounter.js
ts#example---counter

export class DownloadCounter {
  constructor(state) {
    this.state = state;
    // `blockConcurrencyWhile()` ensures no requests are delivered until initialization completes.
    this.state.blockConcurrencyWhile(async () => {
      let stored = await this.state.storage.get("value");
      this.value = stored || 0;
    });
  }

  async fetch(request) {
    const url = new URL(request.url);
    let currentValue = this.value;

    if (url.pathname === "/increment") {
      currentValue = ++this.value;
      await this.state.storage.put("value", currentValue);
    }

    return jsonResponse(currentValue);
  }
}

ミドルウェア

関数を実行する前に一部のコード(認証またはロギングなど)を実行する必要がある場合のために、Pagesには使いやすいミドルウェアが用意されており、これはファイルベースのルーティングの任意のレベルで適用することができます。ディレクトリに_middleware.tsファイルを作成することで、このファイルが最初に実行され、next()が呼び出されるときにユーザーの関数を実行することがわかります。

私たちのアプリケーションでは、未認証ユーザーが画像をアップロード(/api/admin/upload)または画像を削除(/api/admin/delete)できないようにしたいと思います。Cloudflare Accessではすべてまたは一部のアプリケーションに対するロールベースのアクセス制御を適用することができ、ユーザーが必要なのはサーバーレス関数に統合する単一ファイルだけです。私たちは./functions/api/admin/_middleware.tsを作成し、これは/api/admin/*の受信リクエストすべてに適用されます:

// ./functions/api/admin/_middleware.ts

const validateJWT = async (jwtAssertion: string | null, aud: string) => {
  // If the JWT is valid, return the JWT payload
  // Else, return false
  // https://developers.cloudflare.com/cloudflare-one/identity/users/validating-json

  return jwtPayload;
};

const cloudflareAccessMiddleware: PagesFunction<{ IMAGES: KVNamespace }> =
  async ({ request, env, next, data }) => {
    const { aud } = (await env.IMAGES.get("setup", "json")) as Setup;

    const jwtPayload = await validateJWT(
      request.headers.get("CF-Access-JWT-Assertion"),
      aud
    );

    if (jwtPayload === false)
      return new Response("Access denied.", { status: 403 });

    // We could also use the data object to pass information between middlewares
    data.user = jwtPayload.email;

    return await next();
  };

export const onRequest = [cloudflareAccessMiddleware];

ミドルウェアは自由に使える強力なツールで、Cloudflare Accessを使用するアプリケーションのパーツを容易に保護、またはHoneycombおよびSentryなどの可観測性およびエラーロギングプラットフォームと素早く統合することができます。

Jamstackとしての統合

"Jamstack"の"Jam"はJavaScript、API、Markupを表します。Cloudflare Pagesは以前からこの「J」と「M」を提供していましたが、中間にWorkersがあると、本当にフルスタックのJamstackを使用することができます。

取り組みやすい例として、Create Reactアプリでこの画像共有プラットフォームのフロントエンドを構築しましたが、Cloudflare Pagesは増加の一途をたどるフレームワークと自然に統合し(現在は23)、いつでも独自の完全なカスタムビルドコマンドを構成することができます

ユーザーのフロントエンドは私たちが構成済みの関数を呼び出して、そのデータをレンダリングするだけです。私たちはSWRを使用して物事を単純化しますが、ユーザーが望めば、全くありきたりなJavaScriptの fetchを使用してこれを実行することができます。

// ./src/components/ImageGrid.tsx

export const ImageGrid = () => {
  const { data, error } = useSWR<{ images: Image[] }>("/api/images");

  if (error || data === undefined) {
    return <div>An unexpected error has occurred when fetching the list of images. Please try again.</div>;
  }


  return (
    <div>
      {data.images.map((image) => (
        <ImageCard image={image} key={image.id} />
      ))}
    </div>
  );

}

ローカルデプロイ

動作状況をテストするためにすべての変更をプッシュする必要がある場合、それが高速であるとしても、このようなプロジェクト上の反復は手間がかかります。関数、Workers、機密情報、環境変数、KVに対する完全なサポートを含む、Pagesプロジェクトのローカルデプロイのための、Wranglerとの素晴らしい統合がリリースされました。Durable Objectsのサポートは間もなく開始されます。

npmからのインストール:

npm install wrangler@beta

静的アセットのフォルダの提供をするか、既存のツールをプロキシします:

# Serve a directory
npx wrangler pages dev ./public

# or integrate with your other tools
npx wrangler pages dev -- npx react-scripts start

前進、そして作成!

子犬がお好きでしたら、こちらに画像共有アプリケーションを展開してあります。コードがお好きでしたら、GitHub上に用意されています。ご自分で自由にフォークしてデプロイしてください! 5分間のセットアップウィザードがありますが、Cloudflare Images、Access、Workers、Durable Objectsが必要になります。

私たちはPagesプラットフォームの将来に胸を高鳴らせており、あなたが構築しているものについて聞きたいです!当社のDiscordサーバー上の#what-i-builtチャンネルであなたのフルスタックアプリケーションを公開するか、#pages-helpチャンネルでサポートを受けてください。

Cloudflareは 企業のネットワーク全体 を保護し、お客様が インターネット規模のアプリケーションを効率的に 構築するためのお手伝いをします。また、すべての Webサイトまたはインターネットアプリケーション を迅速化し、 DDoS攻撃を阻止して、 ハッカーを封じ込めます。 さらに、 Zero Trustを始める、あるいは導入のあらゆるフェーズにいる お客様を支援します。

インターネットを高速化し、安全性を高めるには、ご使用のデバイスから 1.1.1.1 にアクセスすることで、Cloudflareの無料アプリをご利用いただけます。

より良いインターネットの構築を支援するというCloudflareの使命について詳しくは、 こちら をご覧ください。新たなキャリア形成をお考えの方は、 求人情報 にアクセスしてください。

Full Stack Week (JP) Cloudflare Workers (JP) Cloudflare Access (JP) サーバーレス 日本語

Follow on Twitter

Greg Brimble |@GregBrimble
Obinna Ekwuno |@Obinnaspeaks
Cloudflare |Cloudflare

Related Posts

November 19, 2021 1:59PM

Cloudflareで次の動画アプリケーションを構築

歴史的に、動画アプリケーションの構築は非常に難しいものでした。動画の録画、エンコード、そして再生の裏には多くの複雑な技術があります。幸いなことに、Cloudflare Streamではすべての難しい部分を取り除くことで、カスタム動画やストリーミングアプリケーションを簡単に構築できます...

November 17, 2021 1:22PM

Cloudflare PagesがGitLabのサポートを開始

Pagesの構想の初期段階で、既存のワークフローとシームレスに統合するスムーズな開発者エクスペリエンスを備えたプラットフォームの構築に着手しました。ところが、Pagesの一般提供を発表した後、当社のプラットフォームは、実際にはすべての開発者が使用できるとは限らないことに気づいたのです。...

November 16, 2021 1:58PM

Cloudflare WorkersでJavaScriptモジュールのサポートを開始

Cloudflare WorkersでJavaScriptモジュールのサポートを開始します。JavaScriptで記述されたWorkerの例を見れば、過去数年間にインターネット上で出現するようになった次のコードスニペットに気付くかもしれませ...

November 15, 2021 1:59PM

開発者スポットライト:JamstackとDurable Objectsを使ってゲームジャムを制覇

「開発者スポットライト」と名づけられた新しいブログ記事シリーズへようこそ。このシリーズでは、Cloudflare Workersのエコシステム上に構築された、興味深いアプリケーションをご紹介します...