Nuxt.js サーバーランタイム
このガイドでは、Nuxt.js Server-side Runtime (SSR) から Amplify Data に接続する方法について説明します。Nuxt.js アプリケーションの場合、Amplify はルーティング (Pages)、API Routes、およびミドルウェアに対してファーストクラスサポートを提供します。
開始する前に、以下が必要です。
Nuxt.js サーバーランタイムから Amplify Data に接続する
Amplify Data に接続するには、runWithAmplifyServerContext アダプターで AmplifyAPIs プラグインをセットアップし、useNuxtApp() composable を使用して、Amplify サーバーコンテキストユーティリティをセットアップしてから、runAmplifyApi 関数を使用して分離されたサーバーコンテキストで API を呼び出します。
Step 1 - AmplifyAPIs プラグインをセットアップする
Nuxt 3 はデフォルトでユニバーサルレンダリングを提供し、データフェッチングロジックはクライアント側とサーバー側の両方で実行される可能性があります。Amplify はサーバーサイドレンダリング (SSR) や静的サイト生成 (SSG) などのユースケースをサポートするためにサーバーコンテキスト内で実行できる API を提供しますが、Amplify のクライアント側 API とサーバー側 API はわずかに異なります。AmplifyAPIs プラグインをセットアップして、データフェッチングロジックがクライアント側とサーバー側の両方でスムーズに動作するようにすることができます。サーバーサイドレンダリングで Amplify カテゴリ API を使用する方法の詳細については、このドキュメントを参照してください。
- Nuxt プロジェクトのルートの下に
pluginsディレクトリを作成します。 pluginsディレクトリの下に01.amplify-apis.client.tsと01.amplify-apis.server.tsの 2 つのファイルを作成します。
これらのファイルでは、Nuxt プロジェクトで使用するクライアント固有およびサーバー固有の Amplify API をプラグインとして登録します。その後、useNuxtApp composable を介してこれらの API にアクセスできます。
01.amplify-apis.client.ts ファイルを以下のコードで編集します。
展開してコード実装を表示
import { fetchAuthSession, fetchUserAttributes, signIn, signOut, getCurrentUser,} from "aws-amplify/auth";import { generateClient } from "aws-amplify/data";import outputs from "../amplify_outputs.json";import type { Schema } from "@/amplify/data/resource";import { Amplify } from "aws-amplify";
// configure the Amplify client libraryif (process.client) { Amplify.configure(outputs, { ssr: true });}
// generate your data client using the Schema from your backendconst client = generateClient<Schema>();
export default defineNuxtPlugin({ name: "AmplifyAPIs", enforce: "pre", setup() { return { provide: { // You can call the API by via the composable `useNuxtApp()`. For example: // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` Amplify: { Auth: { fetchAuthSession, fetchUserAttributes, getCurrentUser, signIn, signOut, }, GraphQL: { client, }, }, }, }; },});次に、01.amplify-apis.server.ts ファイルを以下のコードで編集します。
展開してコード実装を表示
import type { CookieRef } from "nuxt/app";import { createKeyValueStorageFromCookieStorageAdapter, createUserPoolsTokenProvider, createAWSCredentialsAndIdentityIdProvider, runWithAmplifyServerContext,} from "aws-amplify/adapter-core";import { parseAmplifyConfig } from "aws-amplify/utils";import { fetchAuthSession, fetchUserAttributes, getCurrentUser,} from "aws-amplify/auth/server";import { generateClient } from "aws-amplify/data/server";import type { LibraryOptions, FetchAuthSessionOptions,} from "@aws-amplify/core";import type { GraphQLOptionsV6, GraphQLResponseV6,} from "@aws-amplify/api-graphql";
import outputs from "../amplify_outputs.json";
// parse the content of `amplify_outputs.json` into the shape of ResourceConfigconst amplifyConfig = parseAmplifyConfig(outputs);
// create the Amplify used token cookies names arrayconst userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId;const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`;
// create a GraphQL client that can be used in a server contextconst gqlServerClient = generateClient({ config: amplifyConfig });
const getAmplifyAuthKeys = (lastAuthUser: string) => ["idToken", "accessToken", "refreshToken", "clockDrift"] .map( (key) => `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}` ) .concat(lastAuthUserCookieName);
// define the pluginexport default defineNuxtPlugin({ name: "AmplifyAPIs", enforce: "pre", setup() { // The Nuxt composable `useCookie` is capable of sending cookies to the // client via the `SetCookie` header. If the `expires` option is left empty, // it sets a cookie as a session cookie. If you need to persist the cookie // on the client side after your end user closes your Web app, you need to // specify an `expires` value. // // We use 30 days here as an example (the default Cognito refreshToken // expiration time). const expires = new Date(); expires.setDate(expires.getDate() + 30);
// Get the last auth user cookie value // // We use `sameSite: 'lax'` in this example, which allows the cookie to be // sent to your Nuxt server when your end user gets redirected to your Web // app from a different domain. You should choose an appropriate value for // your own use cases. const lastAuthUserCookie = useCookie(lastAuthUserCookieName, { sameSite: "lax", expires, secure: true, });
// Get all Amplify auth token cookie names const authKeys = lastAuthUserCookie.value ? getAmplifyAuthKeys(lastAuthUserCookie.value) : [];
// Create a key-value map of cookie name => cookie ref // // Using the composable `useCookie` here in the plugin setup prevents // cross-request pollution. const amplifyCookies = authKeys .map((name) => ({ name, cookieRef: useCookie(name, { sameSite: "lax", expires, secure: true }), })) .reduce<Record<string, CookieRef<string | null | undefined>>>( (result, current) => ({ ...result, [current.name]: current.cookieRef, }), {} );
// Create a key value storage based on the cookies // // This key value storage is responsible for providing Amplify Auth tokens to // the APIs that you are calling. // // If you implement the `set` method, when Amplify needed to refresh the Auth // tokens on the server side, the new tokens would be sent back to the client // side via `SetCookie` header in the response. Otherwise the refresh tokens // would not be propagate to the client side, and Amplify would refresh // the tokens when needed on the client side. // // In addition, if you decide not to implement the `set` method, you don't // need to pass any `CookieOptions` to the `useCookie` composable. const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({ get(name) { const cookieRef = amplifyCookies[name];
if (cookieRef && cookieRef.value) { return { name, value: cookieRef.value }; }
return undefined; }, getAll() { return Object.entries(amplifyCookies).map(([name, cookieRef]) => { return { name, value: cookieRef.value ?? undefined }; }); }, set(name, value) { const cookieRef = amplifyCookies[name]; if (cookieRef) { cookieRef.value = value; } }, delete(name) { const cookieRef = amplifyCookies[name];
if (cookieRef) { cookieRef.value = null; } }, });
// Create a token provider const tokenProvider = createUserPoolsTokenProvider( amplifyConfig.Auth!, keyValueStorage );
// Create a credentials provider const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( amplifyConfig.Auth!, keyValueStorage );
// Create the libraryOptions object const libraryOptions: LibraryOptions = { Auth: { tokenProvider, credentialsProvider, }, };
return { provide: { // You can add the Amplify APIs that you will use on the server side of // your Nuxt app here. You must only use the APIs exported from the // `aws-amplify/<category>/server` subpaths. // // You can call the API by via the composable `useNuxtApp()`. For example: // `useNuxtApp().$Amplify.Auth.fetchAuthSession()` // // Recall that Amplify server APIs are required to be called in a isolated // server context that is created by the `runWithAmplifyServerContext` // function. Amplify: { Auth: { fetchAuthSession: (options: FetchAuthSessionOptions) => runWithAmplifyServerContext( amplifyConfig, libraryOptions, (contextSpec) => fetchAuthSession(contextSpec, options) ), fetchUserAttributes: () => runWithAmplifyServerContext( amplifyConfig, libraryOptions, (contextSpec) => fetchUserAttributes(contextSpec) ), getCurrentUser: () => runWithAmplifyServerContext( amplifyConfig, libraryOptions, (contextSpec) => getCurrentUser(contextSpec) ), }, GraphQL: { client: { // Follow this typing to ensure the`graphql` API return type can // be inferred correctly according to your queries and mutations graphql: < FALLBACK_TYPES = unknown, TYPED_GQL_STRING extends string = string >( options: GraphQLOptionsV6<FALLBACK_TYPES, TYPED_GQL_STRING>, additionalHeaders?: Record<string, string> ) => runWithAmplifyServerContext< GraphQLResponseV6<FALLBACK_TYPES, TYPED_GQL_STRING> >(amplifyConfig, libraryOptions, (contextSpec) => gqlServerClient.graphql( contextSpec, options, additionalHeaders ) ), }, }, }, }, }; },});Step 2 - useNuxtApp() composable を使用する
~/app.vue で GraphQL API を使用する場合:
<script setup lang="ts">import { Authenticator } from '@aws-amplify/ui-vue';import '@aws-amplify/ui-vue/styles.css';import { onMounted, ref } from 'vue';import type { Schema } from '@/amplify/data/resource';
// create a reactive reference to the array of todosconst todos = ref<Schema['Todo']['type'][]>([]);
async function listTodos() { try { // `$Amplify` is generated by Nuxt according to the `provide` key in the plugins // fetch all todos const { data } = await useNuxtApp().$Amplify.GraphQL.client.models.Todo.list(); todos.value = data;
} catch (error) { console.error('Error fetching todos', error); }}
// fetch todos when the component is mountedonMounted(() => { listTodos();});</script>
<template> <Authenticator> <template v-slot="{ user, signOut }"> <h1>Hello, Amplify 👋</h1> <ul> <li v-for="todo in todos" :key="todo.id">{{ todo.content }}</li> </ul> <button @click="signOut">Sign Out</button> </template> </Authenticator></template>app.vue ファイルはデフォルトでクライアント側とサーバー側の両方でレンダリングできます。useNuxtApp().$Amplify composable は、ランタイムに応じて 01.amplify-apis.client.ts と 01.amplify-apis.server.ts の正しい実装を選択します。
Step 3 - API Routes 用に Amplify をセットアップする
Nuxt の仕様に従って、API ルートハンドラーは Nuxt アプリの他の部分とは別の環境である ~/server の下に配置されます。したがって、前の段階で作成したプラグインは使用できず、追加の作業が必要です。
Amplify サーバーコンテキストユーティリティをセットアップする
- Nuxt プロジェクトの
serverディレクトリの下にutilsディレクトリを作成します。 utilsディレクトリの下にamplifyUtils.tsファイルを作成します。
このファイルでは、コンテキスト分離を使用してサーバー側で実行できる Amplify API を呼び出すためのヘルパー関数を作成します。amplifyUtils.ts ファイルを以下のコードで編集します。
展開してコード実装を表示
import type { H3Event, EventHandlerRequest } from "h3";import { createKeyValueStorageFromCookieStorageAdapter, createUserPoolsTokenProvider, createAWSCredentialsAndIdentityIdProvider, runWithAmplifyServerContext, AmplifyServer, CookieStorage,} from "aws-amplify/adapter-core";import { parseAmplifyConfig } from "aws-amplify/utils";
import type { LibraryOptions } from "@aws-amplify/core";import outputs from "~/amplify_outputs.json";
const amplifyConfig = parseAmplifyConfig(outputs);
const createCookieStorageAdapter = ( event: H3Event<EventHandlerRequest>): CookieStorage.Adapter => { // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions const readOnlyCookies = parseCookies(event);
return { get(name) { if (readOnlyCookies[name]) { return { name, value: readOnlyCookies[name] }; } }, set(name, value, options) { setCookie(event, name, value, options); }, delete(name) { deleteCookie(event, name); }, getAll() { return Object.entries(readOnlyCookies).map(([name, value]) => { return { name, value }; }); }, };};
const createLibraryOptions = ( event: H3Event<EventHandlerRequest>): LibraryOptions => { const cookieStorage = createCookieStorageAdapter(event); const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter(cookieStorage); const tokenProvider = createUserPoolsTokenProvider( amplifyConfig.Auth!, keyValueStorage ); const credentialsProvider = createAWSCredentialsAndIdentityIdProvider( amplifyConfig.Auth!, keyValueStorage );
return { Auth: { tokenProvider, credentialsProvider, }, };};
export const runAmplifyApi = <Result>( // we need the event object to create a context accordingly event: H3Event<EventHandlerRequest>, operation: ( contextSpec: AmplifyServer.ContextSpec ) => Result | Promise<Result>) => { return runWithAmplifyServerContext<Result>( amplifyConfig, createLibraryOptions(event), operation );};これで、runAmplifyApi 関数を使用して、分離されたサーバーコンテキストで Amplify API を呼び出せます。server ディレクトリに新しい API ルート /api/current-user を作成し、current-user.ts ファイルを以下のコードで編集します。
import { getCurrentUser } from "aws-amplify/auth/server";import { runAmplifyApi } from "~/server/utils/amplifyUtils";
export default defineEventHandler(async (event) => { const user = await runAmplifyApi(event, (contextSpec) => getCurrentUser(contextSpec) );
return user;});その後、このAPI ルートからデータをフェッチできます。例: fetch('http://localhost:3000/api/current-user')