DEV Community

Alejandro AO
Alejandro AO

Posted on • Edited on

[Tutorial] Full-stack app with ChatGPT API, React and Node

Hello everyone! In this article, I will show you how to make an app that uses the OpenAI API. This API is a very powerful tool that allows you to generate text based on a prompt. We will be using React for the front end and Node.js for the backend.

Both the front end and the backend are going to be very simple. In our configuration we will have them both in the same repository. This is not the best way to do it, but it is the easiest way to get started.

What we are going to build

By the end of this article, you will have your own app that uses the OpenAI API to generate SQL queries based on what the user wants it to do.

The idea of this app that we are going to build is to take an input value from the user and convert it to an SQL query. For example, if the user submits the following text:

show 10 elements from a table called users where the column name is equal to "John" 
Enter fullscreen mode Exit fullscreen mode

Our app should return this:

SELECT * FROM users WHERE name = "John" LIMIT 10
Enter fullscreen mode Exit fullscreen mode

This is a spin-off of the example that OpenAI uses in their documentation. But we are going to build it from scratch. You can find their example here.

Step 1: Set up the project

Remember that we mentioned that we are going to have our front end and our back end in the same repository. To do this, let's create a new folder called openai-sql-generator. Inside of this folder, we are going to create our folder server and our folder client. But don't worry about creating the client folder just yet. We will do that in the next step.

mkdir openai-sql-generator
cd openai-sql-generator
mkdir server
Enter fullscreen mode Exit fullscreen mode

Step 2: Set up the front end

Now, we are going to start a new React app. We will be using Vite to do this. If you don’t know what Vite is, it is a new tool that allows you to create React apps without having to use create-react-app. It is very fast and it is very easy to use. So we run:

npm create vite@latest
Enter fullscreen mode Exit fullscreen mode

And then we name it client and select the react template. Now we can just install the dependencies and run the app.

cd client
npm install
npm run dev
Enter fullscreen mode Exit fullscreen mode

Now it's a good time to make your first commit. Go to the root folder of your project (the one that contains the server and client folders) and run the following commands:

git init
git add .
git commit -m "Initial commit"
git remote add origin # your GitHub repo URL
git push -u origin master
Enter fullscreen mode Exit fullscreen mode

Step 3: Create the main component

Main skeleton

The first thing that we are going to need is to create the skeleton of the React application. Our main component will look something like the code below. Since we are building this off of the example of the OpenAI documentation, here is the CSS code they used for this application. All you have to do is copy and paste it into a file called styles.module.css in your src folder.

We are also going to need to import the SQL server icon that we are going to use in our app. You can find it on Flaticon. You can download it and put it in a folder called assets in your src folder. Then you can import it in your main component.

import styles from "./styles.module.css";
import sqlServer from "./assets/sql-server.png";

