Front-End Web & Mobile

Use Generative AI and Next.js with AWS Amplify to build a Fullstack Recipe Generator

Let’s dive into the world of Generative AI, Next.js, AWS Amplify, and Amazon Bedrock supercharged by Claude 3. In this guide, we’ll walk you through creating a recipe generator app where users can input a list of ingredients, and Claude 3 will generate delicious recipes based on their selection.

In November 2023, AWS Amplify unveiled the public preview of its next-generation full-stack app building capabilities. Amplify Gen 2 adopts a code-first developer experience, enabling developers to define and provision cloud resources, including authentication and data use cases, using TypeScript and AWS Cloud Development Kit (AWS CDK).

Amazon Bedrock is a fully managed service that offers a choice of high-performing foundation models (FMs) from leading AI companies like AI21 Labs, Anthropic, Cohere, Meta, Stability AI, and Amazon via a single API, along with a broad set of capabilities you need to build generative AI applications with security, privacy, and responsible AI. Amazon Bedrock’s single API access, regardless of the models you choose, gives you the flexibility to use different FMs and upgrade to the latest model versions with minimal code changes.

AWS Amplify accelerates web app development in the cloud, providing a suite of capabilities including data management, UI components, hosting, and more. When building GenAI apps, Amplify streamlines the process, offering the necessary tools for seamless development. Additionally, with AWS CDK powering Amplify Gen 2, connecting to Amazon Bedrock requires just a few lines of code. This powerful combination enables efficient development, deployment, and scaling of the recipe generator app, while ensuring security and performance.

The Recipe AI App running in the browser

Prerequisites

  • An AWS account. Note that Amplify is part of the AWS Free Tier.
  • Node.js v18.17 or later
  • npm v9 or later
  • git v2.14.1 or later
  • A text editor, for this guide we will use VSCode, but you can use your preferred IDE.

Amazon Bedrock Model Access

Amazon Bedrock enables users to request access to a variety of Generative AI Models. In this example, you require access to Claude 3 Sonnet from Anthropic. Follow the steps outlined below to request access.

Step 1: Sign in to the AWS console and navigate to Amazon Bedrock. Choose the us-east-1 region from the region selector.

Amazon Bedrock home page on AWS console

Step 2: Choose the Claude model, then click on the Request model access button.

Select Claude model and request access

Step 3: Select the Manage model access button

Select the Manage model access button

Step 4: Check the Claude 3 Sonnet option, then click on the “Save changes” button.

Check the Claude 3 Sonnet option and save the changes

Cloning the repo

Step 1: Navigate to the repository on AWS Samples and fork it to your GitHub repositories

Fork the repository on AWS samples

Step 2: Clone the app by running the command below in your terminal

git clone https://github.com/<YOUR_GITHUB>/recipe-ai.git

Step 3: Access the newly cloned repository in VSCode by executing the commands below in your terminal.

cd recipe-ai
code . -r

VSCode will open the repository folder, including the Amplify folder, which contains the backend details that we’ll discuss in the next section.

Open the cloned repository using VSCode

Step 4: Install the required packages including the Amplify Gen2 packages by running the commands below

npm i 

The Amplify Backend

In the final app (as seen in the gif at the beginning of the post), users type in their ingredients and click a button to request a recipe from Amazon Bedrock. The code for this is in the repository you cloned. Here, we’ll go over the key steps to connect your Amplify app with Amazon Bedrock.

In the repository, you’ll find an amplify folder containing a data directory. In the amplify/data/resource.ts file, we’ve defined a GraphQL query capable of receiving a list of ingredients and linking to Amazon Bedrock to produce a recipe based on those ingredients. This query will use a custom type to structure the response from Amazon Bedrock.

The Amplify folder in the project

The GraphQL API schema consists of two main parts:

  • The askBedrock query takes an array of strings called ingredients and returns a BedrockResponse. We made it publicly accessible using .authorization([a.allow.public()]). The .handler(a.handler.custom({ entry: "./bedrock.js", dataSource: "bedrockDS" })) line sets up a custom handler for this query, defined in bedrock.js, using bedrockDS as its data source.
  • The BedrockResponse is a custom type with fields body & error, both of type string. This custom type will be used to structure the response from the askBedrock query,
 ...
 
 const schema = a.schema({
  BedrockResponse: a.customType({
    body: a.string(),
    error: a.string(),
  }),

  askBedrock: a
    .query()
    .arguments({ ingredients: a.string().array() })
    .returns(a.ref("BedrockResponse"))
    .authorization([a.allow.public()])
    .handler(
      a.handler.custom({ entry: "./bedrock.js", dataSource: "bedrockDS" })
    ),
});
 
 ...

In the amplify/backend.ts file, we create an HTTP data source named bedrockDS to connect the query to Amazon Bedrock. This data source is associated with the Bedrock service in the us-east-1 region. Additionally, we add a new policy to the principal of the bedrockDS data source using the addToPrincipalPolicy method. The policy statement specifies the allowed resources and actions. In this case, the resource is the AWS ARN (Amazon Resource Name) for the Claude 3 model, and the permitted action is bedrock:InvokeModel.


const bedrockDataSource = backend.data.resources.graphqlApi.addHttpDataSource(
  "bedrockDS",
  "https://bedrock-runtime.us-east-1.amazonaws.com",
  {
    authorizationConfig: {
      signingRegion: "us-east-1",
      signingServiceName: "bedrock",
    },
  }
);

bedrockDataSource.grantPrincipal.addToPrincipalPolicy(
  new PolicyStatement({
    resources: [
      "arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-3-sonnet-20240229-v1:0",
    ],
    actions: ["bedrock:InvokeModel"],
    
  })
);

The file amplify/data/bedrock.js contains the logic of the  implementation of the askBedrock handler It utilizes the query’s input parameter, i.e., ingredients, to generate a prompt and transmit it to the HTTP data source (Amazon Bedrock) using a POST request to Claude 3 model, the body of the request includes the prompt string as part of a messages array.


export function request(ctx) {
  const { ingredients = [] } = ctx.args;

  const prompt = `Suggest a recipe idea using these ingredients : ${ingredients.join(
    ","
  )}.`;

  return {
    resourcePath: `/model/anthropic.claude-3-sonnet-20240229-v1:0/invoke`,
    method: "POST",
    params: {
      headers: {
        "Content-Type": "application/json",
      },
      body: {
        anthropic_version: "bedrock-2023-05-31",
        max_tokens: 1000,
        messages: [
          {
            role: "user",
            content: [
              {
                type: "text",
                text: `\n\nHuman:${prompt}\n\nAssistant:`,
              },
            ],
          },
        ],
      },
    },
  };
}

export function response(ctx) {
  return {
    body: ctx.result.body,
  };
}

When you run the app (as demonstrated in the next section), a file named amplifyconfiguration.json is generated automatically. This file holds your API’s endpoint details. In the src/app/amplify-utils.ts, we initialize and configure the Amplify client library as shown below. Then, we create a data client to facilitate fully-typed API requests to the Amplify backend.


import config from "@/../amplifyconfiguration.json";
import { Amplify } from "aws-amplify";
import { generateClient } from "aws-amplify/data";
import { type Schema } from "../../amplify/data/resource";

Amplify.configure(config);

export const amplifyClient = generateClient<Schema>();

The app uses the src/app/page.tsx file to present a form to users for submitting a list of ingredients. Once submitted, the function generateRecipe in the src/app/actions.ts file is called to retrieve the generated recipe and display it to the user.

