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.
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.
Step 2: Choose the Claude model, then click on the Request model access button.
Step 3: Select the Manage model access button
Step 4: Check the Claude 3 Sonnet option, then click on the “Save changes” button.
Cloning the repo
Step 1: Navigate to the repository on AWS Samples and fork it to your GitHub repositories
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.
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 GraphQL API schema consists of two main parts:
- The
askBedrock
query takes an array of strings calledingredients
and returns aBedrockResponse
. 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 inbedrock.js
, usingbedrockDS
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 theaskBedrock
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
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.”
Step 2: Choose Option 2: Start with an existing app and select GitHub, then proceed by selecting Next.
Step 3 Log in to GitHub and click on the “Authorize AWS Amplify” button.
Step 4: Choose the repository and the branch from the dropdown lists, then proceed by selecting Next.
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.
Step 5: Review the settings and click on the Next button to proceed.
Step 6: Lastly, click on the “Save and deploy” button to initiate the deployment process.
Step 7: Wait for the deployment process to finish, and the you can use the Visit deployed Url button to open the web app.
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.
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: