I tried creating a chatbot using the OpenAI API [Next.js + Tailwind CSS]

I tried creating a chatbot using the OpenAI API [Next.js + Tailwind CSS]

Recently, more and more people are using OpenAI's API to create their own chatbots and business assistants.
In this article, we'll show you how to use Next.js (App Router) and Tailwind CSS

OpenAI's chat/completions endpoint to enable a ChatGPT-like conversation experience on your browser, in a simple way.

those who find it a bit of a hassle to write code yourself or
want a template with a UI that works quickly, we also sell templates on the following platforms .

The templates introduced in this article are now on sale!

This chatbot UI template is available for sale on multiple platforms.

The structure is perfect for those who want to try something that works first, "I want to use it right away without building an environment," or "I want to check the entire code."

You can also check other templates from each My Page:

table of contents

About the technology used

This chatbot UI is built using the following technologies:

Next.js (App Router configuration)

A React-based framework allows you to build unified pages and API routes. I use an App Router configuration starting from src/app

Tailwind CSS

A utility-first CSS framework that allows you to efficiently organize your designs using classes. Quickly create a responsive and simple UI

OpenAI API (Chat Completions)

This is a mechanism for interacting with GPT-based models using the /v1/chat/completions we've made it easy to switch between environment variables gpt-4.1-mini and gpt-3.5-turbo

API Routes + fetch

I am writing server processing that relays between the browser and the OpenAI API using the Next.js API root ( /api/chat to clients using fetch ("/api/chat")

Create an OpenAI API key

This time, we will be using the OpenAI API, so we will need to create an OpenAI API key.
You will be redirected to API keys in the OpenAI API dashboard. Select "Create new secret key."

You can have the Project selected any way you like and select Permissions to suit the model you want to use, or select All to generate it.

Once the creation is complete, a string starting with "sk-" will be created, so we will use it after this.
Please be careful not to let this key go.

Environmental construction

First, prepare a development environment for Next.js.

Creating a project

I'll create a project.
Basically, I think it's fine to leave everything at the defaults, but it's fine as long as you can change it as needed.
(This project uses Tailwind CSS, so it's best to set it to Yes. The default is Yes.)

$ npx create-next-app@latest my-chatbot --typescript Need to install the following packages: create-next-app@15.4.4 Ok to proceed? (y) y ✔ Would you like to use ESLint? … No / Yes ✔ Would you like to use Tailwind CSS? … No / Yes ✔ Would you like your code inside a `src/` directory? … No / Yes ✔ Would you like to use App Router? (recommended) … No / Yes ✔ Would you like to use Turbopack for `next dev`? … No / Yes ✔ Would you like to customize the import alias (`@/*` by default)? … No / Yes Creating a new Next.js app in /my-chatbot. Using npm. Initializing project with template: app-tw Installing dependencies: - react - react-dom - next Installing devDependencies: - typescript - @types/node - @types/react - @types/react-dom - @tailwindcss/postcss - tailwindcss - eslint - eslint-config-next - @eslint/eslintrc added 336 packages, and audited 337 packages in 14s 137 packages are looking for funding run `npm fund` for details found 0 vulnerabilities Initialized a git repository. Success! Created my-chatbot at /my-chatbot npm notice npm notice New minor version of npm available! 11.0.0 -> 11.5.1 npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.5.1 npm notice To update run: npm install -g npm@11.5.1 npm notice

If "Success! Created my-chatbot at /my-chatbot" appears, the project creation is complete.
Once you have created the project, move the directory.

cd my-chatbot

Set OpenAI API Key

Create a .env in the root

OPENAI_API_KEY=sk-... OPENAI_MODEL=gpt-4.1-mini SYSTEM_PROMPT=You are a helpful assistant.

Short description:

  • OPENAI_API_KEY: Private key required for OpenAI API access (issued by a personal account)
  • OPENAI_MODEL: The model name to use. Depending on the purpose, gpt-3.5-turbo , gpt-4.1-mini , etc.
  • SYSTEM_PROMPT: Initial message specifying the AI's personality and role (effective when chat starts)

Start the development server

Running the following command will launch the template:

npm run dev

Implementing the UI (Chat screen)

The chat user interface is consolidated in
src/app/page.tsx The total amount of source code is as follows:

"use client"; import { useState, useRef, useEffect } from "react"; // Reusable chat interface using OpenAI API // Reusable chat interface using OpenAI API export default function Home() { const [input, setInput] = useState(""); const [messages, setMessages] = useState< { role: "user" | "assistant"; content: string }[] >([]); const [loading, setLoading] = useState(false); const chatEndRef = useRef<HTMLDivElement> (null); // Handle message submission to the API // The process of sending a message to the API const sendMessage = async () => { if (!input.trim()) return; // Add user message to chat history // Add user message to history const newMessages = [ ...messages, { role: "user", content: input } as const, ]; setMessages(newMessages); setInput(""); setLoading(true); // Send message to backend API route // Send message to backend API endpoint const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: input }), }); const data = await res.json(); // Add assistant response to chat history // Add assistant response to history setMessages((prev) => [ ...prev, { role: "assistant", content: data.reply }, ]); setLoading(false); }; // Scroll to bottom of chat view on update // Scroll to the bottom of chat view when updating a message useEffect(() => { chatEndRef.current?.scrollIntoView({ behavior: "smooth" }); }, [messages, loading]); return ( <main className="h-screen flex items-center justify-center bg-gray-100 text-gray-800"><div className="flex flex-col w-full max-w-3xl h-[90vh] border-x border-gray-300 bg-white"> {/* Header */} {/* Header */}<header className="p-4 border-b text-lg font-semibold"> ChatBot-ChatGPT</header> {/* Chat message display */} {/* Chat message display area */} <div className="flex-1 overflow-y-auto p-6 space-y-4 scrollbar-thin scrollbar-thumb-gray-400 scrollbar-track-gray-100"> {messages.map((msg, idx) => ( <div key={idx} className={`flex ${ msg.role === "user" ? "justify-end" : "justify-start" }`} ><div className={`max-w-xl px-4 py-2 rounded-lg text-sm whitespace-pre-wrap ${ msg.role === "user" ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-900" }`} > {msg.content}</div></div> ))} {/* Typing indicator while loading */} {/* Indicator during loading */} {loading && ( <div className="flex justify-start"><div className="bg-gray-100 text-gray-700 px-4 py-2 rounded-lg text-sm animate-pulse"> Thinking...</div></div> )}<div ref={chatEndRef} /></div> {/* Input area */} {/* Input field */}<footer className="border-t p-4"><div className="flex gap-2"><input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage()} className="flex-grow p-2 border rounded focus:outline-none focus:ring focus:border-blue-300" placeholder="Type your message..." /> <button onClick={sendMessage} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition" >Send</button></div></footer></div></main> ); }

Message status management

const [input, setInput] = useState(""); const [messages, setMessages] = useState< { role: "user" | "assistant"; content: string }[] >([]); const [loading, setLoading] = useState(false);

Short description:

  • input: Keep input value of text box
  • messages: Previous chat history (user and assistant conversations)
  • loading: Whether or not a response from the API

Send Processing and API Calling

const sendMessage = async () => { if (!input.trim()) return; const newMessages = [...messages, { role: "user", content: input }]; setMessages(newMessages); setInput(""); setLoading(true); const res = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ message: input }), }); const data = await res.json(); setMessages((prev) => [...prev, { role: "assistant", content: data.reply }]); setLoading(false); };

