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.43k stars 2.13k forks source link

AppSync API not working when called from production website. #13800

Closed ramon-san closed 2 months ago

ramon-san commented 2 months ago

Before opening, please confirm:

JavaScript Framework

Next.js

Amplify APIs

GraphQL API

Amplify Version

v6

Amplify Categories

api

Backend

CDK

Environment information

``` # Put output below this line System: OS: macOS 14.6.1 CPU: (8) arm64 Apple M1 Memory: 79.69 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 22.4.1 - /opt/homebrew/bin/node Yarn: 1.22.15 - /usr/local/bin/yarn npm: 10.8.1 - /opt/homebrew/bin/npm pnpm: 8.3.1 - /opt/homebrew/bin/pnpm Browsers: Chrome: 128.0.6613.120 Safari: 17.6 npmPackages: @ampproject/toolbox-optimizer: undefined () @aws-amplify/adapter-nextjs: ^1.2.17 => 1.2.17 @aws-amplify/adapter-nextjs/api: undefined () @aws-amplify/adapter-nextjs/data: undefined () @aws-amplify/ui-react: ^6.3.0 => 6.3.0 @aws-amplify/ui-react-internal: undefined () @aws-amplify/ui-react-server: undefined () @babel/core: undefined () @babel/runtime: 7.22.5 @edge-runtime/cookies: 5.0.0 @edge-runtime/ponyfill: 3.0.0 @edge-runtime/primitives: 5.0.0 @hapi/accept: undefined () @heroicons/react: ^2.0.18 => 2.1.5 @mswjs/interceptors: undefined () @napi-rs/triples: undefined () @next/eslint-plugin-next: ^13.5.4 => 13.5.6 @next/font: undefined () @opentelemetry/api: undefined () @types/node: ^22.5.1 => 22.5.4 @types/react: ^18.3.4 => 18.3.5 @typescript-eslint/eslint-plugin: ^8.3.0 => 8.4.0 @typescript-eslint/parser: ^8.3.0 => 8.4.0 (6.21.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 () autoprefixer: ^10.4.14 => 10.4.20 aws-amplify: ^6.6.0 => 6.6.0 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 () cli-select: undefined () client-only: 0.0.1 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.50.0 => 8.57.0 eslint-config-next: ^13.5.4 => 13.5.6 eslint-config-prettier: ^9.0.0 => 9.1.0 eslint-config-standard: ^17.1.0 => 17.1.0 eslint-plugin-import: ^2.28.1 => 2.30.0 eslint-plugin-n: ^16.1.0 => 16.6.2 eslint-plugin-prettier: ^5.2.1 => 5.2.1 eslint-plugin-react: ^7.33.2 => 7.35.2 eslint-plugin-react-hooks: ^4.6.0 => 4.6.2 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: ^8.0.3 => 8.0.3 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 () lint-staged: ^15.2.9 => 15.2.10 loader-runner: undefined () loader-utils: undefined () lodash.curry: undefined () lru-cache: undefined () mini-css-extract-plugin: undefined () nanoid: undefined () native-url: undefined () neo-async: undefined () next: ^14.2.8 => 14.2.8 node-fetch: undefined () node-html-parser: undefined () ora: undefined () os-browserify: undefined () p-limit: undefined () path-browserify: undefined () picomatch: undefined () platform: undefined () postcss: ^8.4.22 => 8.4.45 (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 () prettier: ^3.0.3 => 3.3.3 process: undefined () punycode: undefined () querystring-es3: undefined () raw-body: undefined () react: 18.2.0 => 18.2.0 react-builtin: undefined () react-dom: 18.2.0 => 18.2.0 react-dom-builtin: undefined () react-dom-experimental-builtin: undefined () react-dropzone: ^14.2.3 => 14.2.3 react-experimental-builtin: undefined () react-gtm-module: ^2.0.11 => 2.0.11 react-icons: ^4.11.0 => 4.12.0 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 () tailwindcss: ^3.3.1 => 3.4.10 tar: undefined () terser: undefined () text-table: undefined () timers-browserify: undefined () tty-browserify: undefined () typescript: ^5.5.4 => 5.5.4 ua-parser-js: undefined () unistore: undefined () util: undefined () vm-browserify: undefined () watchpack: undefined () web-vitals: undefined () webpack: undefined () webpack-sources: undefined () ws: undefined () zod: undefined () npmGlobalPackages: aws-cdk: 2.154.0 create-next-app: 13.3.0 dbdocs: 0.7.3 jest: 28.1.2 nodemon: 2.0.19 npm: 10.8.1 pnpm: 8.3.1 ```

