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

export 'API' (imported as 'API') was not found in 'aws-amplify' (possible exports: Amplify) #12635

Closed greg-munro closed 8 months ago

greg-munro commented 11 months ago

Before opening, please confirm:

JavaScript Framework

React

Amplify APIs

GraphQL API

Amplify Categories

api

Environment information

``` # Put output below this line System: OS: macOS 14.0 CPU: (8) arm64 Apple M1 Memory: 499.50 MB / 16.00 GB Shell: 5.9 - /bin/zsh Binaries: Node: 16.20.2 - /usr/local/bin/node Yarn: 1.22.19 - /usr/local/bin/yarn npm: 8.19.4 - /usr/local/bin/npm Browsers: Chrome: 119.0.6045.159 Safari: 17.0 npmGlobalPackages: @angular/cli: 14.1.2 @aws-amplify/cli: 12.8.2 @motor-js/cli: 0.4.0 corepack: 0.17.0 create-react-app: 5.0.1 gatsby-cli: 5.3.1 grunt-cli: 1.4.3 grunt: 1.5.2 json-server: 0.17.0 n: 9.0.0 node-pre-gyp: 0.17.0 nodemon: 2.0.16 npm: 8.19.4 yarn: 1.22.19 ```

Describe the bug

I am following the build a full stack React app tutorial here (https://aws.amazon.com/getting-started/hands-on/build-react-app-amplify-graphql/module-four/?e=gs2020&p=build-a-react-app-three) and module 4 results in the error, saying the API from amplify is not found.

Expected behavior

Code to run on npm start

Reproduction steps

Follow the steps in module 4 and run npm start

Code Snippet

// Put your code below this line.

import React, { useState, useEffect } from "react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import { API } from "aws-amplify";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  View,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import { listNotes } from "./graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "./graphql/mutations";

const App = ({ signOut }) => {
  const [notes, setNotes] = useState([]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await API.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    setNotes(notesFromAPI);
  }

  async function createNote(event) {
    event.preventDefault();
    const form = new FormData(event.target);
    const data = {
      name: form.get("name"),
      description: form.get("description"),
    };
    await API.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });
    fetchNotes();
    event.target.reset();
  }

  async function deleteNote({ id }) {
    const newNotes = notes.filter((note) => note.id !== id);
    setNotes(newNotes);
    await API.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);

Log output

``` // Put your logs below this line Attempted import error: 'API' is not exported from 'aws-amplify' (imported as 'API'). ERROR in ./src/App.js 22:26-37 export 'API' (imported as 'API') was not found in 'aws-amplify' (possible exports: Amplify) ERROR in ./src/App.js 35:10-21 export 'API' (imported as 'API') was not found in 'aws-amplify' (possible exports: Amplify) ERROR in ./src/App.js 49:10-21 export 'API' (imported as 'API') was not found in 'aws-amplify' (possible exports: Amplify) ```

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

nadetastic commented 11 months ago

Hi @greg-munro - thank you for opening this issue. Can you confirm which version of aws-amplify you have installed? The reason I ask is with the latest version (6.x.x) released about 2 weeks ago, we no longer export the API class, but instead have a generateClient() export to setup a client to make the graphql calls. You can view an example of this in our documentation.

scotttlee commented 11 months ago

@nadetastic i am also following this tutorial and getting the same error. how would this fix be done with the given code from the original post?

greg-munro commented 11 months ago

Hi @nadetastic, thanks for the quick response. I am using Amplify 6.0.5.

I would suggest the tutorial frontend code needs updating here.

many thanks.

chrisbonifacio commented 11 months ago

Hi @greg-munro 👋 we are looking into addressing outdated guides that were written for Amplify v5 projects.

@scotttlee , here is what the code from Module 4: Add a GraphQL API and Database would look like if migrated to Amplify JS v6.

import React, { useState, useEffect, FormEvent } from "react";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  View,
  WithAuthenticatorProps,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import { listNotes } from "../src/graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "../src/graphql/mutations";
import { CreateNoteInput, Note } from "@/src/API";

import { Amplify } from "aws-amplify";
import { generateClient } from "aws-amplify/api";
import awsconfig from "@/src/amplifyconfiguration.json";