Short description:

  • Send messages to the API while adding user input to the history
  • If you get a response, add it to the history as an assistant

Chat screen layout (formatting with Tailwind)

<div className="flex-1 overflow-y-auto p-6 space-y-4">{messages.map((msg, idx) => ( <div key={idx} className={`flex ${msg.role === "user" ? "justify-end" : "justify-start"}`}><div className={`max-w-xl px-4 py-2 rounded-lg text-sm whitespace-pre-wrap ${ msg.role === "user" ? "bg-blue-600 text-white" : "bg-gray-100 text-gray-900" }`}> {msg.content}</div></div> ))} {loading && ( <div className="flex justify-start"><div className="bg-gray-100 text-gray-700 px-4 py-2 rounded-lg text-sm animate-pulse"> Thinking...</div></div> )}</div>

Short description:

  • User and assistant messages are displayed on the left and right sides
  • When waiting for a response, "Thinking..." will be displayed to create a conversational feel.

Input field and send button

<footer className="border-t p-4"><div className="flex gap-2"><input value={input} onChange={(e) => setInput(e.target.value)} onKeyDown={(e) => e.key === "Enter" && sendMessage()} className="flex-grow p-2 border rounded focus:outline-none" placeholder="Type your message..." /> <button onClick={sendMessage} className="bg-blue-600 text-white px-4 py-2 rounded hover:bg-blue-700 transition" >Send</button></div></footer>

Short description:

  • Specifications that can be sent using the Enter or button on the keyboard
  • Using Tailwind CSS, we have created a simple and easy-to-read form.

Server-side implementation (OpenAI API integration)

The chat interaction involves a client sending a request to
/api/chat then relaying and responses are returned to OpenAI's Chat API on the server.

The total amount of server-side source code is as follows:

// src/app/api/chat/route.ts import { NextRequest, NextResponse } from "next/server"; // POST handler for chat requests // POST handler that handles chat requests export async function POST(req: NextRequest) { const { message } = await req.json(); // Call OpenAI API using server-side secret key and parameters // SYSTEM_PROMPT defines the assistant's personality, tone, or behavior. // OpenAI with server-side private key and parameters Call API // SYSTEM_PROMPT specifies the personality, speaking, and behavior of the assistant. const response = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: process.env.OPENAI_MODEL || "gpt-4.1-mini", messages: [ { role: "system", content: process.env.SYSTEM_PROMPT || "You are a helpful AI assistant.", }, { role: "user", content: message }, ], }), }); const data = await response.json(); // Handle error response from OpenAI // Handle error response from OpenAI // Handle error response from OpenAI if (data.error) { console.error(data.error); return NextResponse.json( { reply: "An error occurred. Please try again later." }, { status: 500 } ); } // Return assistant reply // Return assistant reply return NextResponse.json({ reply: data.choices[0].message.content }); }

Basic configuration of route.ts

import { NextRequest, NextResponse } from "next/server"; export async function POST(req: NextRequest) { const { message } = await req.json();

Short description:

  • API routes limited to POST
  • Receives the message body sent from the client side

Requests for OpenAI API

const response = await fetch("https://api.openai.com/v1/chat/completions", { method: "POST", headers: { Authorization: `Bearer ${process.env.OPENAI_API_KEY}`, "Content-Type": "application/json", }, body: JSON.stringify({ model: process.env.OPENAI_MODEL || "gpt-4.1-mini", messages: [ { role: "system", content: process.env.SYSTEM_PROMPT || "You are a helpful assistant." }, { role: "user", content: message }, ], }), }); const data = await response.json();

Short description:

  • Dynamically load API keys , model names , and prompts from environment variables
  • Create a message to send to a GPT model

Response processing and error handling

if (data.error) { console.error(data.error); return NextResponse.json({ reply: "An error occurred." }, { status: 500 }); } return NextResponse.json({ reply: data.choices[0].message.content }); }

Short description:

  • If the OpenAI API fails, output the error to the console
  • If you get a response successfully, send the assistant message back to the client as reply

[Bonus] Things to note

Make sure to manage
with .env so that they are never exposed If your API key is leaked, remove the API key from the OpenAI dashboard.

The model name is an environment variable, so please replace it according to the purpose.
The amount of money involved varies depending on the model, so be careful when selecting a model.

Information on template sales

The chatbot UI introduced in this article is also sold as a template that can be used for commercial purposes

Why sell templates?

We have prepared this for those who are experiencing the following needs:

  • "Even if you follow the steps, the environment doesn't work out..."
  • "I just want to start with a moving sample."
  • "I looked at my blog and found it useful, so I would like to purchase it with the aim of supporting and donating."

Even those who are not used to developing the game are able to start up and try it out quickly with minimal effort

Examples of template use (customization ideas)

This template is perfect for personal development and learning purposes.
For example, we recommend modifying and expanding the following:

  • Add history and context to API responses
  • Try saving input history to local storage
  • Try adding a function to output chat logs to PDF or CSV
  • speech reading and image generation may be interesting

It's also a good practice to try it out on a "serial basis" basis for programming beginners.

What's included in the template

contains
all the source code for the project we introduced here Therefore, you don't have to create or configure your own project from scratch, and you can start it immediately .

  • Chat UI with Next.js (App Router)
  • Server-side implementation with API integration
  • Clean code structure with comments
  • Simple and easy to improve design using Tailwind CSS
  • Docker startup configuration files ( Dockerfile , docker-compose.yml )

*To execute, a basic execution environment such as Node.js and Docker is required.

The templates introduced in this article are now on sale!

This chatbot UI template is available for sale on multiple platforms.

The structure is perfect for those who want to try something that works first, "I want to use it right away without building an environment," or "I want to check the entire code."

You can also check other templates from each My Page:

summary

we've introduced how to build
a simple chatbot UI using the OpenAI API and Next.js.

Looking back at the points:

  • The Chat API allows you to build high-quality chatbots with just a little bit of code
  • Tailwind CSS + Next.js App Router makes it easy to expand and reuse
  • Using templates allows you to quickly check the operation by omitting the environment construction.

Not only is it suitable as a starter for development and personal projects, but it is also recommended as a basis for adding unique features and customizing UI.

Share if you like!

Who wrote this article

This is a blog I started to study information security. As a new employee, I would be happy if you could look with a broad heart.
There is also Teech Lab, which is an opportunity to study programming fun, so if you are interested in software development, be sure to take a look!

table of contents