Describe the bug

GraphQL API works when called from my localhost or Postman. However, when I deploy the website to production and make requests to the API, all requests fail with the following error:

GraphQLError: A network error has occurred.
    at s (https://mipey.mx/_next/static/chunks/pages/_app-07b4d7124b689df8.js:3:12449)
    at async https://mipey.mx/_next/static/chunks/pages/_app-07b4d7124b689df8.js:3:14606
    at async https://mipey.mx/_next/static/chunks/pages/_app-07b4d7124b689df8.js:3:15357
    at async nH (https://mipey.mx/_next/static/chunks/855-ce77091a8814e86c.js:10:2788)
    at async s (https://mipey.mx/_next/static/chunks/855-ce77091a8814e86c.js:10:3299)
    at async rR._graphql (https://mipey.mx/_next/static/chunks/855-ce77091a8814e86c.js:14:3320)
    at async Object.getStoresByUser (https://mipey.mx/_next/static/chunks/pages/app-b5f7944cef2af94b.js:1:6061)
    at async f (https://mipey.mx/_next/static/chunks/pages/app-b5f7944cef2af94b.js:1:3699)

Reading through #7025 I thought my problem might be in the Amplify configuration, but this seems to cause no issue:

import { Amplify } from 'aws-amplify';
import { I18n } from 'aws-amplify/utils';
import { translations, Theme } from '@aws-amplify/ui-react';

// Config docs found here: https://docs.amplify.aws/gen1/nextjs/tools/libraries/configure-categories/
export function configureAmplify() {
  Amplify.configure({
    Auth: {
      Cognito: {
        userPoolId: process.env.COGNITO_USER_POOL_ID,
        userPoolClientId: process.env.COGNITO_CLIENT_ID,
        loginWith: {
          email: true,
          oauth: {
            domain: 'pey-auth.auth.us-east-1.amazoncognito.com',
            scopes: ['openid', 'email', 'phone'],
            redirectSignIn: ['http://localhost:3000/', 'https://mipey.mx/'],
            redirectSignOut: ['http://localhost:3000/', 'https://mipey.mx/'],
            responseType: 'code'
          }
        }
      }
    },
    API: {
      GraphQL: {
        endpoint: process.env.APP_SYNC_API_ENDPOINT,
        defaultAuthMode: 'apiKey',
        apiKey: process.env.APP_SYNC_API_KEY,
        region: process.env.AMAZON_WEB_SERVICES_REGION
      }
    }
  });

  I18n.putVocabularies(translations);
  I18n.setLanguage('es');
}

I tried to check for any CORS requirements on AppSync or something that could be blocking my requests, but haven't found anything. My AppSync service is created through CDK and this is the code for the construct:

import * as cdk from 'aws-cdk-lib';
import * as appsync from 'aws-cdk-lib/aws-appsync';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import { Construct } from 'constructs';
import * as fs from 'fs';
import * as path from 'path';
import { mergeFiles } from '../../../shared/src/lib/file-merge';

interface TableInfo {
  tableName: string;
  table: dynamodb.Table;
}

export interface AppSyncConstructProps {
  environment: string;
  apiName: string;
  pathGraphQL: string;
  dynamoDBTables: TableInfo[];
}

export class AppSyncConstruct extends Construct {
  public readonly api: appsync.GraphqlApi;

  constructor(scope: Construct, id: string, props: AppSyncConstructProps) {
    super(scope, id);

    // Merge schemas to be used for the GraphQL API.
    const rootPath = path.resolve(__dirname, '..', '..', '..', 'graphql');
    const schemaString = mergeFiles({
      rootPath,
      fileExtension: 'graphql'
    });

    // Save merged schema to a file
    const schemaPath = path.join(__dirname, 'merged-schema.graphql');
    fs.writeFileSync(schemaPath, schemaString);

    this.api = new appsync.GraphqlApi(this, props.apiName, {
      name: props.apiName,
      definition: appsync.Definition.fromFile(schemaPath)
    });

    new cdk.CfnOutput(this, 'GraphQLAPIURL', {
      value: this.api.graphqlUrl,
    });

    new cdk.CfnOutput(this, 'GraphQLAPIKey', {
      value: this.api.apiKey || '',
    });

    // Create data sources using the table name from the TableInfo object
    const dataSources = props.dynamoDBTables.reduce((dataSourceMap, tableInfo) => {
      dataSourceMap[tableInfo.tableName] = this.api.addDynamoDbDataSource(
        tableInfo.tableName,  // Use table name as the logical ID
        tableInfo.table
      );
      return dataSourceMap;
    }, {} as { [key: string]: appsync.DynamoDbDataSource });

    const noneDataSource = this.api.addNoneDataSource('NoneDataSource');

    // Helper function to create AppsyncFunction
    const createAppsyncFunction = (name: string, dataSource: appsync.BaseDataSource, filePath: string) => {
      return new appsync.AppsyncFunction(this, name, {
        name: `CdkFunction${name}`,
        api: this.api,
        dataSource: dataSource,
        code: appsync.Code.fromAsset(path.join(props.pathGraphQL, 'functions', filePath)),
        runtime: appsync.FunctionRuntime.JS_1_0_0,
      });
    };

    // Helper function to create Resolver
    const createResolver = (typeName: string, fieldName: string, filePath: string, functions: appsync.AppsyncFunction[]) => {
      return new appsync.Resolver(this, `PipelineResolver${fieldName}`, {
        api: this.api,
        typeName: typeName,
        fieldName: fieldName,
        code: appsync.Code.fromAsset(path.join(props.pathGraphQL, 'resolvers', filePath)),
        runtime: appsync.FunctionRuntime.JS_1_0_0,
        pipelineConfig: functions,
      });
    };

    // Docs for AppSync resolvers with DynamoDB: https://docs.aws.amazon.com/appsync/latest/devguide/resolver-mapping-template-reference-dynamodb.html

    // Stores
    const getStoresByUserFunction = createAppsyncFunction('GetStoresByUser', dataSources[`${props.environment}-Stores`], 'read/storesByUser.js');
    createResolver('Query', 'getStoresByUser', 'pipelineResolver.js', [getStoresByUserFunction]);

    const getStoreFunction = createAppsyncFunction('GetStore', dataSources[`${props.environment}-Stores`], 'read/store.js');
    createResolver('Query', 'getStore', 'pipelineResolver.js', [getStoreFunction]);

    const createStoreFunction = createAppsyncFunction('CreateStore', dataSources[`${props.environment}-Stores`], 'create/store.js');
    createResolver('Mutation', 'createStore', 'pipelineResolver.js', [createStoreFunction]);

    // Users
    const validateEmailFunction = createAppsyncFunction('ValidateEmail', noneDataSource, 'create/signUp/validateEmail.js');
    const saveUserFunction = createAppsyncFunction('SaveUser', dataSources[`${props.environment}-Users`], 'create/signUp/saveUser.js');
    createResolver('Mutation', 'signUp', 'signUpResolver.js', [validateEmailFunction, saveUserFunction]);

    const getUserFunction = createAppsyncFunction('GetUser', dataSources[`${props.environment}-Users`], 'read/user.js');
    createResolver('Query', 'getUser', 'pipelineResolver.js', [getUserFunction]);
  }
}

I don't know what might cause the issue, but I think it must be a simple config error. Any help is greatly appreciated.

Expected behavior

I was expecting my API request to work on the production website as well.

Reproduction steps

  1. Build a AppSync service with the CDK command previously given.
  2. Deploy a website and try to connect with the service.

Code Snippet

I have the following file from which I make all calls to AppSync GraphQL API.

import { generateClient } from 'aws-amplify/api';
import { getCurrentUser } from 'aws-amplify/auth';

// Initialize the API client
const client = generateClient();

// Define types for your API responses
interface CreateStoreInput {
  name: string;
  description?: string;
}

interface UserStores {
  limit?: number;
  nextToken?: string;
}

interface User {
  userId: string;
}

// Define your GraphQL queries and mutations
const QUERIES = {
  GET_STORE_BY_USER: `
    query GetStoresByUser($userId: ID!, $limit: Int, $nextToken: String) {
      getStoresByUser(userId: $userId, limit: $limit, nextToken: $nextToken) {
        nextToken
        scannedCount
        startedAt
        items {
            id
            name
            logo
            banner
        }
      }
    }
  `,
};

const MUTATIONS = {
  CREATE_STORE: `
    mutation CreateStore($input: CreateStoreInput!) {
      createStore(input: $input) {
        id
        name
      }
    }
  `,
};

// API functions
export const api = {
  getCurrentUser: async (): Promise<User> => {
    try {
      return await getCurrentUser();
    } catch (error) {
      console.error('Error getting current user:', error);
      throw error;
    }
  },

  createStore: async (storeData: CreateStoreInput) => {
    try {
      const currentUser = await api.getCurrentUser();
      const response = await client.graphql({
        query: MUTATIONS.CREATE_STORE,
        variables: {
          input: {
            userId: currentUser.userId,
            ...storeData
          }
        }
      });
      return response;
    } catch (error) {
      console.error('Error creating store:', error);
      throw error;
    }
  },

  getStoresByUser: async (userStores: UserStores) => {
    try {
      const currentUser = await api.getCurrentUser();
      const response = await client.graphql({
        query: QUERIES.GET_STORE_BY_USER,
        variables: {
          userId: currentUser.userId,
          ...userStores
        }
      });
      return response;
    } catch (error) {
      console.error('Error getting stores:', error);
      throw error
    }
  }
};

I want to clarify that this code works because when used from localhost requests work just fine.

Log output

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

aws-exports.js

No response

Manual configuration

Already pasted this code in the issue description.

import { Amplify } from 'aws-amplify';
import { I18n } from 'aws-amplify/utils';
import { translations, Theme } from '@aws-amplify/ui-react';

// Config docs found here: https://docs.amplify.aws/gen1/nextjs/tools/libraries/configure-categories/
export function configureAmplify() {
  Amplify.configure({
    Auth: {
      Cognito: {
        userPoolId: process.env.COGNITO_USER_POOL_ID,
        userPoolClientId: process.env.COGNITO_CLIENT_ID,
        loginWith: {
          email: true,
          oauth: {
            domain: 'pey-auth.auth.us-east-1.amazoncognito.com',
            scopes: ['openid', 'email', 'phone'],
            redirectSignIn: ['http://localhost:3000/', 'https://mipey.mx/'],
            redirectSignOut: ['http://localhost:3000/', 'https://mipey.mx/'],
            responseType: 'code'
          }
        }
      }
    },
    API: {
      GraphQL: {
        endpoint: process.env.APP_SYNC_API_ENDPOINT,
        defaultAuthMode: 'apiKey',
        apiKey: process.env.APP_SYNC_API_KEY,
        region: process.env.AMAZON_WEB_SERVICES_REGION
      }
    }
  });

  I18n.putVocabularies(translations);
  I18n.setLanguage('es');
}

// Theme docs found here: https://ui.docs.amplify.aws/react/theming
export const amplifyTheme: Theme = {
  name: 'pey-theme',
  tokens: {
    colors: {
      primary: {
        10: '#e6f0ff',
        20: '#cce0ff',
        40: '#99c2ff',
        60: '#66a3ff',
        80: '#3385ff',
        100: '#0067f1', // Your original primary color
      },
      font: {
        primary: '#333333',
        secondary: '#666666',
      },
      background: {
        primary: '#ffffff',
        secondary: '#f5f5f5',
      },
    },
    components: {
      button: {
        primary: {
          backgroundColor: '{colors.primary.100}',
          color: '#ffffff',
          _hover: {
            backgroundColor: '{colors.primary.80}',
          },
          _active: {
            backgroundColor: '{colors.primary.60}',
          },
        },
      },
      heading: {
        color: '{colors.font.primary}',
      },
      text: {
        color: '{colors.font.secondary}',
      },
      card: {
        backgroundColor: '{colors.background.primary}',
        borderColor: '{colors.primary.20}',
      },
    },
  },
};

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

ramon-san commented 2 months ago

Really dumb problem, the issue was in my Amplify Hosting. For some reason, changes in the environmental variables didn't propagate, and had to manually redeploy my website. Closing the issue.