aws-amplify / amplify-js

A declarative JavaScript library for application development using cloud services.
https://docs.amplify.aws/lib/q/platform/js
Apache License 2.0
9.42k stars 2.12k forks source link

API name is invalid when I try to use rest api in server components #13525

Closed didemkkaslan closed 3 months ago

didemkkaslan commented 3 months ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

REST API

Amplify Version

v6

Amplify Categories

api

Backend

CDK

Environment information

``` # Put output below this line System: OS: macOS 14.5 CPU: (8) arm64 Apple M2 Memory: 122.42 MB / 8.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 18.17.1 - ~/.nvm/versions/node/v18.17.1/bin/node Yarn: 1.22.19 - /opt/homebrew/bin/yarn npm: 9.6.7 - ~/.nvm/versions/node/v18.17.1/bin/npm bun: 1.0.2 - ~/.bun/bin/bun Browsers: Chrome: 126.0.6478.62 Safari: 17.5 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/adapter-nextjs: ^1.2.5 => 1.2.5 @aws-amplify/adapter-nextjs/api: undefined () @aws-amplify/adapter-nextjs/data: undefined () @aws-amplify/auth: ^6.3.1 => 6.3.1 @aws-amplify/auth/cognito: undefined () @aws-amplify/auth/cognito/server: undefined () @aws-amplify/auth/enable-oauth-listener: undefined () @aws-amplify/auth/server: undefined () @aws-amplify/ui-react: ^6.1.9 => 6.1.9 @aws-amplify/ui-react-internal: undefined () @babel/core: undefined () @babel/runtime: 7.22.5 @edge-runtime/cookies: 4.1.1 @edge-runtime/ponyfill: 2.4.2 @edge-runtime/primitives: 4.1.0 @fortawesome/fontawesome-svg-core: ^6.5.2 => 6.5.2 @fortawesome/free-solid-svg-icons: ^6.5.2 => 6.5.2 @fortawesome/react-fontawesome: ^0.2.2 => 0.2.2 @hapi/accept: undefined () @mswjs/interceptors: undefined () @napi-rs/triples: undefined () @next/font: undefined () @opentelemetry/api: undefined () @radix-ui/react-slot: ^1.0.2 => 1.0.2 (1.0.0) @tanstack/query-codemods: 4.24.3 @tanstack/react-query: ^5.40.1 => 5.40.1 @tanstack/react-table: ^8.17.3 => 8.17.3 @types/node: ^20 => 20.12.7 @types/react: ^18 => 18.2.79 @types/react-dom: ^18 => 18.2.25 @typescript-eslint/parser: ^7.7.1 => 7.7.1 (7.2.0) @vercel/nft: undefined () @vercel/og: 0.6.2 acorn: undefined () amphtml-validator: undefined () anser: undefined () arg: undefined () assert: undefined () async-retry: undefined () async-sema: undefined () aws-amplify: ^6.3.1 => 6.3.1 aws-amplify/adapter-core: undefined () aws-amplify/analytics: undefined () aws-amplify/analytics/kinesis: undefined () aws-amplify/analytics/kinesis-firehose: undefined () aws-amplify/analytics/personalize: undefined () aws-amplify/analytics/pinpoint: undefined () aws-amplify/api: undefined () aws-amplify/api/server: undefined () aws-amplify/auth: undefined () aws-amplify/auth/cognito: undefined () aws-amplify/auth/cognito/server: undefined () aws-amplify/auth/enable-oauth-listener: undefined () aws-amplify/auth/server: undefined () aws-amplify/data: undefined () aws-amplify/data/server: undefined () aws-amplify/datastore: undefined () aws-amplify/in-app-messaging: undefined () aws-amplify/in-app-messaging/pinpoint: undefined () aws-amplify/push-notifications: undefined () aws-amplify/push-notifications/pinpoint: undefined () aws-amplify/storage: undefined () aws-amplify/storage/s3: undefined () aws-amplify/storage/s3/server: undefined () aws-amplify/storage/server: undefined () aws-amplify/utils: undefined () babel-packages: undefined () browserify-zlib: undefined () browserslist: undefined () buffer: undefined () bytes: undefined () ci-info: undefined () class-variance-authority: ^0.7.0 => 0.7.0 cli-select: undefined () client-only: 0.0.1 clsx: ^2.1.1 => 2.1.1 (2.0.0) commander: undefined () comment-json: undefined () compression: undefined () conf: undefined () constants-browserify: undefined () content-disposition: undefined () content-type: undefined () cookie: undefined () cross-spawn: undefined () crypto-browserify: undefined () css.escape: undefined () data-uri-to-buffer: undefined () debug: undefined () devalue: undefined () domain-browser: undefined () edge-runtime: undefined () eslint: ^8 => 8.57.0 eslint-config-airbnb: ^19.0.4 => 19.0.4 eslint-config-airbnb-typescript: ^18.0.0 => 18.0.0 eslint-config-next: 14.2.1 => 14.2.1 events: undefined () find-cache-dir: undefined () find-up: undefined () fresh: undefined () get-orientation: undefined () glob: undefined () gzip-size: undefined () http-proxy: undefined () http-proxy-agent: undefined () https-browserify: undefined () https-proxy-agent: undefined () husky: ^9.0.11 => 9.0.11 icss-utils: undefined () ignore-loader: undefined () image-size: undefined () is-animated: undefined () is-docker: undefined () is-wsl: undefined () jest-worker: undefined () json5: undefined () jsonwebtoken: undefined () loader-runner: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () lucide-react: ^0.390.0 => 0.390.0 mini-css-extract-plugin: undefined () nanoid: undefined () native-url: undefined () neo-async: undefined () next: 14.2.1 => 14.2.1 node-fetch: undefined () node-html-parser: undefined () ora: undefined () os-browserify: undefined () p-limit: undefined () path-browserify: undefined () picomatch: undefined () platform: undefined () postcss: ^8 => 8.4.38 (8.4.31) postcss-flexbugs-fixes: undefined () postcss-modules-extract-imports: undefined () postcss-modules-local-by-default: undefined () postcss-modules-scope: undefined () postcss-modules-values: undefined () postcss-preset-env: undefined () postcss-safe-parser: undefined () postcss-scss: undefined () postcss-value-parser: undefined () process: undefined () punycode: undefined () querystring-es3: undefined () raw-body: undefined () react: ^18 => 18.2.0 react-builtin: undefined () react-dom: ^18 => 18.2.0 react-dom-builtin: undefined () react-dom-experimental-builtin: undefined () react-experimental-builtin: undefined () react-is: 18.2.0 react-refresh: 0.12.0 react-server-dom-turbopack-builtin: undefined () react-server-dom-turbopack-experimental-builtin: undefined () react-server-dom-webpack-builtin: undefined () react-server-dom-webpack-experimental-builtin: undefined () regenerator-runtime: 0.13.4 sass-loader: undefined () scheduler-builtin: undefined () scheduler-experimental-builtin: undefined () schema-utils: undefined () semver: undefined () send: undefined () server-only: 0.0.1 setimmediate: undefined () shell-quote: undefined () source-map: undefined () source-map08: undefined () stacktrace-parser: undefined () stream-browserify: undefined () stream-http: undefined () string-hash: undefined () string_decoder: undefined () strip-ansi: undefined () superstruct: undefined () tailwind-merge: ^2.3.0 => 2.3.0 tailwindcss: ^3.4.1 => 3.4.3 tailwindcss-animate: ^1.0.7 => 1.0.7 tar: undefined () terser: undefined () text-table: undefined () timers-browserify: undefined () tty-browserify: undefined () typescript: ^5 => 5.4.5 ua-parser-js: undefined () unistore: undefined () use-debounce: ^10.0.1 => 10.0.1 util: undefined () vm-browserify: undefined () watchpack: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () ws: undefined () zod: undefined () npmGlobalPackages: corepack: 0.18.0 npm: 9.6.7 ```

