Building a Recipe Nutrition App with OpenAI, Copilot, and Next.js

Large Language Models (LLMs) aren't just for chatbots and text generation—they can solve everyday problems when integrated into practical applications. One useful example is extracting nutritional information from recipe text, something that typically requires complex natural language parsing. By combining GitHub Copilot, OpenAI's GPT-3.5-turbo model, Next.js, and React, you can build such an app quickly and with minimal boilerplate.

Project Setup and Initial Dependencies

Start by creating a new repository from the GitHub Codespaces Next.js template. Click "Use this template" and create a new repository with a name of your choosing. Clone it locally and open it in your code editor.

In your terminal, install the required dependencies:

npm i express openai dotenv @material-ui/core @material-ui/icons

Then install the dev dependency:

npm i --save-dev nodemon

Getting Your OpenAI API Key

Navigate to OpenAI's developer platform, sign in, and select "View API keys" from your profile menu. Create a new secret key and store it securely. Add it to a .env file at the project root as OPENAI_API_KEY, and make sure .env is listed in your gitignore file.

Installing GitHub Copilot

Install the GitHub Copilot extension from your editor's extensions panel. Since Copilot generates suggestions based on your prompts and comments, being explicit in your instructions is critical—the clarity of your input directly impacts the quality of the generated code.

Building the Express Server

Create a folder called api inside the pages directory, then add a server.js file. Write a prompt asking Copilot to build a simple Express server:

Create a server with the following specifications:

1. import express and dotenv node modules
3. create the server with express and name it app
4. use port 8080 as default port
5. enable body parser to accept json data
6. state which port the server is listening to and log it to the console

Accept Copilot's suggestions by pressing tab. Update the package.json file to include the script:

"devserver": "nodemon pages/api/server.js"

Run npm run devserver to start the server.

Creating the Controller

Create a new file generateInfo.js in the api folder. Use a comment to instruct Copilot on what to build:

Create a controller with the following specifications:

1. import the Configuration class and the OpenAIApi class from the openai npm module
2. create a new configuration object that includes the api key and uses the Configuration class from the openai module
3. create a new instance of the OpenAIApi class and pass in the configuration object
4. create an async function called generateInfo that accepts a request and response object as parameters
5. use try to make a request to the OpenAI completetion api and return the response
6. use catch to catch any errors and return the error include a message to the user
7. export the generateInfo function as a module

Copilot will generate a controller, but you'll need to customize it. Since you're using the gpt-3.5-turbo model, the generated code likely uses the davinci engine and may include unnecessary parameters. Update the controller to use the completion API with the following structure:

Create a data folder at the project root and add a prompt.json file containing:

{
"recipePrompt": "I want you to act as a Nutrition Facts Generator. I will provide you with a recipe and your role is to generate nutrition facts for that recipe. You should use your knowledge of nutrition science, nutrition facts labels and other relevant information to generate nutritional information for the recipe. Add each nutrition fact to a new line. I want you to only reply with the nutrition fact. Do not provide any other information. My first request is: "
}

Import this prompt into your controller and write the core function:

// add the prompt to the top of the file
const { recipePrompt } = require('../../data/recipe.json');

// update this function to include the recipe before the try
const generateInfo = async(req, res) => {
const { recipe } = req.body
}

Then update the try block with the completion parameters—max_tokens, prompt, model, temperature, and n—and the response handling:

model: "gpt-3.5-turbo",
messages: [{ role: "user", content: `${recipePrompt}${recipe}` }],
max_tokens: 200,
temperature: 0,
n: 1,
const response = completion.data.choices[0].message.content;

return res.status(200).json({
success: true,
data: response,
});

Update the catch block to return more descriptive error codes, including handling for a 401 invalid API key:

catch (error) {
if (error.response.status === 401) {
return res.status(401).json({
error: "Please provide a valid API key.",
});
}
return res.status(500).json({
error:
"An error occurred while generating recipe information. Please try again later.",
});
}

Your finished controller should resemble:

const { Configuration, OpenAIApi } = require("openai");
const { recipePrompt } = require("../../data/recipe.json");

const config = new Configuration({
apiKey: process.env.OPENAI_API_KEY,
});

const openai = new OpenAIApi(config);

const generateInfo = async (req, res) => {
const { recipe } = req.body;

try {
const completion = await openai.createChatCompletion({
model: "gpt-3.5-turbo",
messages: [{ role: "user", content: `${recipePrompt}${recipe}` }],
max_tokens: 200,
temperature: 0,
n: 1,
});
const response = completion.data.choices[0].message.content;

return res.status(200).json({
success: true,
data: response,
});
} catch (error) {
console.log(error);
if (error.response.status === 401) {
return res.status(401).json({
error: "Please provide a valid API key.",
});
}
return res.status(500).json({
error:
"An error occurred while generating recipe information. Please try again later.",
});
}
};