Amplify.configure(awsconfig);

const client = generateClient();

const App = ({ signOut }: WithAuthenticatorProps) => {
  const [notes, setNotes] = useState<Note[]>([]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await client.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    setNotes(notesFromAPI);
  }

  async function createNote(event: FormEvent<HTMLFormElement>) {
    const form = event.target as HTMLFormElement;

    event.preventDefault();

    const formData = new FormData(form);

    const data: CreateNoteInput = {
      name: formData.get("name") as string,
      description: formData.get("description") as string,
    };

    await client.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });

    await fetchNotes();

    form.reset();
  }

  async function deleteNote({ id }: Note) {
    const newNotes = notes.filter((note) => note.id !== id);

    setNotes(newNotes);

    await client.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);
juliamarselle commented 11 months ago

@chrisbonifacio I am running into the same sort of error on another tutorial:

https://aws.amazon.com/getting-started/guides/deploy-webapp-amplify/module-two/?pg=webappamplify

I'm assuming there needs to be a tweak in the contents of the App.js file in here as well to account for the latest version of amplify, I'd love some assistance. Thank you!

AnLewis78 commented 11 months ago

@chrisbonifacio I am running into the same sort of error on another tutorial:

https://aws.amazon.com/getting-started/guides/deploy-webapp-amplify/module-two/?pg=webappamplify

I'm assuming there needs to be a tweak in the contents of the App.js file in here as well to account for the latest version of amplify, I'd love some assistance. Thank you!

Personally would like to see an update as well, as I am not able to get past this issue currently

DeveloperMatheus commented 11 months ago

Hello! Sorry for asking this (might be a stupid question):

I already have an application that we did a lot of progress using the v5. Are you guys going to deprecate this version, or make it unable to do any http calls? This is important for us because we're evaluating the migration to v6

keco1249 commented 11 months ago

I ran into a slightly confusing documentation issue with amplify-aws-react-native as well. The docs are updated to show the amplify-aws v6 api yet the latest version of amplify-aws-react-native only works with v5. https://docs.amplify.aws/react-native/build-a-backend/graphqlapi/connect-to-api/

rosenzw commented 10 months ago

I just had to make the following changes to the code in the tutorial:

add:

import { generateClient } from 'aws-amplify/api';
const client = generateClient();

Then change all references from API to client. e.g. const apiData = await API.graphql({ query: listNotes }); to const apiData = await client.graphql({ query: listNotes });

Here is my modified code which works in my environment:

// Put your code below this line.

import React, { useState, useEffect } from "react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  View,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import { listNotes } from "./graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "./graphql/mutations";
import { generateClient } from 'aws-amplify/api';

const client = generateClient();

const App = ({ signOut }) => {
  const [notes, setNotes] = useState([]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await client.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    setNotes(notesFromAPI);
  }

  async function createNote(event) {
    event.preventDefault();
    const form = new FormData(event.target);
    const data = {
      name: form.get("name"),
      description: form.get("description"),
    };
    await client.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });
    fetchNotes();
    event.target.reset();
  }

  async function deleteNote({ id }) {
    const newNotes = notes.filter((note) => note.id !== id);
    setNotes(newNotes);
    await client.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);
Smith-Steve commented 10 months ago

yes, I had the same issues... Technical writing is becoming a lost art... these directions could be stronger.

Bruce-8 commented 10 months ago

For anyone that has trouble with Module 5: Add Storage (such as encountering a similar error except with 'Storage' instead of 'API'), building on the code that was provided by @rosenzw, here is a solution for displaying images on the app using the updated documentation provided by @nadetastic.

Changes made (following the 'Update the React App' section in Module 5):

Note: A slight issue that the code has right now is that if you just entered into the app with no notes, you need to refresh the page once after you create your first note so the image displays properly. I suspect it has something to do with caching the image, but overall I'm not sure why this happens.

Here is the code below:

import React, { useState, useEffect } from "react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  Image,
  View,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import { listNotes } from "./graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "./graphql/mutations";
import { generateClient } from 'aws-amplify/api';
import { uploadData, getUrl, remove } from 'aws-amplify/storage';