Describe the bug

Hello I'm using amplify v6 in nextjs app router and when I try to use rest api then I get API name is invalid error only if I try to use it in a server component. It works well with client components like useQuery, useSWR or classical useeffect fetch

Expected behavior

I'm not so sure If I'm doing it right way but it should fetch the data in server components too

Reproduction steps

Call rest api in a server component

Code Snippet

// Put your code below this line.

This is my root layout 
export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <ReactQueryClientProvider>
      <html lang="en">
        <body className={inter.className}>
          <ConfigureAmplifyClientSide />
          {children}
        </body>
      </html>
    </ReactQueryClientProvider>
  );
}

"use client";

import { Amplify, ResourcesConfig } from "aws-amplify";
import { fetchAuthSession } from "aws-amplify/auth";
import logger from "@/lib/logger";

export const getCognitoAuthIDToken = async () => {
  try {
    const session = await fetchAuthSession();
    console.log("session:", session);
    return session.tokens?.idToken?.toString() as string;
  } catch (error) {
    logger.error("Error getting auth tokens:", error);
    return null;
  }
};

export const amplifyResourceConfig: ResourcesConfig = {
  Auth: {
    Cognito: {
      userPoolId: String(process.env.NEXT_PUBLIC_USER_POOL_ID),
      userPoolClientId: String(process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID),
    },
  },
  API: {
    REST: {
      PlatformCoreOldRestApi: {
        endpoint: `${String(
          process.env.NEXT_PUBLIC_PLATFORM_CORE_OLD_REST_API_URL
        )}`,
      },
      PlatformCoreNewRestApi: {
        endpoint: `${String(
          process.env.NEXT_PUBLIC_PLATFORM_CORE_NEW_REST_API_URL
        )}`,
      },
    },
  },
};

