Rafal Zawadzki
Posted on September 13, 2023
Hi there! Thought Iād share this short guide on how I made a chatbot that lets users converse with the Reddit API. As a dev and Reddit fan, I wanted to try creating a new way of interacting with the platform. Here's how I did it using NextJS, Chatwith and OpenAPI!
Here is the source code if you want to jump right into it, and here is the final app: https://redditbot.chat
Project features
In summary, the project has:
- A thin wrapper over the Reddit API to expose only useful endpoints and trim data payloads
- An OpenAPI spec file in YAML for the Chatwith chatbot
- A landing page with a chatbot iframe embed
Arguably most of the heavy lifting has been done by Chatwith. This project focuses on creating an API for the chatbot to interact with and setting up Chatwith to build the chatbot.
I used NextJS for this project because I also wanted to make a simple landing page - but you can easily create the API with eg. an Express backend.
Getting started
I used the plain NextJS 13 starter with the default configuration:
bunx create-next-app
You can see I'm using Bun which is the new JS all-in-one toolkit. You can also use npm
or pnpm
as a replacement in this project.
After creating the project, quickly check if it runs on localhost:
bun dev
Creating Reddit API wrapper
The essential component of this project is the Reddit API wrapper. It serves as a mediator between our chatbot and the Reddit API. Its role is to reduce the amount of data transferred and to simplify the API for the chatbot to understand.
Here's why the wrapper is important:
- Use only selected endpoints: The Reddit API has a myriad of endpoints for various functionalities. Our chatbot only needs a subset of these. The wrapper makes sure to expose only the necessary ones.
- Payload size: Reddit API responses contain a ton of information that we don't need. Because GPT-3.5 and GPT-4 from OpenAI accept limited tokens, the wrapper trims the API responses to contain only the data the bot will actually use.
API endpoints
Check the official Reddit API documentation for the full specification of all endpoints.
For now we'll add the following Reddit API endpoints in our wrapper:
-
/r/{subreddit}/{listing}.json
: Fetches stories from a specific subreddit based on the type of listing (hot, new, rising, etc.) -
/{listing}.json
: Fetches stories from all subreddits based on the type of listing. -
/r/{subreddit}/search.json
: Allows us to search for a topic within any subreddit. -
/search.json
: Enables a search across all subreddits. -
/r/{subreddit}/comments/{post_id}.json
: Retrieves comments from a specific post within a subreddit.
Create API endpoint
Let's create a new NextJS route handler which will:
- relay requests to Reddit API
- trim the payloads and return a response
First, create a new file: /app/api/[...catchall]/route.ts
[...catchall]
is the name of the directory and means that any path at/api/
will be handled by this route (so/api/search
,/api/anything/goes
etc). Learn more here.
In this file is a GET handler:
export const runtime = "edge";
export const revalidate = 10;
export async function GET(request: Request) {
const urlObj = new URL(request.url);
const path = urlObj.pathname.replace("/api/", "");
const queryParams = urlObj.search;
try {
const redditResponse = await fetch(
`https://api.reddit.com/${path}${queryParams}`
);
const redditData = await redditResponse.json();
if (isPostListingResponse(redditData)) {
// Handle posts
const summaries = redditData.data.children.map(extractPostSummary);
return NextResponse.json(summaries);
} else if (isCommentsResponse(redditData)) {
if (path.startsWith("r/") && path.includes("/comments/")) {
// Handle comments
const comments = redditData[1].data.children.map(extractCommentSummary);
return NextResponse.json(comments);
}
} else {
return new Response(null, {
status: 400,
statusText: "Invalid Reddit API response",
});
}
} catch (error) {
console.error(error);
return new Response(null, {
status: 500,
statusText: "Failed to fetch data from Reddit",
});
}
}
Let break it down step by step!
We'll use the Edge runtime for lowest latency and add short caching to not get rate limited by the Reddit API:
export const runtime = "edge";
export const revalidate = 10;
Then, we clean up the request path so we can switch the host from our API to Reddit one:
const urlObj = new URL(request.url);
const path = urlObj.pathname.replace("/api/", "");
const queryParams = urlObj.search;
Next, we make a request to Reddit API and parse the response JSON:
const redditResponse = await fetch(`https://api.reddit.com/${path}${queryParams}`);
const redditData = await redditResponse.json();
Then we fiddle with data structures to recognize what kind of response we're dealing with so we can trim it and return back to the client:
if (isPostListingResponse(redditData)) {
// Handle posts
const summaries = redditData.data.children.map(extractPostSummary);
return NextResponse.json(summaries);
} else if (isCommentsResponse(redditData)) {
if (path.startsWith("r/") && path.includes("/comments/")) {
// Handle comments
const comments = redditData[1].data.children.map(extractCommentSummary);
return NextResponse.json(comments);
}
} else {
return new Response(null, {
status: 400,
statusText: "Invalid Reddit API response",
});
}
You can see the full source code for the isPostListingResponse
, isCommentsResponse
, extractPostSummary
and extractCommentSummary
functions in the repository. The first two functions just check the existence and types of JSON elements, and the latter two map large objects returned by Reddit API into simplified ones that contain post title, author name etc.
OpenAPI specification
To allow our chatbot to talk to our API, we'll need to describe it using the OpenAPI specification (as per Chatwith requirements).
Let's start by creating a YAML file in the static assets directory: /public/openapi.yml
. This way the file will be accessible to Chatwith after deployment like this: http://redditbot.chat/openapi.yml
Here's what we'll put in the file:
openapi: 3.0.0
info:
title: Simple Reddit API
version: 1.0.0
description: An API specification for a simple Reddit client. Using this API you can respond to user queries by searching any subreddit or listing type.
servers:
- url: https://redditbot.chat/api/
paths:
/r/{subreddit}/{listing}.json:
get:
summary: Fetch stories from a specific subreddit by listing type
parameters:
- name: subreddit
in: path
required: true
schema:
type: string
- name: listing
in: path
required: true
schema:
type: string
enum:
- hot
- new
- rising
- top
- controversial
- name: limit
in: query
schema:
type: integer
responses:
"200":
description: A JSON object containing stories
[... trimmed for brevity]
You can see the whole specification file content here. Make sure to stick to YAML whitespace formatting! Wrong tabs or spaces here and there may result in an incorrect spec file.
The important bits
Make sure to put the final path to your API route after its deployed. In my case, the project is deployed at https://redditbot.chat
so the path to API wrapper is https://redditbot.chat/api/
:
servers:
- url: https://redditbot.chat/api/
Document all endpoints the chatbot can use, along with short description and parameters.
paths:
/r/{subreddit}/{listing}.json:
...
The OpenAPI specification does not need to be very extensive and detailed, but it must be descriptive enough for the chatbot to understand how to make a correct request.
At this point we are over the hump, the hardest part is behind! Let's test the new endpoints by calling in the browser eg.:
http://localhost:3000/api/search.json?q=cat
This should return a JSON response with a bunch of cat posts! š»
Creating the chatbot
The final step is creating a chatbot in Chatwith to get an embed. I'm using the free account. I followed this guide. Here are the steps:
Main steps
- Create a free Chatwith account here
- During onboarding pick the 'Chat with API' option
- Click 'Install' on the 'API' card
- Enter our API details in the popup
- Finish the onboarding flow and go to the dashboard to add a system prompt and customize the chatbot
Installing the API Action
Here's the screenshot of how installing the action popup looks like. You should put your OpenAPI spec file url and API urls (but not the localhost! must be the public url after deployment on eg Vercel).
Adding system prompt
To give the chatbot instructions how to use the API, we will fill in the 'System Prompt' field in the 'Settings' section in the dashboard.
Here is the prompt:
You are Reddit AI assistant. You will respond to user query and using the provided API search posts in any subreddit and also by categories like hot, newest etc. You will provide a summary of listings and comments in an easy & digestible way. You are flexible and can understand the query so users can ask about news, fun posts, specialized subreddits etc. You will take the user query and try to make the best API request to find an answer to it. If a post has an image attached, you will show a thumbnail in markdown. Always provide post permalinks in the answer.
You can extend the prompt to add more character to the chatbot, eg. make it use emojis or funny language šø
Testing the chatbot
We're almost done! The chatbot should be working now. Let's go to 'Preview' section of the dashboard to test the chatbot. You can start by asking a question like Summarize hottest posts
!
Fin
Hopefully at this point you have a cool chatbot that can talk to an API š If something didn't work or you have questions feel free to ping me in the comments!
As next steps, you can add more actions to your chatbot (ie. add more API endpoints for it to use) and customize how it looks in the Chatwith dashboard. After updating the API make sure to save the action in Chatwith again so the chatbot knows about the changes.
Thanks for reading!
Posted on September 13, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.