Fundamentals: How to Start Building a ChatGPT Powered Next JS App

A ChatGPT-powered app is all about being able to accomplish the following:

  • Send prompt to the OpenAI API and then parse the API response; and

  • Store and/or display the data response to the web app;

The process of accomplishing these tasks in Next.js is surprisingly straightforward. All you need to do is set up your API route handler to send prompts to the OpenAI API using the POST method, await and parse the response, store it (e.g., using useState), and then display or use it within your React component.

A crucial part of building the app involves initializing the API handler in your Next.js project. While many online resources on platforms like YouTube often suggest using TypeScript to create a Next.js app, it's important to note that you can build applications without TypeScript as well. Opting for JavaScript instead simplifies the development process and allows you to quickly prototype apps.

Pre-requisites

  • OpenAI API Key

  • An existing Next JS project that does not use TypeScript

Install OpenAI Library

First, install openai library to your project folder.

npm install openai

Create Utility Files for Configuration

Create a utils folder inside the src folder. Then createopenai.js file. Inside it, paste the following code. This is from the openai website, so nothing fancy here.

const { Configuration, OpenAIApi } = require("openai");

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

export default openai

As you can see in the apiKey value, the code expects that the OPEN_API_KEY is already defined in an .env file, which should be at the root of your project folder.

Build the API Route Handler Function

Go to the src folder, then pages folder, then api folder. Inside lies the file where we should create our API route handler. For demonstration purposes, let's create a hello.js file inside. We'll be using this code:

import openai from '@/utils/openai.js';

export default async function handler(req, res) {
  const prompt = "Please give a nice meme about Spiderman";

  const aiResult = await openai.createChatCompletion({
    model: 'gpt-3.5-turbo',
    max_tokens: 2048,
    messages: [
      {
        role: 'user',
        content: `${prompt}`,
      },
    ],
  });

  const responseText = aiResult.data.choices[0].message.content;

  res.status(200).json({ responseText });
}

What we've done so far is we've defined an API endpoint in NextJS called api/hello. Here's what happening on the code:

  1. openai module is imported from the utility file we created earlier, allowing us to create ChatCompletion objects.

  2. The asynchronous handler function takes req (request) and res (response) as parameters. The

  3. The prompt constant contains our prompt. For now, we're assigning this with a fixed value (i.e. "Please give a nice meme about Spiderman")

  4. The aiResult constant which calls and awaits the openai.createChatCompletion function to send a request to the OpenAI API to generate ChatGPT's chat response based on the inputted properties, which includes the prompt. To be clear, the inputted properties are what is inside the openai.createChatCompletion() :

    • model: Specifies the version or type of the OpenAI model to use. In this case, we use 'gpt-3.5-turbo'.

    • max_tokens: Specifies the maximum number of tokens to generate in the response. Tokens can be roughly thought of as individual words or characters in the model's language.

    • messages: An array containing an object with two properties: role and content. This is our message to ChatGPT which comes from the prompt constant variable.

  5. Once the response arrives/is completed from the aiResult API call, the responseText variable extracts the content of the generated response from the aiResult object. It assumes that the response is stored in aiResult.data.choices[0].message.content.

  6. Finally, the code sends a JSON response back to the client with a status code of 200 (indicating a successful response). The response includes an object with a responseText property containing the generated text.

We can now test if the API endpoint route is working. Run the npm run dev to your terminal to start your NextJS app. A URL address should be provided where you can access your app. Since we are testing the API, we're targeting to go at the URL address with /api/hello at the tail. So go ahead and open your browser, and go to the target address (in my case, I went to http://localhost:3000/api/hello).

The page should display the ChatGPT's response to your prompt in JSON format. (Remember that our prompt is Please give a nice meme about Spiderman). I got this response in my page:

{"responseText":"Sorry, as an AI language model, I am not capable of creating visual content like memes or pictures, but I can help you generate memes textually. Here's one:\n\nCaption: When you finally get a date but can't decide which Spidey suit to wear\n\nImage: A split-image of Spiderman looking conflicted with different suits on either side"}

If you got your response as well, your API route handler is working! However, before we proceed, we need to modify the constant prompt (inside the handler function) to facilitate what we will demonstrate in the next sections:

const prompt = req.body.prompt;

This just means that the prompt will now be coming from the req argument that the handler will accept. The rationale behind this comes full circle in the next section.

Send Prompt and Get ChatGPT's Response via API Route Handler

The other half of the work is figuring out how to send a prompt from your web app. What's interesting here is that you can use a user's input to send a dynamic prompt. This is done by using a fetch function to send a POST request to the API endpoint of our app.

At the index.js file of your NextJS (inside the pages folder), let's make a React component to test this:

import { useState } from 'react';

function TestPage() {
  const [aiReply, setAIReply] = useState('');

  const fetchChatGPTResponse = async (prompt) => {
    try {
      const response = await fetch('/api/hello', {
        method: 'POST',
        body: JSON.stringify({ prompt }),
        headers: { 'Content-Type': 'application/json' },
      });

      const data = await response.json();
      const responseText = data.responseText;
      setAIReply(responseText);
    } catch (error) {
      console.error('Error:', error);
    }
  };

  return (
    <div>
      <button
        className="block"
        onClick={() => fetchChatGPTResponse('Give nice meme about Deadpool')}
      >
        Deadpool
      </button>
      <button
        className="block"
        onClick={() => fetchChatGPTResponse('Give nice meme about Wolverine')}
      >
        Wolverine
      </button>
      <div>{aiReply}</div>
    </div>
  );
}

export default TestPage;

What essentially happens here is that:

  1. We use useState hook to store the ChatGPT's response, which our state variable is named aiReply.

  2. An async function named fetchChatGPTResponse is our main tool to accomplish the following:

    • Fetch data to our API endpoint api/hello via POST method, where the body of our request is the prompt argument. Focus on this part of the syntax:

        ....
        <button className="block"
         onClick={() => fetchChatGPTResponse('Give nice meme about  Deadpool')}> Deadpool
         </button>
        <button className="block"
         onClick={() => fetchChatGPTResponse('Give nice meme about Wolverine')}>
            Wolverine
        </button>
        ....
      
    • The prompt argument is taken from the button (click event). This is to simplify user's input to determine the prompt. You can change this into an input textbox or any mechanism to get user's input to manipulate the prompt.

    • Also, remember our API route handler? Since we modified the prompt variable to get the data from the req argument via const prompt = req.body.prompt, the message that will be sent to OpenAI is the prompt argument that is passed to the fetchChatGPTResponse.

    • Await API endpoint's response, and then parse the response to string.

    • Store the parsed data to our aiReply hook variable (using setAIReply)

  3. Once the fetchChatGPTResponse is successfully called, meaning the ChatGPT's response is stored to aiReply hook, our page will automatically display the response in the designated div element.

     ....
      <div>{aiReply}</div>
     ....
    

To test, click either Deadpool or Wolverine. Here's what I got as response:

And this is what I got for Wolverine. Yeah, ChatGPT's data on memes sucks:

Overall, what we have done so far is send prompts to ChatGPT based on inputs from the web app and display the responses. Understanding this concept has become a necessary requirement for every developer, similar to knowing how to create a simple CRUD app. However, one area that still requires refinement is the art of engineering prompts to get useful responses from ChatGPT. By mastering this skill, we can leverage the full potential of ChatGPT and enhance our applications with additional functionalities