module.exports = { generateInfo };

Router and Testing with Postman

Create a router.js file and let Copilot assist with the Express router:

Add the router to server.js:

app.use('/openai', require('./router'));

Test the route in Postman with a POST request to http://localhost:3001/api/generateInfo. Add a recipe such as:

data: '{"model":"text-davinci-003","prompt":"I want you to act as a Nutrition Facts Generator. I will provide you with a recipe and your role is to generate nutrition facts for that recipe. You should use your knowledge of nutrition science, nutrition facts labels and other relevant information to generate nutritional information for the recipe. Add each nutrition fact to a new line. I want you to only reply with the nutrition fact. Do not provide any other information. My first request is: 1 cup of all purpose flour, sifted 1 1/2 teaspoon baking powder 1/4 teaspoon salt 2 Tablespoon granulated sugar 1/2 Tablespoon unsalted butter, room temperature Approximately 1/3 cup water","max_tokens":200,"temperature":0.5,"n":1}',

You should receive a successful response containing the nutritional data generated by OpenAI.

Building the React Frontend

Delete the existing code in index.js and use a comment to prompt Copilot to build a simple text area:

Create a text area with the following specifications:
1. a H1 with the text "Find Nutrition Facts for any recipe"
2. a text area for users to upload recipe
3. a button for users to submit the entered recipe
4. a section at the bottom to display nutrition facts
5. Get the data from this link: http://localhost:8080/openai/generateinfo
6. Name the component RecipeInfo

Accept the generated code and run npm run dev. At localhost:3000 you'll see a basic form. Testing it will likely produce a CORS error in the browser console. To resolve this, ask Copilot Chat how to fix it or install the cors middleware:

const cors = require("cors");

// Allow cross-origin requests (CORS)
app.use(cors());

Update router.js to use the middleware:

router.options("/generateInfo", (req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*");
res.setHeader("Access-Control-Allow-Headers", "*");
res.setHeader("Access-Control-Allow-Methods", "*");
res.sendStatus(200);
});

Retry the request—you'll now see a different error. The API response returns an object with success and data keys, so update the frontend's submit handler to extract data from the response:

const recipeInfo = await response.json();
setNutrition(recipeInfo.data);

At this point, the core app is functional: entering a recipe returns its nutritional breakdown.

Polishing with Material UI

With the backend and frontend working, use GitHub Copilot Chat to improve the UI. Ask it to implement material-ui components and make iterative improvements:

  • Make the text area larger and implement Material UI
  • Wrap content in a Material UI Paper component
  • Add a "clear" button that resets the text area and results
  • Show a loader while data is being fetched
  • Add a custom theme with primary and secondary colors
  • Prevent the text area from overflowing the paper container
  • Add a footer component
update the component to use material ui with the content centered and the buttoned positioned below the text area. use Grid from material ui and any other components needed.
add a button to the app to clear the text in the textarea
how do I create a custom theme with material ui and where do I create the custom theme?
in the highlihghted code how do I prevent the text area line from going over the paper component?
Create a footer component with the following specifications:
1. The footer must be fixed at the bottom of the page
2. Use the Paper component from Material UI
3. Use the Typography component from Material UI
3. The text must say "Made with ❤️ by LadyKerr & GitHub Copilot" and "Powered by OpenAI"
4. The text "GitHub Copilot" must be a link to https://copilot.github.com/ that opens in a new tab with alt text " GitHub Copilot"
5. The text "OpenAI" must be a link to https://openai.com/ that opens in a new tab with alt text "openai api"

Separating Components

Create a new component NutritionFacts.js in the components folder and prompt Copilot to split each nutrition fact onto its own line:

Create a component with the following specifications:
1. the component must split the received string data at /n/n or /n and return a Typography component for each string
2. the component must set a unique key for each Typography component
3. the component must return a div with the Typography components
4. the component must return null if the data is not a string
5. Name the component NutritionFacts
6. Use the Paper Component from Material UI
7. Add text above the data that says "Here are the nutrition facts for your recipe:"

Import this component into index.js. To keep the codebase clean, move the header into its own Header.js component as well. The final app accepts any recipe text and returns nutritional data formatted clearly.

The complete code is available in the mealmetrics-copilot repository. From there, deployment steps would include hosting the frontend on GitHub Pages and the server on a service like Azure.