Creating content with ChatGPT: Building a Dog Breed Information App
Tags: react, openai, chatgpt, typescript
Published: 2023-03-27 02:40:19

In this article, I explore the idea of leveraging ChatGPT to generate engaging content for a website featuring various dog breeds. The concept is simple: present users with a list of dog breeds sourced from an API, and upon selecting a breed, display three images accompanied by informative content about the chosen breed. Gathering information on all dog breeds across the globe can be a daunting task, but with the power of ChatGPT, this process can be significantly simplified.

To maintain security and avoid exposing our OpenAI API key, I’ll create an Express server that serves as an intermediary between our React application and both the Dog API and OpenAI API. So join us as we embark on this exciting journey to harness the capabilities of ChatGPT for dynamic content generation on a dog breed-focused website.

## Building a Dog Breed Information App with React + Typescript, Express, and OpenAI’s ChatGPT

In today’s fast-paced world, having instant access to information is crucial. Dog enthusiasts are no exception — whether you’re a potential dog owner, a seasoned dog trainer, or simply a curious individual, having quick access to reliable information about dog breeds can be immensely valuable. With this in mind, I’ve developed a cutting-edge Dog Breed Information App leveraging React, Express, and OpenAI’s ChatGPT to provide users with a seamless, interactive experience.
First, let’s delve into the backend aspect of our project: the Express server. In this endeavor, I’ll be utilizing two APIs:
1. Dog API: https://dog.ceo/dog-api/
2. OpenAI API: https://platform.openai.com/docs/api-reference/introduction

You might wonder why I’ve chosen Express for this task. Initially, Firebase seemed like a suitable intermediary; however, deploying Firebase Functions required an upgrade from the Spark plan (free) to the Blaze plan (pay-as-you-go). Therefore, I opted for Vercel as a more suitable alternative for our project to keep our costs minimal.

On a local folder you will need to install Express and Axios.

```
npm install express axios
```
ChatGPT provided us with valuable guidance that assisted in resolving issues within our code and during deployment on Vercel. Here’s the finalized version of our server.js file. If you only intend to run this locally, setting up CORS might not be necessary.

```js
// Import required packages
const express = require('express');
const cors = require('cors');
const axios = require('axios');

// Initialize the Express app
const app = express();
const port = process.env.PORT || 3001;

// Set up CORS options
const corsOptions = {
  origin: ['*', 'http://localhost:5173'],
  optionsSuccessStatus: 200,
};
app.use(cors(corsOptions));

// Enable JSON support
app.use(express.json());

// Global error handler
app.use((err, req, res, next) => {
  console.error(err.stack);
  res.status(500).send('An error occurred');
});

// Start the server
const server = app.listen(port, () => {
  console.log(`Server is running on http://localhost:${port}`);
});

// Set the server timeout to 30 seconds
server.timeout = 30000; // Set the timeout to 30 seconds (10000 ms)

// Set the Dog API endpoint
const dogApi = 'https://dog.ceo/api';

// Route to fetch all dog breeds
app.get('/dog-breeds/list/all', async (req, res) => {
  try {
    const response = await axios.get(`${dogApi}/breeds/list/all`);
    res.json(response.data);
  } catch (error) {
    console.error('Error fetching dog breeds list:', error);
    res
      .status(500)
      .json({ error: 'An error occurred while fetching the dog breeds list' });
  }
});

// Route to fetch images for a specific dog breed
app.get('/breed/:breed/images', async (req, res) => {
  try {
    const breed = req.params.breed;
    const response = await axios.get(`${dogApi}/breed/${breed}/images`);
    res.json(response.data);
  } catch (error) {
    console.error('Error fetching breed images:', error);
    res
      .status(500)
      .json({ error: 'An error occurred while fetching the breed images' });
  }
});

// OpenAI API key
const openApiKey = 'YOUR-OPENAI-API-KEY';