....

  const onSubmit = async (event: FormEvent<HTMLFormElement>) => {
    setloading(true);
    event.preventDefault();

    try {
      const formData = new FormData(event.currentTarget);
      const data = await generateRecipe(formData);
      const recipe = typeof data === "string" ? data : "No data returned";
      setloading(false);
      setResult(recipe);
    } catch (e) {
      alert(`An error occurred: ${e}`);
    }
  };
  
  ....


    <form
        onSubmit={onSubmit}
        className=" p-4 flex flex-col items-center gap-4  max-w-full mx-auto"
    >
        <input
        type="text"
        id="ingredients"
        name="ingredients"
        required
        placeholder="Ingredient1, Ingredient2, Ingredient3,etc"
        className="border border-black  text-gray-900 p-4 rounded-lg max-w-full w-full text-xl "
        />
        <button
        type="submit"
        className="  text-white p-2 rounded-lg bg-blue-500   w-1/2 text-xl  "
        >
        Generate
        </button>
    </form>
    
 ...
 
    {loading ? (
    <div className="flex flex-col items-center gap-4 w-1/2  mx-auto ">
        <h2 className="m-10 font-medium   text-xl   max-w-prose text-blue-600 ">
        Wait for it...
        </h2>

    </div>
    ) : (
    <div>
        {result ? (
        <section className="    mt-10 mx-auto  border border-black  bg-gray-50  rounded-xl     ">
            <Card className=" p-4 flex flex-col items-center gap-4  max-w-full mx-auto text-xl  font-semibold    ">
            <h2 className="whitespace-pre-wrap">{result}</h2>
            </Card>
        </section>
        ) : null}
    </div>
    )}

In the src/app/actions.ts file, you’ll find the generateRecipe function. This function leverages the Amplify client to invoke the askBedrock query, passing the ingredients as parameters to retrieve an AI-generated recipe from Amazon Bedrock.


import { amplifyClient } from "./amplify-utils";

export async function generateRecipe(formData: FormData) {
  const response = await amplifyClient.queries.askBedrock({
    ingredients: [formData.get("ingredients")?.toString() || ""],
  });

  const res = JSON.parse(response.data?.body!);
  const content = res.content[0].text;
  return content || "";
}

Running the App

Step 1: Amplify provides each developer with a personal cloud sandbox environment, offering isolated development spaces for rapid building, testing, and iteration. To initiate a cloud sandbox environment, open a new terminal window and execute the following command:


npx amplify sandbox

Step 2: Execute the command below to start a localhost development server.

npm run dev

The Recipe AI App running in the browser

Deploy the App

Now that your app is functioning correctly, let’s deploy and host it on Amplify. Amplify provides a fully managed hosting service with built-in CI/CD, simplifying the setup of production and staging environments using Git branches. In Gen 2, each Git branch in your repository corresponds directly to a fullstack branch in Amplify.

Step 1: Sign in to the AWS console and select your desired AWS Region. Click on the Public Preview banner and choose “Try Amplify Gen 2.”

Amplify homepage on AWS console

Step 2: Choose Option 2: Start with an existing app and select GitHub, then proceed by selecting Next.

select option 2 then select Github

Step 3 Log in to GitHub and click on the “Authorize AWS Amplify” button.

Authorize Amplify to access your GitHub

Step 4: Choose the repository and the branch from the dropdown lists, then proceed by selecting Next.

Choose the repository and the branch from the dropdown lists

Note: If you don’t see the repository in the dropdown list, click on the “View GitHub permissions” button. Then, select the repository and click on the “Install & Authorize” button to authorize access for your repository.

click on the "View GitHub permissions" button to authorize Amplify access to your repository

Select the repository and click Install & Authorize button

Step 5: Review the settings and click on the Next button to proceed.

Review the settings

Step 6: Lastly, click on the “Save and deploy” button to initiate the deployment process.

click on the "Save and deploy" button

Step 7: Wait for the deployment process to finish, and the you can use the Visit deployed Url button to open the web app.

Click the Visit deployed Url button to open the web app

The Recipe AI App running in the browser

Clean up resources

Now that you’ve finished this walkthrough, you can delete the backend resources to prevent unexpected costs by deleting the app from the Amplify console, as shown below.

Click the Delete app button

Conclusion

Congratulations! You’ve successfully utilized AWS Amplify Gen 2 and Amazon Bedrock to develop an AI-powered Recipe Generator App. Additionally, you’ve deployed the app on AWS using Amplify Hosting. To get started with Amplify Gen 2, try out our Quickstart tutorial, and join our Community Discord to leave any feedback or feature requests.

Author:

Mo Malaka

Mo Malaka is a Senior Solution Architect on the AWS Amplify Team. The Solution Architecture team educates developers regarding products and offerings, and acts as the primary point of contact for assistance and feedback. Mo enjoys using technology to solve problems and making people’s lives easier. You can find Mo on YouTube or on Twitter.