const client = generateClient();

const App = ({ signOut }) => {
  const [notes, setNotes] = useState([]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await client.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    await Promise.all(
      notesFromAPI.map(async (note) => {
        if (note.image) {
          const url = await getUrl({ key: note.name });
          note.image = url.url;  
        }
        return note;
      })
    );
    setNotes(notesFromAPI);
  }

  async function createNote(event) {
    event.preventDefault();
    const form = new FormData(event.target);
    const image = form.get("image");
    const data = {
      name: form.get("name"),
      description: form.get("description"),
      image: image.name,
    };
    if (!!data.image) await uploadData({
      key: data.name,
      data: image
    });
    await client.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });
    fetchNotes();
    event.target.reset();
  }

  async function deleteNote({ id, name }) {
    const newNotes = notes.filter((note) => note.id !== id);
    setNotes(newNotes);
    await remove({ key: name });
    await client.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <View
            name="image"
            as="input"
            type="file"
            style={{ alignSelf: "end" }}
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            {note.image && (
              <Image
                src={note.image}
                alt={`visual aid for ${notes.name}`}
                style={{ width: 400 }}
              />
            )}
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);

Hope this helps!

tomermesser commented 9 months ago

I must say, I'm quite surprised by the complexity and lack of clarity in the AWS documentation, especially around Amplify. Given AWS's reputation for professionalism, I expected a smoother, more intuitive guide, particularly for beginners eager to explore and learn. The current state feels like a bit of a maze and could really benefit from a revamp to make it more accessible and less of a hurdle for newcomers.

ak0123 commented 9 months ago

Hi @greg-munro - thank you for opening this issue. Can you confirm which version of aws-amplify you have installed? The reason I ask is with the latest version (6.x.x) released about 2 weeks ago, we no longer export the API class, but instead have a generateClient() export to setup a client to make the graphql calls. You can view an example of this in our documentation.