// Route to handle OpenAI chat requests
app.post('/openai-chat', async (req, res) => {
  console.log('Received request at /openai-chat');
  const { chatGptMessages } = req.body;

  try {
    const openaiResponse = await axios.post(
      'https://api.openai.com/v1/chat/completions',
      {
        model: 'gpt-3.5-turbo',
        messages: chatGptMessages,
        max_tokens: 2048,
        temperature: 0.8,
      },
      {
        headers: {
          'Content-Type': 'application/json',
          Authorization: `Bearer ${openApiKey}`,
        },
        timeout: 30000, // Set a custom timeout, e.g., 30 seconds
      },
    );
    res.json(openaiResponse.data);
  } catch (error) {
    console.error('OpenAI API Error:', error.message, error.stack);

    let errorMessage = error.message;
    if (error.response && error.response.data) {
      errorMessage = JSON.stringify(error.response.data);
    }

    res.status(500).json({
      error: 'An error occurred while communicating with the OpenAI API',
      details: errorMessage,
    });
  }
});
```
My _package.json_ file is simple, to start the project I only need to do: `npm start`.

```json
{
  "scripts": {
    "start": "node server.js"
  },
  "dependencies": {
    "axios": "^1.3.4",
    "cors": "^2.8.5",
    "express": "^4.18.2",
    "openai": "^3.2.1"
  }
}
```
And this is the vercel.json file I need to deploy my Express project in Vercel:

```json
{
 "version": 2,
 "builds": [{ "src": "server.js", "use": "@vercel/node" }],
 "routes": [{ "src": "/(.*)", "dest": "server.js" }]
}
```

React + Typescript
The second part of this project was developed with React+Typescript. Like in a previous post in Medium I used [Vite](https://vitejs.dev/) to create the project.

{% embed https://medium.com/@andreszv/react-typescript-chatgpt-b3595281be9c %}

Now, I have three components that I'm using in the App.tsx: Dogs, DogImage and ChatGpt.

### Dogs
It will get a list of dog breeds

```ts
import React from 'react';
import './App.css';
import { API_URL } from './configuration';

interface ComponentProps {
    onBreedClick: (breed: string) => void;
    onSubBreedClick?: (breed: string, sub: string) => void;
}

interface DogBreedsResponse {
    message: {
        [breed: string]: string[];
    };
    status: string;
}

export const Dogs: React.FC<ComponentProps> = ({
    onBreedClick,
    onSubBreedClick,
}) => {
    const [dogData, setDogData] = React.useState<
        DogBreedsResponse['message'] | undefined
    >(undefined);
    const getDogsList = async () => {
        const response = await fetch(`${API_URL}/dog-breeds/list/all`);
        const data = await response.json();
        setDogData(data.message);
    };
    const handleBreedClick = (breed: string) => {
        onBreedClick(breed);
    };
    const handleSubBreedClick = (breed: string, sub: string) => {
        onSubBreedClick?.(breed, sub);
    };
    React.useEffect(() => {
        getDogsList();
    }, []);
    return (
        <>
            <ul>
                {!!dogData &&
                    Object.entries(dogData).map(
                        ([breed, subBreeds]: [string, string[]]) => {
                            return subBreeds.length === 0 ? (
                                <li className="list-elem" key={breed}>
                                    <span
                                        onClick={() => handleBreedClick(breed)}
                                    >
                                        {breed}
                                    </span>
                                </li>
                            ) : (
                                <li className="list-elem" key={breed}>
                                    <span
                                        onClick={() => handleBreedClick(breed)}
                                    >
                                        {breed}
                                    </span>
                                    <ul>
                                        {subBreeds.map((sub) => {
                                            return (
                                                <li
                                                    className="list-elem"
                                                    key={sub}
                                                    onClick={() =>
                                                        handleSubBreedClick(
                                                            breed,
                                                            sub,
                                                        )
                                                    }
                                                >
                                                    <span>
                                                        {sub} {breed}
                                                    </span>
                                                </li>
                                            );
                                        })}
                                    </ul>
                                </li>
                            );
                        },
                    )}
            </ul>
        </>
    );
};
```

### DogImage
It will get an array of image URLs and randomly select 3.

```ts
import React from 'react';
import './App.css';
import { API_URL } from './configuration';

interface ComponentProps {
    dogBreed: string;
    dogSubBreed?: string;
}

interface DogApiImageResponse {
    message: string[];
    status: 'success' | 'error';
}

export const DogImage: React.FC<ComponentProps> = ({
    dogBreed,
    dogSubBreed,
}) => {
    const [dogImgs, setDogImgs] = React.useState<string[]>([]);
    const getDogImages = async () => {
        try {
            const response = await fetch(`${API_URL}/breed/${dogBreed}/images`);
            if (!response.ok) {
                throw new Error(
                    `Failed to fetch image. Status: ${response.status}`,
                );
            }
            const data: DogApiImageResponse = await response.json();
            let message: string[] = [];
            if (!dogSubBreed) {
                message = data.message;
            } else {
                message = data.message.filter((item) =>
                    item.includes(`${dogBreed}-${dogSubBreed}`),
                );
            }
            let images: string[] = [];
            if (message.length <= 3) {
                for (let i = 0; i < 3; i++) {
                    images.push(message[i]);
                }
            } else {
                for (let i = 0; i < 3; i++) {
                    images.push(
                        message[Math.floor(Math.random() * message.length)],
                    );
                }
            }

            setDogImgs(images);
        } catch (error) {
            console.error('Error fetching dog image:', error);
        }
    };

    React.useEffect(() => {
        if (dogBreed) getDogImages();
    }, [dogBreed]);
    return !dogImgs ? null : (
        <div>
            {dogImgs.map((dogImg) => {
                return (
                    <img
                        key={dogImg}
                        className="topImage"
                        src={dogImg}
                        alt={dogBreed}
                    />
                );
            })}
        </div>
    );
};
```

### ChatGPT
I’ll be posing three questions to ChatGPT about each dog breed:
1. “What can you tell me about <breed>?”
2. “Does a <breed> live longer?”
3. “Is a <breed> an aggressive dog breed?”
These queries can be expanded or made more specific, and if needed, we can even ask ChatGPT to supply us with more insightful questions. To conclude, we will utilize React Markdown to format the response.

```ts
import React from 'react';
import { ChatCompletionRequestMessageRoleEnum } from 'openai';
import ReactMarkdown from 'react-markdown';
import { API_URL } from './configuration';