export default function App() {
  return (
    <main className={styles.main}>
      <img src={sqlServer} className={styles.icon} alt="SQL server" />
      <h3>Generate SQL</h3>
      <form>
        <input
          type="text"
          name="query-description"
          placeholder="Describe your query"
        />
        <input type="submit" value="Generate query" />
      </form>
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Pretty good, huh? So far we just added the default styles that come with the demo app from the OpenAI documentation. Now let’s make that input field reactive.

Manage input and submit

As you may know, if you use React, we don’t want to forget to make the input field reactive and track its value within the state of the component. So let’s add a state to our component and make the input field reactive. We are going to use the useState hook to do this.

While we are at it, let’s also add a onSubmit function to our form. We will use this function to trigger the API call that we are going to make later on. But for now, let’s just log the user’s prompt in the console. Our component should look like this:

import styles from "./styles.module.css";
import sqlServer from "./assets/sql-server.png";
import { useState } from "react";

export default function App() {

  const [userPrompt, setUserPrompt] = useState("");

  const onSubmit = (e) => {
    e.preventDefault();
    console.log(userPrompt);
  };

  return (
    <main className={styles.main}>
      <img src={sqlServer} className={styles.icon} alt="SQL server" />
      <h3>Generate SQL</h3>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          name="query-description"
          placeholder="Describe your query"
          value={userPrompt}
          onChange={(e) => setUserPrompt(e.target.value)}
        />
        <input type="submit" value="Generate query" />
      </form>
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Great! Now we have a component that is reactive and that has an onSubmit function. But we still need to make the API call to OpenAI. Let's build our back end first. We will come back to this component later.

Step 4: Setup the back end

Create the API key

The first thing that we need to do is to create an API key for our app. To do this, we need to go to the OpenAI dashboard and create a new API key. You can name it whatever you want. I named mine openai-sql-generator. Then you can copy the key and save it somewhere safe. We will need it later on.

Initialize the Node.js project

We will use Node.js to create the API. So let's navigate to the server folder and initialize a new NPM project. We will also need to install the OpenAI SDK for Node.js and dotenv to be able to use environment variables.

cd server
npm init -y
npm install openai
npm install dotenv
Enter fullscreen mode Exit fullscreen mode

In case you don't know what environment variables are, they are variables that are stored outside of your code. They are usually used to store sensitive information such as API keys. You can read more about them here.

Create the .env file

Now we are going to create a new file called .env in the root of our project. This is where we are going to store our API key. We will also need to create a new file called .env.example. This is where we are going to store the structure of our .env file. It is a good practice to do this so that other developers know what environment variables they need to set up in order to run your project. So let's add the following code to our .env.example file:

OPENAI_API_KEY=
Enter fullscreen mode Exit fullscreen mode

Now we can add our API key to the .env file. We will also need to add this file to our .gitignore file so that we don't accidentally push it to GitHub (what would be the point of having a secret key in our .env file if we are going to push it to GitHub?). So let's add the following code to our .gitignore file:

.env
Enter fullscreen mode Exit fullscreen mode

Step 5: Create the API

Create api.js and index.js

Now we can create the API. We will create a new file called api.js in the root of our project. This is where we are going to make the API call to OpenAI. We will also need to import the OpenAI SDK and the dotenv package. We will also need to import the OPENAI_API_KEY environment variable from our .env file. We will use this key to make the API call. Our api.js file should look like this:

// api.js

import { Configuration, OpenAIApi } from "openai";
import dotenv from "dotenv";
dotenv.config();

const openaiApiKey = process.env.OPENAI_API_KEY;

if (!openaiApiKey) {
  console.error('OPENAI_API_KEY is not set.');
  process.exit(1);
}

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

export default openai;

Enter fullscreen mode Exit fullscreen mode

Now we will have to install the express package to create the API endpoint. We will also need to install the cors package to allow cross-origin requests. We will need this because we are going to make the API call from a different domain than the one where the API is hosted. You can read more about this here.

npm install express
npm install cors
Enter fullscreen mode Exit fullscreen mode

Now we can create the API endpoint. Inside the server folder, create a new file called index.js. This is where we are going to create the API endpoint. We will also need to import the express and cors packages, and the api.js file that we created earlier. We will use this file to make the API call to OpenAI. Our index.js file should look like this:

// index.js

import express from "express";
import cors from "cors";

const app = express();  
app.use(cors());

const port = process.env.PORT || 3002;

app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Listening on port ${port}...`);
});
Enter fullscreen mode Exit fullscreen mode

There we go! That's the configuration file for our API. Now we can start the API by running the following command:

node index.js
Enter fullscreen mode Exit fullscreen mode

If everything went well, you should see the following message in your terminal:

Example app listening at http://localhost:3001
Enter fullscreen mode Exit fullscreen mode

Now we can test our API by going to http://localhost:3001 in our browser. You should see the following message:

Hello World!
Enter fullscreen mode Exit fullscreen mode

Great! Now we have a working API. But we still need to create an endpoint that will make the API call to OpenAI and generate our SQL query. Let's do that now.

Create the API endpoint

Now we can create the API endpoint that will make the API call to OpenAI. We will create a new file called generate.js in the server folder. This is where we are going to make the API call to OpenAI. We will also need to import the openaiClient object that we created in the api.js file. We will use this object to make the API call. Our generate.js file should look like this:

// generate.js

import openaiClient from "./api.js";

const generate = async (queryDescription) => {
  const response = await openaiClient.createCompletion({
    model: 'text-davinci-003',
    prompt: `Convert the following natural language description into a SQL query:\n\n${queryDescription}.`,
    max_tokens: 100,
    temperature: 0,
  });
  return response.data.choices[0].text;
};

export default generate;

Enter fullscreen mode Exit fullscreen mode

Period at the end of the prompt

Be careful when designing your prompt. If you don't design it correctly, the API will not be able to generate the response you want. For example, note that in the example above, there is a period at the end of the prompt. This is important because this is a text-completion API. If you don't add the period, it might try to complete your prompt instead of generating the response.

For example, if you try to generate a SQL query for the following prompt:

"Convert the following natural language description into a SQL query:\n\nSelect all the users from the users table"
Enter fullscreen mode Exit fullscreen mode

You will get the following response:

"where the lastname equals 'smith' and the firstname equals 'john'\n\nSELECT * FROM users WHERE lastname = 'smith' AND firstname = 'john';"
Enter fullscreen mode Exit fullscreen mode

Did you see that? It completed your own prompt before generating the response. So be sure to add the period at the end.

Now we can create our endpoint. We will create a new route called /generate that will take a queryDescription parameter. We will use this parameter to make the API call to OpenAI. Note that we are using a POST request instead of a GET request. This is because we are sending a body with the request. Our index.js file should look like this:

// index.js

// ...

app.post("/generate", async (req, res) => {
  const { prompt } = req.query;
  try {
    const sqlQuery = await generate(prompt);
    res.json({ sqlQuery });
  } catch (error) {
    console.error(error);
    res.status(500).send("Internal Server Error");
  }
});
Enter fullscreen mode Exit fullscreen mode

Now we can test our API by using a tool like Postman or Thunder Client. Send a POST request to http://localhost:3001/generate with the following body:

{
  "prompt": "Select all the users from the users table"
}
Enter fullscreen mode Exit fullscreen mode

You should see the following message:

{
  "sqlQuery": "SELECT * FROM users;"
}
Enter fullscreen mode Exit fullscreen mode

Great! Now we have a working API endpoint that will make the API call to OpenAI and generate our SQL query. But we still need to make our frontend communicate with this API endpoint. Let's do that now.

Step 6: Call the API from the frontend

So our API is working and our frontend is ready. Let's just make the connection between them. Many people like to work with Axios, but I prefer to use the Fetch API. So let's use the Fetch API to make the API call to our API endpoint.

We will do this directly inside the component to keep things simple and since the frontend part is not the central part of this tutorial. So let's go to the App.js file and complete the function onSubmit that we previously created.

// app.jsx

// ...

export default function App() {

  // ...

  const onSubmit = async (e) => {
    e.preventDefault();
    const query = await generateQuery();
    console.log(query)
  };

  const generateQuery = async () => {
    const response = await fetch("http://localhost:3002/generate", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ queryDescription: userPrompt }),
    });

    const data = await response.json();
    return data.sqlQuery.trim();
  };

  // ...
}
Enter fullscreen mode Exit fullscreen mode

Display the generated query

Finally, we can display the generated query. We will do this simply by adding a <pre> tag to our App.js file and setting a state variable to store the generated query. Our App.js file should look like this:

import styles from "./styles.module.css";
import sqlServer from "./assets/sql-server.png";
import { useState } from "react";

export default function App() {
  const [userPrompt, setUserPrompt] = useState("");
  const [sqlQuery, setSqlQuery] = useState("");

  const onSubmit = async (e) => {
    e.preventDefault();
    const query = await generateQuery();
    setSqlQuery(query);
  };

  const generateQuery = async () => {
    const response = await fetch("http://localhost:3002/generate", {
      method: "POST",
      headers: {
        "Content-Type": "application/json",
      },
      body: JSON.stringify({ queryDescription: userPrompt }),
    });

    const data = await response.json();
    return data.sqlQuery.trim();
  };

  return (
    <main className={styles.main}>
      <img src={sqlServer} className={styles.icon} alt="SQL server" />
      <h3>Generate SQL</h3>
      <form onSubmit={onSubmit}>
        <input
          type="text"
          name="query-description"
          placeholder="Describe your query"
          value={userPrompt}
          onChange={(e) => setUserPrompt(e.target.value)}
        />
        <input type="submit" value="Generate query" />
      </form>
      <pre>{sqlQuery}</pre>
    </main>
  );
}

Enter fullscreen mode Exit fullscreen mode

Use the new ChatGPT API instead

I recommend that you use the new ChatGPT API instead of the TextGPT API. It is cheaper and works better! Once you have understood how to use the TextGPT API, you can easily switch to the ChatGPT API. All you have to do is change the model parameter in the generate.js file and feed it a conversation instead of a prompt.

You will also need to update the method that makes the API call to OpenAI. You can do this by changing the createCompletion method to the createChatCompletion method.

Here is an example of how our current app would look like if we used the ChatGPT API instead of the TextGPT API:

import openaiClient from "./api.js";

const generate = async (queryDescription) => {

  const daVinci = async (queryDescription) => {
    const response = await openaiClient.createCompletion({
      model: 'text-davinci-003',
      prompt: `Convert the following natural language description into a SQL query:\n\n${queryDescription}`,
      max_tokens: 100,
      temperature: 0,
    });
    return response.data.choices[0].text;
  };

  const chatGPT = async (queryDescription) => {
    const message = [
      { role: "system", content: `You are a translator from plain English to SQL.` },
      { role: "user", content: `Convert the following natural language description into a SQL query:\n\nShow all all the elements in the table users` },
      { role: "assistant", content: "SELECT * FROM users;" },
      { role: "user", content: `Convert the following natural language description into a SQL query:\n\n${queryDescription}` },
    ];
    const response = await openaiClient.createChatCompletion({
      model: "gpt-3.5-turbo",
      messages: message,
    });

    return response.data.choices[0].message.content;
  }

  const sqlQuery = await chatGPT(queryDescription);
  return sqlQuery;

};

export default generate;
Enter fullscreen mode Exit fullscreen mode

Conclusion

And that's it! We have successfully created a web app that can generate SQL queries from natural language descriptions. We have used OpenAI's GPT-3 API to generate the SQL queries. We have also used React to create the frontend and Express to create the API. And we also learned how to use the TextGPT API and the ChatGPT API.

Note: This is a repost from my personal blog

Top comments (0)