Use Amplify categories APIs from Nuxt 3
If you have not already done so, please read the introduction documentation, Use Amplify Categories APIs in Server Side Rendering, to learn about how to use Amplify categories APIs in server-side rendering.
This documentation provides a getting-started guide to using the generic runWithAmplifyServerContext
adapter (exported from aws-amplify/adapter-core
) to enable Amplify in a Nuxt 3 project. The examples in this documentation may not present best practices for your Nuxt project. You are welcome to provide suggestions and contributions to improve this documentation or to create a Nuxt adapter package for Amplify and let others use it.
Set Up the AmplifyAPIs Plugin
Nuxt 3 offers universal rendering by default, where your data fetching logic may be executed on both the client and server sides. Amplify offers APIs that are capable of running within a server context to support use cases such as server-side rendering (SSR) and static site generation (SSG), though Amplify's client-side APIs and server-side APIs of Amplify are slightly different. You can set up an AmplifyAPIs
plugin to make your data fetching logic run smoothly across the client and server.
- If you haven’t already done so, create a
plugins
directory under the root of your Nuxt project - Create two files
01.amplify-apis.client.ts
and01.amplify-apis.server.ts
under theplugins
directory
In these files, you will register both client-specific and server-specific Amplify APIs that you will use in your Nuxt project as a plugin. You can then access these APIs via the useNuxtApp
composable.
Implement 01.amplify-apis.client.ts
Example implementation:
Learn moreExpand to view the example implementation
1import { Amplify } from 'aws-amplify';2import {3 fetchAuthSession,4 fetchUserAttributes,5 signIn,6 signOut7} from 'aws-amplify/auth';8import { list } from 'aws-amplify/storage';9import { generateClient } from 'aws-amplify/api';10import config from '../amplifyconfiguration.json';11
12const client = generateClient();13
14export default defineNuxtPlugin({15 name: 'AmplifyAPIs',16 enforce: 'pre',17
18 setup() {19 // This configures Amplify on the client side of your Nuxt app20 Amplify.configure(config, { ssr: true });21
22 return {23 provide: {24 // You can add the Amplify APIs that you will use on the client side25 // of your Nuxt app here.26 //27 // You can call the API by via the composable `useNuxtApp()`. For example:28 // `useNuxtApp().$Amplify.Auth.fetchAuthSession()`29 Amplify: {30 Auth: {31 fetchAuthSession,32 fetchUserAttributes,33 signIn,34 signOut35 },36 Storage: {37 list38 },39 GraphQL: {40 client41 }42 }43 }44 };45 }46});
Implement 01.amplify-apis.server.ts
Example implementation:
Learn moreExpand to view the example implementation
1import type { CookieRef } from 'nuxt/app';2import {3 createKeyValueStorageFromCookieStorageAdapter,4 createUserPoolsTokenProvider,5 createAWSCredentialsAndIdentityIdProvider,6 runWithAmplifyServerContext7} from 'aws-amplify/adapter-core';8import { parseAmplifyConfig } from 'aws-amplify/utils';9import {10 fetchAuthSession,11 fetchUserAttributes,12 getCurrentUser13} from 'aws-amplify/auth/server';14import { list } from 'aws-amplify/storage/server';15import { generateClient } from 'aws-amplify/api/server';16import type { ListPaginateInput } from 'aws-amplify/storage';17import type {18 LibraryOptions,19 FetchAuthSessionOptions20} from '@aws-amplify/core';21import type {22 GraphQLOptionsV6,23 GraphQLResponseV624} from '@aws-amplify/api-graphql';25
26import config from '../amplifyconfiguration.json';27
28// parse the content of `amplifyconfiguration.json` into the shape of ResourceConfig29const amplifyConfig = parseAmplifyConfig(config);30
31// create the Amplify used token cookies names array32const userPoolClientId = amplifyConfig.Auth!.Cognito.userPoolClientId;33const lastAuthUserCookieName = `CognitoIdentityServiceProvider.${userPoolClientId}.LastAuthUser`;34
35// create a GraphQL client that can be used in a server context36const gqlServerClient = generateClient({ config: amplifyConfig });37
38const getAmplifyAuthKeys = (lastAuthUser: string) =>39 ['idToken', 'accessToken', 'refreshToken', 'clockDrift']40 .map(41 (key) =>42 `CognitoIdentityServiceProvider.${userPoolClientId}.${lastAuthUser}.${key}`43 )44 .concat(lastAuthUserCookieName);45
46// define the plugin47export default defineNuxtPlugin({48 name: 'AmplifyAPIs',49 enforce: 'pre',50 setup() {51 // The Nuxt composable `useCookie` is capable of sending cookies to the52 // client via the `SetCookie` header. If the `expires` option is left empty,53 // it sets a cookie as a session cookie. If you need to persist the cookie54 // on the client side after your end user closes your Web app, you need to55 // specify an `expires` value.56 //57 // We use 30 days here as an example (the default Cognito refreshToken58 // expiration time).59 const expires = new Date();60 expires.setDate(expires.getDate() + 30);61
62 // Get the last auth user cookie value63 //64 // We use `sameSite: 'lax'` in this example, which allows the cookie to be65 // sent to your Nuxt server when your end user gets redirected to your Web66 // app from a different domain. You should choose an appropriate value for67 // your own use cases.68 const lastAuthUserCookie = useCookie(lastAuthUserCookieName, {69 sameSite: 'lax',70 expires,71 secure: true72 });73
74 // Get all Amplify auth token cookie names75 const authKeys = lastAuthUserCookie.value76 ? getAmplifyAuthKeys(lastAuthUserCookie.value)77 : [];78
79 // Create a key-value map of cookie name => cookie ref80 //81 // Using the composable `useCookie` here in the plugin setup prevents82 // cross-request pollution.83 const amplifyCookies = authKeys84 .map((name) => ({85 name,86 cookieRef: useCookie(name, { sameSite: 'lax', expires, secure: true })87 }))88 .reduce<Record<string, CookieRef<string | null | undefined>>>(89 (result, current) => ({90 ...result,91 [current.name]: current.cookieRef92 }),93 {}94 );95
96 // Create a key value storage based on the cookies97 //98 // This key value storage is responsible for providing Amplify Auth tokens to99 // the APIs that you are calling.100 //101 // If you implement the `set` method, when Amplify needed to refresh the Auth102 // tokens on the server side, the new tokens would be sent back to the client103 // side via `SetCookie` header in the response. Otherwise the refresh tokens104 // would not be propagate to the client side, and Amplify would refresh105 // the tokens when needed on the client side.106 //107 // In addition, if you decide not to implement the `set` method, you don't108 // need to pass any `CookieOptions` to the `useCookie` composable.109 const keyValueStorage = createKeyValueStorageFromCookieStorageAdapter({110 get(name) {111 const cookieRef = amplifyCookies[name];112
113 if (cookieRef && cookieRef.value) {114 return { name, value: cookieRef.value };115 }116
117 return undefined;118 },119 getAll() {120 return Object.entries(amplifyCookies).map(([name, cookieRef]) => {121 return { name, value: cookieRef.value ?? undefined };122 });123 },124 set(name, value) {125 const cookieRef = amplifyCookies[name];126 if (cookieRef) {127 cookieRef.value = value;128 }129 },130 delete(name) {131 const cookieRef = amplifyCookies[name];132
133 if (cookieRef) {134 cookieRef.value = null;135 }136 }137 });138
139 // Create a token provider140 const tokenProvider = createUserPoolsTokenProvider(141 amplifyConfig.Auth!,142 keyValueStorage143 );144
145 // Create a credentials provider146 const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(147 amplifyConfig.Auth!,148 keyValueStorage149 );150
151 // Create the libraryOptions object152 const libraryOptions: LibraryOptions = {153 Auth: {154 tokenProvider,155 credentialsProvider156 }157 };158
159 return {160 provide: {161 // You can add the Amplify APIs that you will use on the server side of162 // your Nuxt app here. You must only use the APIs exported from the163 // `aws-amplify/<category>/server` subpaths.164 //165 // You can call the API by via the composable `useNuxtApp()`. For example:166 // `useNuxtApp().$Amplify.Auth.fetchAuthSession()`167 //168 // Recall that Amplify server APIs are required to be called in a isolated169 // server context that is created by the `runWithAmplifyServerContext`170 // function.171 Amplify: {172 Auth: {173 fetchAuthSession: (options: FetchAuthSessionOptions) =>174 runWithAmplifyServerContext(175 amplifyConfig,176 libraryOptions,177 (contextSpec) => fetchAuthSession(contextSpec, options)178 ),179 fetchUserAttributes: () =>180 runWithAmplifyServerContext(181 amplifyConfig,182 libraryOptions,183 (contextSpec) => fetchUserAttributes(contextSpec)184 ),185 getCurrentUser: () =>186 runWithAmplifyServerContext(187 amplifyConfig,188 libraryOptions,189 (contextSpec) => getCurrentUser(contextSpec)190 )191 },192 Storage: {193 list: (input: ListPaginateInput) =>194 runWithAmplifyServerContext(195 amplifyConfig,196 libraryOptions,197 (contextSpec) => list(contextSpec, input)198 )199 },200 GraphQL: {201 client: {202 // Follow this typing to ensure the`graphql` API return type can203 // be inferred correctly according to your queries and mutations204 graphql: <205 FALLBACK_TYPES = unknown,206 TYPED_GQL_STRING extends string = string207 >(208 options: GraphQLOptionsV6<FALLBACK_TYPES, TYPED_GQL_STRING>,209 additionalHeaders?: Record<string, string>210 ) =>211 runWithAmplifyServerContext<212 GraphQLResponseV6<FALLBACK_TYPES, TYPED_GQL_STRING>213 >(amplifyConfig, libraryOptions, (contextSpec) =>214 gqlServerClient.graphql(215 contextSpec,216 options,217 additionalHeaders218 )219 )220 }221 }222 }223 }224 };225 }226});
Usage Example
Using the Storage list
API in ~/pages/storage-list.vue
:
1// `useAsyncData` and `useNuxtApp` are Nuxt composables2// `$Amplify` is generated by Nuxt according to the `provide` key in the plugins3// we've added above4<script setup lang="ts">5const { data, error } = useAsyncData(async () => {6 const listResult = await useNuxtApp().$Amplify.Storage.list({7 options: {accessLevel: 'guest'}8 });9 return listResult.items;10});11</script>12
13<template>14 <h3>Files with access level: guest</h3>15 <pre>{{ data }}</pre>16</template>
Using the GraphQL API in ~/pages/todos-list.vue
:
1<script setup lang="ts">2// Amplify codegen generated code after you run `amplify push` or `amplify pull`3import { listTodos } from '~/graphql/queries';4
5const { data, error } = useAsyncData(async () => {6 const result = await useNuxtApp().$Amplify.GraphQL.client.graphql({7 query: listTodos,8 });9 return result.data.listTodos;10});11</script>12
13<template>14 <h3>Todos</h3>15 <pre>{{ data }}</pre>16</template>
The above two pages can be rendered on both the client and server sides by default. useNuxtApp().$Amplify
will pick up the correct implementation of 01.amplify-apis.client.ts
and 01.amplify-apis.server.ts
to use, depending on the runtime.
Set Up Auth Middleware to Protect Your Routes
The auth middleware will use the plugin set up in the previous step as a dependency; therefore you can add the auth middleware via another plugin that will be loaded after the previous one.
- Create a
02.auth-redirect.ts
file under plugins directory
Implement 02.auth-redirect.ts
Example implementation:
Learn moreExpand to view the example implementation
1import { Amplify } from 'aws-amplify';2import config from '~/amplifyconfiguration.json';3
4// Amplify.configure() only needs to be called on the client side5if (process.client) {6 Amplify.configure(config, { ssr: true });7}8
9export default defineNuxtPlugin({10 name: 'AmplifyAuthRedirect',11 enforce: 'pre',12 setup() {13 addRouteMiddleware(14 'AmplifyAuthMiddleware',15 defineNuxtRouteMiddleware(async (to) => {16 try {17 const session = await useNuxtApp().$Amplify.Auth.fetchAuthSession();18
19 // If the request is not associated with a valid user session20 // redirect to the `/sign-in` route.21 // You can also add route match rules against `to.path`22 if (session.tokens === undefined && to.path !== '/sign-in') {23 return navigateTo('/sign-in');24 }25
26 if (session.tokens !== undefined && to.path === '/sign-in') {27 return navigateTo('/');28 }29 } catch (e) {30 if (to.path !== '/sign-in') {31 return navigateTo('/sign-in');32 }33 }34 }),35 { global: true }36 );37 }38});
Set Up Amplify for API Route Use Cases
Following the specification of Nuxt, your API route handlers will live under ~/server
, which is a separate environment from other parts of your Nuxt app; hence, the plugins created in the previous sections are not usable here, and extra work is required.
Set Up Amplify Server Context Utility
- If you haven’t already done so, create a
utils
directory under the server directory of your Nuxt project - Create an
amplifyUtils.ts
file under theutils
directory
In this file, you will create a helper function to call Amplify APIs that are capable of running on the server side with context isolation.
Example implementation:
Learn moreExpand to view the example implementation
1import type { H3Event, EventHandlerRequest } from 'h3';2import {3 createKeyValueStorageFromCookieStorageAdapter,4 createUserPoolsTokenProvider,5 createAWSCredentialsAndIdentityIdProvider,6 runWithAmplifyServerContext,7 AmplifyServer,8 CookieStorage9} from 'aws-amplify/adapter-core';10import { parseAmplifyConfig } from 'aws-amplify/utils';11
12import type { LibraryOptions } from '@aws-amplify/core';13import config from '~/amplifyconfiguration.json';14
15const amplifyConfig = parseAmplifyConfig(config);16
17const createCookieStorageAdapter = (18 event: H3Event<EventHandlerRequest>19): CookieStorage.Adapter => {20 // `parseCookies`, `setCookie` and `deleteCookie` are Nuxt provided functions21 const readOnlyCookies = parseCookies(event);22
23 return {24 get(name) {25 if (readOnlyCookies[name]) {26 return { name, value: readOnlyCookies[name] };27 }28 },29 set(name, value, options) {30 setCookie(event, name, value, options);31 },32 delete(name) {33 deleteCookie(event, name);34 },35 getAll() {36 return Object.entries(readOnlyCookies).map(([name, value]) => {37 return { name, value };38 });39 }40 };41};42
43const createLibraryOptions = (44 event: H3Event<EventHandlerRequest>45): LibraryOptions => {46 const cookieStorage = createCookieStorageAdapter(event);47 const keyValueStorage =48 createKeyValueStorageFromCookieStorageAdapter(cookieStorage);49 const tokenProvider = createUserPoolsTokenProvider(50 amplifyConfig.Auth!,51 keyValueStorage52 );53 const credentialsProvider = createAWSCredentialsAndIdentityIdProvider(54 amplifyConfig.Auth!,55 keyValueStorage56 );57
58 return {59 Auth: {60 tokenProvider,61 credentialsProvider62 }63 };64};65
66export const runAmplifyApi = <Result>(67 // we need the event object to create a context accordingly68 event: H3Event<EventHandlerRequest>,69 operation: (70 contextSpec: AmplifyServer.ContextSpec71 ) => Result | Promise<Result>72) => {73 return runWithAmplifyServerContext<Result>(74 amplifyConfig,75 createLibraryOptions(event),76 operation77 );78};
You can then use runAmplifyApi
function to call Amplify APIs in an isolated server context.
Usage Example
Take implementing an API route GET /api/current-user
, in ~/server/api/current-user.ts
:
1import { getCurrentUser } from 'aws-amplify/auth/server';2import { runAmplifyApi } from '~/server/utils/amplifyUtils';3
4export default defineEventHandler(async (event) => {5 const user = await runAmplifyApi(event, (contextSpec) =>6 getCurrentUser(contextSpec)7 );8
9 return user;10});
Then you can fetch data from this route, e.g. fetch('http://localhost:3000/api/current-user')
.
Set Up Server Middleware to Protect Your API Routes
Similar to API routes, the previously added auth middleware are not usable under /server
, hence extra work is required to set up a auth middleware to protect your routes.
- If you haven’t already done so, create a
middleware
directory under theserver
directory of your Nuxt project - Create an
amplifyAuthMiddleware.ts
file under themiddleware
directory
This middleware will be executed before a request reach your API route.
Example implementation:
1import { fetchAuthSession } from 'aws-amplify/auth/server';2
3export default defineEventHandler(async (event) => {4 if (event.path.startsWith('/api/')) {5 try {6 const session = await runAmplifyApi(event, (contextSpec) =>7 fetchAuthSession(contextSpec)8 );9
10 // You can add extra logic to match the requested routes to apply11 // the auth protection12 if (session.tokens === undefined) {13 setResponseStatus(event, 403);14 return {15 error: 'Access denied!'16 };17 }18 } catch (error) {19 return {20 error: 'Access denied!'21 };22 }23 }24});
With this middleware, when executing fetch('http://localhost:3000/api/current-user')
without signing in a user on the client side, the fetch
will receive a 403 error, and the request won’t reach route /api/current-user
.