export interface ComponentProps {
    dogBreed: string;
    dogSubBreed: string | undefined;
}

export const ChatGpt: React.FC<ComponentProps> = ({
    dogBreed,
    dogSubBreed,
}) => {
    const [response, setResponse] = React.useState<string | undefined>(
        undefined,
    );

    const chatGptMessages = [
        {
            role: ChatCompletionRequestMessageRoleEnum.User,
            content: `Is a ${
                !dogSubBreed ? '' : dogSubBreed
            } ${dogBreed} an aggressive dog breed?`,
        },
        {
            role: ChatCompletionRequestMessageRoleEnum.User,
            content: `Does a ${
                !dogSubBreed ? '' : dogSubBreed
            } ${dogBreed} lives longer?`,
        },
        {
            role: ChatCompletionRequestMessageRoleEnum.User,
            content: `What can you tell me about ${
                !dogSubBreed ? '' : dogSubBreed
            } ${dogBreed}?`,
        },
    ];

    const getOpenAIResponse = async () => {
        try {
            const response = await fetch(`${API_URL}/openai-chat`, {
                method: 'POST',
                headers: {
                    'Content-Type': 'application/json',
                },
                body: JSON.stringify({ chatGptMessages }),
            });

            const data = await response.json();
            console.log(data.choices[0].message?.content);
            setResponse(data.choices[0].message?.content);
        } catch (error) {
            console.error(error);
        }
    };

    React.useEffect(() => {
        console.log('Effect triggered for dogBreed:', dogBreed);
        setResponse('');
        if (!!dogBreed) getOpenAIResponse();
    }, [dogBreed]);

    return (
        <>
            {!dogBreed ? (
                <div>{'Click on a breed'}</div>
            ) : !response ? (
                <div>{'Loading...'}</div>
            ) : (
                <>
                    <h1 className="title1">
                        {dogBreed} {!dogSubBreed ? '' : dogSubBreed}
                    </h1>
                    <ReactMarkdown children={response} />
                </>
            )}
        </>
    );
};
```

Then our final App.tsx file will look like:

```ts
import React from "react";
import "./App.css";
import { ChatGpt } from "./ChatGpt";
import { DogImage } from "./DogImage";
import { Dogs } from "./Dogs";

const App: React.FC = () => {
  const [dogBreed, setDogBreed] = React.useState<string>('');
  const [dogSubBreed, setDogSubBreed] = React.useState<string | undefined>(undefined);
  const getBreed = (breed: string) => {
    if (breed !== dogBreed) {
      setDogBreed(breed);
      setDogSubBreed(undefined);
    }
  };
  const getBreedSubBreed = (breed: string, sub: string) => {
    if (breed !== dogBreed || sub !== dogSubBreed) {
      setDogBreed(breed);
      setDogSubBreed(sub);
    }
  };

  return (
    <div className="App">
      <div className="row">
        <div className="column">
          <Dogs onBreedClick={getBreed} onSubBreedClick={getBreedSubBreed}/>
        </div>
        <div className="column">
          <DogImage dogBreed={dogBreed} dogSubBreed={dogSubBreed} /> 
          <ChatGpt dogBreed={dogBreed} dogSubBreed={dogSubBreed} />
        </div>
      </div>
    </div>
  );
};

export default App;
```

Our final result:

![Final result Dog Breeds App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/kfcof3alrfk82c592aec.png)


In this project, it is essential to account for the potential delay in receiving responses from ChatGPT. To enhance the user experience, I could store the content locally, eliminating the need for subsequent requests to ChatGPT for the same breed. 
Additionally, implementing a backend database to store the information and periodically update it ensures that users have access to reliable, up-to-date data while also speeding up the retrieval process.

There is a [plethora of APIs](https://rapidapi.com/collection/list-of-free-apis) available that can be seamlessly integrated with OpenAI to enrich the information you obtain from them.

So, what’s next on your project horizon? The possibilities are endless!

This is the React + Typescript project:
https://github.com/andresz74/dogapi-chatgpt

And for the Express server, I already gave you ingredients :)

Have fun!