Thanks to the good documentation link above, if you follow part of the section "Add Your First Record" and:

  1. Replace the out of date Line 5 (which I've commended out) and replace that import statement with lines 6 through 8 in my proposed code below and..
  2. Add Line 25 (as @greg-munro advised) then..
  3. replace all references of "API." to "client." as done in my proposed code below, the errors go away and App.js runs without errors!

too long didn't read (tl;dr): the below code worked for me (and I'm also working on the same AWS tutorial

Don't hesitate to message me with follow-up questions if you have issues!

`import React, { useState, useEffect } from "react"; import "./App.css"; import "@aws-amplify/ui-react/styles.css";

//import { API } from "aws-amplify"; import { generateClient } from 'aws-amplify/api'; import {createTodo, updateTodo, deleteTodo } from './graphql/mutations'; import {listTodos} from './graphql/queries';

import { Button, Flex, Heading, Text, TextField, View, withAuthenticator, } from "@aws-amplify/ui-react"; import { listNotes } from "./graphql/queries"; import { createNote as createNoteMutation, deleteNote as deleteNoteMutation, } from "./graphql/mutations";

const client = generateClient();

const App = ({ signOut }) => { const [notes, setNotes] = useState([]);

useEffect(() => { fetchNotes(); }, []);

async function fetchNotes() { const apiData = await client.graphql({ query: listNotes }); const notesFromAPI = apiData.data.listNotes.items; setNotes(notesFromAPI); }

async function createNote(event) { event.preventDefault(); const form = new FormData(event.target); const data = { name: form.get("name"), description: form.get("description"), }; await client.graphql({ query: createNoteMutation, variables: { input: data }, }); fetchNotes(); event.target.reset(); }

async function deleteNote({ id }) { const newNotes = notes.filter((note) => note.id !== id); setNotes(newNotes); await client.graphql({ query: deleteNoteMutation, variables: { input: { id } }, }); }

return (

My Notes App Current Notes {notes.map((note) => ( {note.name} {note.description} ))}

); };

export default withAuthenticator(App);`

rinkup commented 8 months ago

I just had to make the following changes to the code in the tutorial:

add:

import { generateClient } from 'aws-amplify/api';
const client = generateClient();

Then change all references from API to client. e.g. const apiData = await API.graphql({ query: listNotes }); to const apiData = await client.graphql({ query: listNotes });

Here is my modified code which works in my environment:

// Put your code below this line.

import React, { useState, useEffect } from "react";
import "./App.css";
import "@aws-amplify/ui-react/styles.css";
import {
  Button,
  Flex,
  Heading,
  Text,
  TextField,
  View,
  withAuthenticator,
} from "@aws-amplify/ui-react";
import { listNotes } from "./graphql/queries";
import {
  createNote as createNoteMutation,
  deleteNote as deleteNoteMutation,
} from "./graphql/mutations";
import { generateClient } from 'aws-amplify/api';

const client = generateClient();

const App = ({ signOut }) => {
  const [notes, setNotes] = useState([]);

  useEffect(() => {
    fetchNotes();
  }, []);

  async function fetchNotes() {
    const apiData = await client.graphql({ query: listNotes });
    const notesFromAPI = apiData.data.listNotes.items;
    setNotes(notesFromAPI);
  }

  async function createNote(event) {
    event.preventDefault();
    const form = new FormData(event.target);
    const data = {
      name: form.get("name"),
      description: form.get("description"),
    };
    await client.graphql({
      query: createNoteMutation,
      variables: { input: data },
    });
    fetchNotes();
    event.target.reset();
  }

  async function deleteNote({ id }) {
    const newNotes = notes.filter((note) => note.id !== id);
    setNotes(newNotes);
    await client.graphql({
      query: deleteNoteMutation,
      variables: { input: { id } },
    });
  }

  return (
    <View className="App">
      <Heading level={1}>My Notes App</Heading>
      <View as="form" margin="3rem 0" onSubmit={createNote}>
        <Flex direction="row" justifyContent="center">
          <TextField
            name="name"
            placeholder="Note Name"
            label="Note Name"
            labelHidden
            variation="quiet"
            required
          />
          <TextField
            name="description"
            placeholder="Note Description"
            label="Note Description"
            labelHidden
            variation="quiet"
            required
          />
          <Button type="submit" variation="primary">
            Create Note
          </Button>
        </Flex>
      </View>
      <Heading level={2}>Current Notes</Heading>
      <View margin="3rem 0">
        {notes.map((note) => (
          <Flex
            key={note.id || note.name}
            direction="row"
            justifyContent="center"
            alignItems="center"
          >
            <Text as="strong" fontWeight={700}>
              {note.name}
            </Text>
            <Text as="span">{note.description}</Text>
            <Button variation="link" onClick={() => deleteNote(note)}>
              Delete note
            </Button>
          </Flex>
        ))}
      </View>
      <Button onClick={signOut}>Sign Out</Button>
    </View>
  );
};

export default withAuthenticator(App);

Thank you. this worked for me

nadetastic commented 8 months ago

@rosenzw thank you for providing the solution! I'll go ahead and mark this issue as closed.

bulentkucuk commented 6 months ago

@chrisbonifacio I am running into the same sort of error on another tutorial: https://aws.amazon.com/getting-started/guides/deploy-webapp-amplify/module-two/?pg=webappamplify I'm assuming there needs to be a tweak in the contents of the App.js file in here as well to account for the latest version of amplify, I'd love some assistance. Thank you!

Personally would like to see an update as well, as I am not able to get past this issue currently

No fix after 5 months! What a waste of people's time. This suggests Amplify is not something to be taken seriously.

sohamdata commented 6 months ago

@chrisbonifacio I am running into the same sort of error on another tutorial:

https://aws.amazon.com/getting-started/guides/deploy-webapp-amplify/module-two/?pg=webappamplify

I'm assuming there needs to be a tweak in the contents of the App.js file in here as well to account for the latest version of amplify, I'd love some assistance. Thank you!

Although these issues aren't yet updated with the aws-amplify 6.x.x code in the modules, please see the refactors given by @rosenzw and @Bruce-8 for API and Storage respectively: https://github.com/aws-amplify/amplify-js/issues/12635#issuecomment-1873035673 https://github.com/aws-amplify/amplify-js/issues/12635#issuecomment-1875954882