Amplify.configure(amplifyResourceConfig, {
  ssr: true,
  API: {
    REST: {
      headers: async () => ({
        Authorization: `Bearer ${await getCognitoAuthIDToken()}`,
      }),
    },
  },
});

export default function ConfigureAmplifyClientSide() {
  return null;
}

and the server component I'm making the rest api call: 

export default async function UsersPage({ searchParams }) {
  const { query } = searchParams;
  const users = await PlatformCoreOldRestApi.get<UserData[]>(
    `/platform/people/search?email=${query}`
  );

  where I define platform apis:

  import { get, del } from "aws-amplify/api";

export function platformAPI({ apiName }: { apiName: string }) {
  console.log("apiName:", apiName);
  return {
    get: async <T>(path: string) =>
      get({
        apiName,
        path,
      }).response.then(async (res) => (await res.body.json()) as T),
    delete: async <T>(path: string, queryParams?: Record<string, string>) =>
      del({
        apiName,
        path,
        options: {
          queryParams: queryParams || undefined,
        },
      }).response.then(async (res) => (await res.statusCode) as T),
  };
}

export const PlatformCoreOldRestApi = platformAPI({
  apiName: "PlatformCoreOldRestApi",
});

export const PlatformCoreNewRestApi = platformAPI({
  apiName: "PlatformCoreNewRestApi",
});
Screenshot 2024-06-20 at 19 09 15

Please help me I couldn't find a resource where I can set up nextjs with app router + amplify v6. The current one uses graphql api so I'm kind of stuck

Log output

``` // Put your logs below this line ```

aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

HuiSF commented 3 months ago

Hi @didemkkaslan Looking at the code examples you have provided (thanks for the detailed comments!), that you configured your REST API via Amplify.configure() in the client component ConfigureAmplifyClientSide. This means you can only use the configured REST API on the client side. And you are forcefully calling the REST API within a Server Component, it will throw this error as the configuration is not applied on the server side at all.

If you are aiming to use the REST API on the server side in a Server Component, you need to do the following:

  1. provide the REST API configuration to createServerRunner(), and use the runWithAmplifyServerContext function along with the server REST APIs exported from aws-amplify/api/server
  2. you will need to update your getCognitoAuthIDToken implementation to use the server fetchAuthSession API exported from aws-amplify/auth/server

See this documentation for details about using the Amplify server-side APIs.

didemkkaslan commented 3 months ago

Thanks @HuiSF Ive made these changes:

users/page.tsx

  const users = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      const result = await PlatformCoreOldRestApi.get<UserData[]>(
        contextSpec,
        `/platform/people/search?email=${query}`
      );

      return result;
    },
  });

