Welcome to part 5 of the tutorial! In this part, we’ll be adding a chat feature to the Blockbuster page. We’ve been preparing for this for so long, and now we’ll finally get to create something cool (and understand what we’re doing in the process!).
We’ll continue to work on our route.tsx file in the api/blockbuster folder:
π app
π api
π blockbuster
π route.tsx π οΈ We'll be working on this file
π blockbuster_chat
π page.tsx
π counter
π page.tsx
π favicon.ico
π globals.css
π layout.tsx
π navbar.tsx
π page.tsx
The route we created last time was cool, but we need more functionality, we need more power!

Creating a POST route
We need a POST route in the API so that it allows the page on the client side to send data to the server. This will allow us to send a movie name, a movie character, and a user question to the server, and use this data to do some cool stuff on the backend before sending a response back to the client.
The first thing we’ll do inside the route.tsx file is move the openAIConfig and openai variables outside of the GET function, so we can reuse them in our new POST function as well:
import { NextRequest, NextResponse } from "next/server";
import OpenAI from "openai";
// move these two outside the function for reuse
const openAIConfig = { apiKey: process.env.OPENAI_API_KEY };
const openai = new OpenAI(openAIConfig);
export async function GET(request: NextRequest) {
const systemMessage ...etc...
}
Now we’ll add a second function to the file, this time for handling POST requests. You’ll see that this is very similar to what we’ve done so far:
export async function POST(request: NextRequest) {
const { movieName, movieCharacter, question } = await request.json();
const systemMessage = `You are helpful and provide good information but you are ${movieCharacter} from ${movieName}. You will stay in character as ${movieCharacter} no matter what. Make sure you find some way to relate your responses to ${movieCharacter}'s personality or the movie ${movieName} at least once every response.`;
const answer = await openai.chat.completions.create({
model: "gpt-4o-mini",
messages: [
{ role: "system", content: systemMessage },
{ role: "user", content: question },
]
});
return NextResponse.json({ answer: answer.choices[0].message.content });
}
Let’s cover the differences as most of this is familiar by now. At the start, we destructure the movieName, movieCharacter, and question from the JSON body of the request by awaiting request.json(). The request object is the data that gets sent from the client to the server so this will have our three variables. Of course we’ll have to take care of actually sending this data from the front end later on so we can receive it here.
The systemMessage instructs ChatGPT to take on the persona of the specified movie character by concatenating the movieCharacter and movieName variables inside the string using the ${} syntax to insert the variables.
The rest is exactly the same as before except for this time the question of the user is sent instead of the hardcoded string question we had before.
If you have Postman or a similar tool installed and you are familiar with how it works you can test this route by sending a POST request to http://localhost:3000/api/blockbuster with a JSON body in the POST request like this:

You can see we got the response as expected. If you don’t have Postman installed or you’re not familiar with it, don’t worry, we’ll be testing this in the browser in a moment anyway. You can install it if you want to play around with it more, but we’ll save time by not covering it in detail here and you can just skip this step without missing out on anything important.
Creating the frontend
Now that we have a working backend API endpoint for our chat feature, it’s time to implement the front end. We’ll be doing this in the blockbuster_chat/page.tsx file:
π app
π api
π blockbuster
π route.tsx
π blockbuster_chat
π page.tsx π οΈ We'll be working on this file
π counter
π page.tsx
π favicon.ico
π globals.css
π layout.tsx
π navbar.tsx
π page.tsx
Open up the page.tsx file and we’re going to start from scratch one last time. I’ll basically delete everything we have and start fresh. Let’s start with the imports:
"use client";
import React, { useState, ChangeEvent, FormEvent } from "react";
As we need local-state for the user here, this will still be a client-side component, so we use the "use client" declaration We’re importing React, useState, ChangeEvent, and FormEvent from the react package. So what are these new imports ChangeEvent and FormEvent? These are types that are used for TypeScript to help handle events related to forms and input fields. They are not really that important and you’ll see how we use them later.
We’re going to need to take input from the user, so we’ll be adding several input fields to our page. We’ll define a reusable input field component to reduce the duplication a bit. Before we write a new component named InputField, we’ll have to define the interface for the props or input arguments that this component will receive.
interface InputFieldProps {
label: string;
value: string;
onChange: (event: ChangeEvent<HTMLInputElement>) => void;
disabled?: boolean;
}
This will let TypeScript know what all the input arguments to our InputField component will be. We have a label which is a string and will hold the label alerting the user what to type into this input box. Second, we have a value which is a string that will hold the current value, or whatever has been typed into the input box so far at this moment.
The third argument is an onChange function which will be called whenever the user types something into the input box. This function will receive an event object of type ChangeEvent<HTMLInputElement>. This is a TypeScript type that represents an event that is triggered when the user types something into an input field, don’t worry too much about the TypeScript types here.
All we are defining here is that our InputField component will receive a function under the name onChange that will be called whenever the user types something into the input field. This function is going to receive an event (which is just an object that contains information about the event that was triggered, in this case, what the user typed in the box). This function then does something with this event and will return void in the end which simply means it doesn’t return anything.
Finally, we have an optional argument disabled which is a boolean that will be used to disable the input field if it is set to true. It is defined as optional in TypeScript by adding the ? there at the end like disabled? This is optional because we don’t always want to disable the input field, only sometimes.
All this object defines is a structure. The exact structure or shape of the arguments that our not-yet-created InputField component will receive. If this TypeScript stuff confuses you, don’t worry, it can honestly be a bit weird and much at first but you’ll get used to it. Just do your best to understand the general idea.
Creating the InputField component
So now let’s define the actual InputField component that we’ll then reuse later on to create several inputs for the user to type into:
const InputField: React.FC<InputFieldProps> = ({
label,
value,
onChange,
disabled = false,
}) => {
return (
<div className="mb-4 w-full">
<label className="block mb-2 text-gray-700">{label}:</label>
<input
type="text"
value={value}
onChange={onChange}
disabled={disabled}
className="w-full p-2 border border-gray-300 rounded bg-white"
/>
</div>
);
};
We define a new const InputField which is of type React.FC where FC means functional component. This component will receive the props defined in the InputFieldProps interface to complete the type definition as React.FC<InputFieldProps>. We can see the input arguments as expected: label, value, onChange, and disabled with a default value of false.
The function body then simply has a return statement that returns a div element with a label and an input element inside. This is a basic HTML structure to create a block with a label and input box. The label is the text that will be displayed to the user to tell them what to type into the input box. The input eleme