AmplifyServerUtils.tsx

import { createServerRunner } from "@aws-amplify/adapter-nextjs";
import { ResourcesConfig } from "aws-amplify";

export const amplifyResourceConfig: ResourcesConfig = {
  Auth: {
    Cognito: {
      userPoolId: String(process.env.NEXT_PUBLIC_USER_POOL_ID),
      userPoolClientId: String(process.env.NEXT_PUBLIC_USER_POOL_CLIENT_ID),
    },
  },
  API: {
    REST: {
      PlatformCoreOldRestApi: {
        endpoint: `${String(
          process.env.NEXT_PUBLIC_PLATFORM_CORE_OLD_REST_API_URL
        )}`,
      },
      PlatformCoreNewRestApi: {
        endpoint: `${String(
          process.env.NEXT_PUBLIC_PLATFORM_CORE_NEW_REST_API_URL
        )}`,
      },
    },
  },
};

export const { runWithAmplifyServerContext } = createServerRunner({
  config: amplifyResourceConfig,
});

RestAPIs.ts

import { get, del } from "aws-amplify/api/server";

export function platformAPI({ apiName }: { apiName: string }) {
  console.log("apiName:", apiName);
  return {
    get: async <T>(contextSpec, path: string) =>
      get(contextSpec, {
        apiName,
        path,
      }).response.then(async (res) => (await res.body.json()) as T),
    delete: async <T>(path: string, queryParams?: Record<string, string>) =>
      del({
        apiName,
        path,
        options: {
          queryParams: queryParams || undefined,
        },
      }).response.then(async (res) => (await res.statusCode) as T),
  };
}

export const PlatformCoreOldRestApi = platformAPI({
  apiName: "PlatformCoreOldRestApi",
});

export const PlatformCoreNewRestApi = platformAPI({
  apiName: "PlatformCoreNewRestApi",
});
Screenshot 2024-06-21 at 03 09 42

But I'm getting unauthorized error is there anything I should also do? Maybe its because I don't have this API REST Headers setup with Authorization header


Amplify.configure(amplifyResourceConfig, {
  ssr: true,
  API: {
    REST: {
      headers: async () => ({
        Authorization: `Bearer ${await getCognitoAuthIDToken()}`,
      }),
    },
  },
});

but I couldnt do the same thing with  createServerRunner because importing fetchAuthSession from @aws-amplify/auth/server now it also needs contextspec as param

export const { runWithAmplifyServerContext } = createServerRunner({
  config: amplifyResourceConfig,
});
didemkkaslan commented 3 months ago

Hmm this code actually worked:

  const users = await runWithAmplifyServerContext({
    nextServerContext: { cookies },
    operation: async (contextSpec) => {
      const session = await fetchAuthSession(contextSpec);
      const token = session.tokens?.idToken?.toString() as string;
      const result = await PlatformCoreOldRestApi.get<UserData[]>(
        contextSpec,
        `/platform/people/search?email=${query}`,
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        }
      );

      return result;
    },
  });
HuiSF commented 3 months ago

Hi @didemkkaslan thanks for the additional information. Your latest solution would be something that I recommend to do.

Amplify server-side APIs have to be running inside the isolated context that's created by runWithAmplifyServerContext per incoming request, resolving the auth tokens along each REST API call compliance this pattern.

Please let us know is there anything else we can help.

didemkkaslan commented 3 months ago

Hello @HuiSF my problem is solved thanks for the support 😻