Build an AI Chat App with FastAPI + React

Build a full-stack AI chat assistant using FastAPI, React, and Google Gemini.

Introduction

30 Second Summary

Every time you chat with an AI assistant online, a backend server is calling an AI model and sending the response back to your screen. Building one of these systems yourself is the fastest way to prove you can deliver AI features from start to finish.

In this project, you will build a full-stack AI chat application from scratch. Your Python backend will talk to Google Gemini, and your React frontend will display the conversation in real time.

What You'll Build

You'll type a message into a polished chat interface, watch the AI respond in seconds, then ask it "what did I just say?" and see it recall your entire conversation.

By the end of this project, you'll have:

  • A live chat interface where you type messages and see AI responses appear as distinct bubbles in real time.
  • A conversation memory system that lets the AI recall everything you discussed earlier in the chat.
  • A self-documenting REST API you can demo to anyone by opening a single URL in your browser.
  • Secret Mission: Add a New Chat button and a persistent dark/light theme toggle that gives your project real UX polish.

Are there any prerequisites?

This project assumes basic familiarity with React (components, state, props) and Python (functions, imports, variables). You do not need prior experience with FastAPI or AI APIs.

Before We Start

Before we dive in, let's lock in what you're about to build and why it matters.

Set Up Your Development Environment

Every step after this one produces something you can see and interact with. For that to work, you need your tools installed first: FastAPI for the backend, the Google GenAI SDK for AI, and an API key to authenticate with Google Gemini.

In this step, you'll install everything and verify it works. Once this is done, you'll never touch setup again.

In this step, get ready to:
  • Install FastAPI, the Google GenAI SDK, and python-dotenv.
  • Get a free Gemini API key from Google AI Studio.
  • Verify all three tools are ready with test commands.
Create your project folder and virtual environment

You need a dedicated folder to keep all your project files together, and a Python virtual environment to isolate your packages from the rest of your system.

  • Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type VS Code and press Enter to open it.
  • Open a terminal by pressing Ctrl+` (backtick key, below Escape).
  • Move to your Desktop by running this command:
cd ~/Desktop
  • Create your project folder and move into it by running these commands:
mkdir ai-chat-assistant
cd ai-chat-assistant
  • Create a Python virtual environment by running:
python -m venv venv
  • Confirm the venv folder was created by running:
ls

You should see a venv folder listed in the output.

What is a virtual environment?

A virtual environment is an isolated Python workspace. Packages you install here stay inside this project folder and don't affect anything else on your system.

  • Activate the virtual environment by running:

macOS

source venv/bin/activate

Your terminal prompt now shows (venv) at the beginning, confirming the environment is active.

Windows

venv\Scripts\activate

Your terminal prompt now shows (venv) at the beginning, confirming the environment is active.

Don't see (venv) in your prompt?

On Windows, if you see an error about execution policies, run Set-ExecutionPolicy -Scope CurrentUser RemoteSigned and then try the activate command again.

On macOS, make sure you typed source before the path. help me activate my Python virtual environment

  • Install the project dependencies by running:
pip install "fastapi[standard]" google-genai python-dotenv

You should see output scrolling as packages download. The final line shows a list of successfully installed package names.

What are these packages?

  • fastapi[standard] installs FastAPI plus Uvicorn (the server that runs your app) and other useful extras.
  • google-genai is the official Python SDK for calling Google Gemini's AI models.
  • python-dotenv lets your app read secret values from a .env file so you never hardcode credentials.

Seeing a permission error during install?

Make sure your terminal prompt starts with (venv). If it doesn't, the virtual environment isn't active. Re-run the activate command from the previous step, then try the install again.

help me fix my pip install error

Get your free Gemini API key

Your backend needs an API key to authenticate with Google Gemini. The free tier covers everything in this project with no credit card required.

  • Open aistudio.google.com in your browser.
  • Sign in with your Google account.
  • Click Get API Key in the left sidebar.
  • Click Create API Key.
  • Copy the generated key.

Keep your API key private

Never paste your API key directly into code files. You'll store it in a .env file that stays on your machine only.

  • Back in VS Code, create a new file called .env in your ai-chat-assistant folder by clicking File then New File.
  • Paste the following into the file, replacing the placeholder with your actual key:
GEMINI_API_KEY=your-api-key-here
  • Save the file with Cmd+S (macOS) or Ctrl+S (Windows).

What does this file do?

The .env file stores your API key as an environment variable called GEMINI_API_KEY. When your Python app runs, python-dotenv loads this value into memory so your code can read it without the key ever appearing in your source files.

  • Confirm the file was saved by running this in your terminal:
ls -a

You should see .env listed alongside venv in the output.

Verify your setup

Three quick commands confirm that FastAPI, the Gemini SDK, and python-dotenv are all installed correctly.

  • Before you run these, what do you expect to see if everything installed correctly?
  • Check that FastAPI is installed by running:
fastapi --version

You should see a version number printed.

  • Check that the Google GenAI SDK is installed by running:
python -c "from google import genai; print('SDK ready')"

You should see SDK ready printed.

  • Check that python-dotenv is installed by running:
python -c "from dotenv import load_dotenv; print('dotenv ready')"

You should see dotenv ready printed.

Seeing ModuleNotFoundError?

This means a package didn't install into your active environment. Confirm your prompt shows (venv) at the start. If it doesn't, re-activate the virtual environment and run the pip install command again.

If (venv) is showing but the error persists, run pip list to see what's installed. Look for fastapi, google-genai, and python-dotenv in the list. help me fix my ModuleNotFoundError

Your environment is ready. Next up, you'll build your first API endpoint and see FastAPI's interactive documentation in your browser.

Build Your First API Endpoint

Your development environment is ready with FastAPI and the Gemini SDK installed. Now it's time to build the backend that will power your AI chat assistant.

Before connecting any AI, you need a working server that can receive messages and send responses. FastAPI auto-generates interactive API documentation so you can test your endpoints right in the browser without writing any frontend code.

In this step, get ready to:
  • Create a main.py file with a POST /chat endpoint.
  • Run the FastAPI development server and test your endpoint through the built-in Swagger UI.
Create your chat endpoint

Your API needs two things: a data model that defines what a chat message looks like, and an endpoint that receives that message and returns a response. For now, the endpoint will echo the message back so you can confirm everything is wired up before adding AI.

  • Create a new file called main.py in your ai-chat-assistant folder by running this command in your terminal:
type nul > main.py
  • Open main.py in VS Code and paste the following code:
from fastapi import FastAPI
from pydantic import BaseModel

# Create the FastAPI application instance
app = FastAPI()


# Define the shape of incoming chat requests
class ChatRequest(BaseModel):
    message: str


# Define the shape of outgoing chat responses
class ChatResponse(BaseModel):
    reply: str


# Handle POST requests to /chat
@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    return ChatResponse(reply=f"Echo: {request.message}")

What does this code do?

  • ChatRequest and ChatResponse are Pydantic models. They define the exact shape of data your API accepts and returns. FastAPI uses these to validate requests automatically.
  • The @app.post("/chat") decorator tells FastAPI to call the chat function whenever someone sends a POST request to /chat.
  • Right now the endpoint echoes the message back. This proves the server works before you connect it to AI.
  • Save main.py.

Seeing a red underline on imports?

If VS Code shows a warning on the from fastapi or from pydantic lines, your editor may not be using the virtual environment's Python interpreter.

  • Press Cmd+Shift+P (macOS) or Ctrl+Shift+P (Windows) to open the command palette.
  • Type Python: Select Interpreter and choose the one inside your venv folder.

Still stuck? Why is VS Code not finding my FastAPI import?

Run the development server and test your endpoint

FastAPI comes with a built-in development server that auto-reloads whenever you change your code. It also generates a Swagger UI page where you can send test requests without needing a separate tool.

  • Start the development server by running this command:
fastapi dev main.py

You should see output showing the server is running at http://127.0.0.1:8000.

What does this command do?

The fastapi dev command starts a Uvicorn server with auto-reload enabled. Every time you save a change to main.py, the server restarts automatically so you always see the latest version.

  • Open your browser and go to http://127.0.0.1:8000/docs.

You should see FastAPI's interactive Swagger UI with your POST /chat endpoint listed. This documentation page is generated automatically from your Pydantic models.

Before you test, think about this: what do you expect to get back if you send the message "Hello" to your endpoint?

  • Click the POST /chat endpoint to expand it.
  • Click Try it out.
  • Replace the request body with this JSON:
{
  "message": "Hello!"
}
  • Click Execute.

You should see a response body containing:

{
  "reply": "Echo: Hello!"
}

Your backend received the message, processed it, and returned a structured JSON response. The echo confirms the full request-response cycle works.

Don't see the Swagger UI?

  • Check that your terminal still shows the server running. If you see an error, read the traceback for a typo hint in main.py.
  • Make sure the URL is exactly http://127.0.0.1:8000/docs with no trailing slash after docs.
  • If you see ModuleNotFoundError, your virtual environment may not be activated. Run venv\Scripts\activate and try again.

Need more help? My FastAPI server won't start or I can't see the Swagger docs page.

✔️ Awesome, I've got everything!

Great job. Make sure main.py is saved and your server is still running in the terminal.

ⓧ I'd like to double check the full code

Here is the complete main.py for this step:

from fastapi import FastAPI
from pydantic import BaseModel

app = FastAPI()


class ChatRequest(BaseModel):
    message: str


class ChatResponse(BaseModel):
    reply: str


@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    return ChatResponse(reply=f"Echo: {request.message}")

Your backend is alive and responding to requests. Next up, you'll replace that echo with a real AI brain by connecting Google Gemini.

Connect Google Gemini AI

Your backend can receive messages and send responses. That is the hard part done.

A hardcoded echo is not an AI assistant, though. In this step, you replace the fake response with a real call to Google Gemini, so your API returns actual AI-generated answers.

In this step, get ready to:
  • Import the Gemini SDK and create a client that connects to the AI.
  • Update your /chat endpoint to send messages to Gemini and return real AI responses.
  • Test the AI-powered endpoint in the Swagger UI.
Import the Gemini SDK and create a client

Before your endpoint can call the AI, it needs access to the google-genai SDK and your API key. You will create a single client at module level so every request reuses the same connection.

  • In main.py, replace everything above the class ChatRequest line with the following code:
from fastapi import FastAPI
from pydantic import BaseModel
from google import genai
from dotenv import load_dotenv
import os

# Load environment variables from .env file
load_dotenv()

app = FastAPI()

# Create a reusable Gemini client with your API key
client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))

What does this code do?

  • from google import genai brings in the official Google GenAI SDK that talks to Gemini.
  • load_dotenv() reads your .env file and loads GEMINI_API_KEY into the environment so Python can access it.
  • genai.Client(api_key=...) creates a single client that authenticates with your key. Placing it at module level means it is created once when the server starts, not on every request.
  • Save main.py.
  • Check the terminal where fastapi dev is running. The server auto-reloads on save. You should see a reload log line with no errors.

Seeing an ImportError?

  • If you see ModuleNotFoundError: No module named 'google', confirm your virtual environment is active. Your terminal prompt should show (venv) at the start.
  • If you see an error about GEMINI_API_KEY, make sure your .env file is in the same folder as main.py and contains your key.

Help me fix an ImportError in my FastAPI project that uses google-genai

Replace the echo with a real AI call

Right now the endpoint returns a hardcoded echo. You will swap that for a call to client.models.generate_content() which sends the user's message to Gemini and returns the AI's answer.

  • In main.py, find your existing chat function at the bottom of the file. It currently looks like this:
@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    return ChatResponse(reply=f"Echo: {request.message}")
  • Replace that entire function with the following:
@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    # Send the user's message to Gemini and get a response
    response = client.models.generate_content(
        model="gemini-3.5-flash",
        contents=request.message
    )
    # Return the AI's generated text as the reply
    return ChatResponse(reply=response.text)

What does this code do?

  • client.models.generate_content() sends a prompt to the gemini-3.5-flash model and waits for a response.
  • contents=request.message passes whatever the user typed as the prompt.
  • response.text extracts the AI's plain-text answer from the response object.
  • Save main.py. The server auto-reloads again.

Before you test this, what do you think will happen when you send "What is Python?" to the endpoint? Will you still see an echo, or something different?

  • Return to the Swagger UI at http://127.0.0.1:8000/docs in your browser.
  • Click the POST /chat endpoint to expand it.
  • Click Try it out.
  • Replace the request body with:
{
  "message": "What is Python?"
}
  • Click Execute.

You should see a real, detailed explanation of Python in the response body. No more echo.

Getting a 500 Internal Server Error?

  • Check the terminal running your server for the full error traceback.
  • If you see an authentication error, double-check that your .env file contains a valid key from Google AI Studio and that load_dotenv() appears before the client line in main.py.
  • If you see a model-not-found error, confirm you typed gemini-3.5-flash exactly (no spaces, no typos).

Help me debug a 500 error when calling Gemini from my FastAPI endpoint

  • Try a few more questions to confirm it works consistently. Send messages like "Explain APIs in one sentence" or "What is 2 + 2?" and verify you get intelligent responses each time.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and your server is running without errors.

ⓧ I'd like to double check the full code

Here is what your complete main.py should look like at this point:

from fastapi import FastAPI
from pydantic import BaseModel
from google import genai
from dotenv import load_dotenv
import os

load_dotenv()

app = FastAPI()

client = genai.Client(api_key=os.getenv("GEMINI_API_KEY"))


class ChatRequest(BaseModel):
    message: str


class ChatResponse(BaseModel):
    reply: str


@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    response = client.models.generate_content(
        model="gemini-3.5-flash",
        contents=request.message
    )
    return ChatResponse(reply=response.text)

Your API is now powered by real AI. Next up, you will build a React chat interface so you can talk to your AI assistant in a proper browser UI instead of raw JSON.

Build the React Chat Frontend

Your API works, but nobody wants to demo a project by typing JSON into a Swagger form. A polished chat interface lets you show your AI assistant to anyone in 10 seconds.

In this step, you will scaffold a React frontend with Vite, connect it to your FastAPI backend, and build a real-time chat UI with message bubbles.

In this step, get ready to:
  • Scaffold a React app and start the Vite dev server.
  • Add CORS middleware so the frontend can talk to the backend.
  • Build a chat component that sends messages to your AI and displays responses.
Scaffold the React app with Vite

You need a second terminal for the frontend. Your backend must keep running in its original terminal.

  • Open a new terminal window. Press Cmd+Space (macOS) or the Windows key (Windows) to open your search bar.
  • Type Terminal (macOS) or Command Prompt (Windows) and press Enter to open it.
  • Navigate to your ai-chat-assistant project folder by running:
cd ~/Desktop/ai-chat-assistant
  • Scaffold a new React app inside a frontend folder by running this command:
npm create vite@latest frontend -- --template react

What does this command do?

This tells npm to download the create-vite tool and scaffold a React project in a folder called frontend. The --template react flag skips the framework selection prompt and goes straight to React.

You may see a confirmation prompt

npm may ask you to confirm installing the create-vite package. Type y and press Enter to proceed.

  • Move into the new frontend folder and install its dependencies by running:
cd frontend
npm install
  • Start the Vite development server by running:
npm run dev
  • Open http://localhost:5173 in your browser.
  • You should see the default Vite + React page with a counter button.

Don't see the Vite page?

  • Check your terminal output. Vite prints the URL it is serving on. Make sure you are visiting the correct port.
  • Make sure npm install completed without errors before running npm run dev.

Why won't my Vite dev server start?

Allow frontend requests with CORS

Your React app runs on port 5173 and your FastAPI backend runs on port 8000. Browsers block requests between different origins by default. You need to add CORS middleware to your backend so it accepts requests from the frontend.

  • Switch back to VS Code and open main.py.
  • Find this import line at the top of the file:
from fastapi import FastAPI
  • Add a new import directly below it, so the top of your file looks like this:
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
  • Find the line app = FastAPI() and add the CORS middleware block directly below it:
app.add_middleware(
    CORSMiddleware,
    allow_origins=["http://localhost:5173"],
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

What does this code do?

  • allow_origins lists which domains can make requests to your API. You are allowing only your Vite dev server.
  • allow_methods and allow_headers set to "*" means all HTTP methods and headers are permitted.
  • Save main.py.
  • Check the terminal where your FastAPI server is running. You should see it auto-restart with no errors.

Seeing an import error?

Make sure the CORS import reads from fastapi.middleware.cors import CORSMiddleware exactly. A typo in the import path causes an ImportError on restart.

Help me fix a CORS import error in my FastAPI app.

Build the chat interface

Now you will replace Vite's default page with your chat component. The component handles sending messages to your backend and displaying the conversation.

  • In VS Code, open frontend/src/App.jsx.
  • Select all the existing code and delete it. You will replace it with your chat component in three sections.
  • Start by pasting the imports, state declarations, and auto-scroll effect:
import { useState, useRef, useEffect } from 'react'
import './App.css'

function App() {
  const [messages, setMessages] = useState([])
  const [input, setInput] = useState('')
  const [conversationId, setConversationId] = useState(null)
  const [isLoading, setIsLoading] = useState(false)
  const messagesEndRef = useRef(null)

  useEffect(() => {
    messagesEndRef.current?.scrollIntoView({ behavior: 'smooth' })
  }, [messages])

What does this code do?

  • messages holds the array of chat messages (both user and AI).
  • conversationId tracks which backend conversation session this chat belongs to.
  • The useEffect auto-scrolls to the newest message whenever the messages array changes.
  • Directly below the useEffect block, add the sendMessage function:
  const sendMessage = async (e) => {
    e.preventDefault()
    if (!input.trim() || isLoading) return

    const userMessage = input.trim()
    setInput('')
    setMessages((prev) => [...prev, { role: 'user', text: userMessage }])
    setIsLoading(true)

    try {
      const response = await fetch('http://localhost:8000/chat', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({
          message: userMessage,
          conversation_id: conversationId,
        }),
      })
      const data = await response.json()
      setMessages((prev) => [...prev, { role: 'assistant', text: data.reply }])
      setConversationId(data.conversation_id)
    } catch (error) {
      setMessages((prev) => [
        ...prev,
        { role: 'assistant', text: 'Error: Could not reach the server.' },
      ])
    } finally {
      setIsLoading(false)
    }
  }

What does this code do?

  • It sends a POST request to your FastAPI /chat endpoint with the user's message.
  • The user's message immediately appears in the chat (optimistic UI) while the API call happens in the background.
  • If the backend is unreachable, it shows a friendly error message instead of crashing.
  • Finally, add the component's JSX and the export below the sendMessage function:
  return (
    <div className="chat-container">
      <header className="chat-header">
        <h1>AI Chat Assistant</h1>
      </header>
      <div className="messages">
        {messages.map((msg, i) => (
          <div key={i} className={`message ${msg.role}`}>
            <p>{msg.text}</p>
          </div>
        ))}
        {isLoading && (
          <div className="message assistant">
            <p className="loading">Thinking...</p>
          </div>
        )}
        <div ref={messagesEndRef} />
      </div>
      <form onSubmit={sendMessage} className="input-form">
        <input
          type="text"
          value={input}
          onChange={(e) => setInput(e.target.value)}
          placeholder="Type a message..."
          disabled={isLoading}
        />
        <button type="submit" disabled={isLoading || !input.trim()}>
          Send
        </button>
      </form>
    </div>
  )
}

export default App

What does this code do?

  • Each message renders as a div with a class of either user or assistant. CSS will style these as bubbles on opposite sides.
  • While the AI is thinking, a "Thinking..." indicator appears.
  • The form's onSubmit triggers sendMessage, so pressing Enter also sends.
  • Save App.jsx.
  • Refresh your browser at http://localhost:5173. You should see a header that says "AI Chat Assistant", an input field, and a Send button. It looks unstyled but the layout is there.

Now add the styling. Open frontend/src/App.css and delete all existing contents. Replace it with the styles below in three sections.

  • Start with the reset, container layout, and header styles:
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

.chat-container {
  max-width: 800px;
  margin: 0 auto;
  height: 100vh;
  display: flex;
  flex-direction: column;
  font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
}

.chat-header {
  padding: 1rem;
  background: #2563eb;
  color: white;
  text-align: center;
}

.chat-header h1 {
  font-size: 1.25rem;
}
  • Below that, add the message area and bubble styles:
.messages {
  flex: 1;
  overflow-y: auto;
  padding: 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.75rem;
}

.message {
  max-width: 75%;
  padding: 0.75rem 1rem;
  border-radius: 1rem;
  line-height: 1.5;
  white-space: pre-wrap;
}

.message.user {
  align-self: flex-end;
  background: #2563eb;
  color: white;
  border-bottom-right-radius: 0.25rem;
}

.message.assistant {
  align-self: flex-start;
  background: #f1f5f9;
  color: #1e293b;
  border-bottom-left-radius: 0.25rem;
}
  • Finally, add the loading indicator and input form styles:
.loading {
  opacity: 0.6;
  font-style: italic;
}

.input-form {
  display: flex;
  padding: 1rem;
  gap: 0.5rem;
  border-top: 1px solid #e2e8f0;
}

.input-form input {
  flex: 1;
  padding: 0.75rem 1rem;
  border: 1px solid #e2e8f0;
  border-radius: 0.5rem;
  font-size: 1rem;
  outline: none;
}

.input-form input:focus {
  border-color: #2563eb;
}
  • And the button styles at the end:
.input-form button {
  padding: 0.75rem 1.5rem;
  background: #2563eb;
  color: white;
  border: none;
  border-radius: 0.5rem;
  font-size: 1rem;
  cursor: pointer;
}

.input-form button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
  • Save App.css.

One last file. Open frontend/src/index.css and replace all its contents with:

body {
  margin: 0;
  background: #ffffff;
}
  • Save index.css.

✔️ Awesome, I've got everything!

Great. Double check you have saved all three files: App.jsx, App.css, and index.css.

ⓧ I'd like to double check the full code

Scroll up and confirm each section is present and in order:

  • App.jsx: imports and state (chunk 1), then sendMessage function (chunk 2), then JSX return and export (chunk 3).
  • App.css: reset and layout (chunk 1), then message bubbles (chunk 2), then loading and input form (chunk 3), then button styles (chunk 4).
  • index.css: body margin and background only.

If anything is missing, copy the relevant chunk from above and paste it in the correct position.

Before you test, what do you expect will happen when you type a message and press Send? Will the AI respond?

  • Refresh your browser at http://localhost:5173.
  • Type Hello! in the input field and click Send.

Your message appears as a blue bubble on the right. After a moment, the AI's response appears as a gray bubble on the left. You have a working full-stack AI chat application.

Message stuck on "Thinking..."?

  • Make sure your FastAPI backend is still running in the other terminal. If it stopped, restart it with fastapi dev main.py.
  • Check the browser console (press F12) for CORS errors. If you see one, confirm the allow_origins list in main.py includes http://localhost:5173 exactly.
  • Check that your .env file still has your Gemini API key set correctly.

Why is my React frontend not connecting to my FastAPI backend?

Your full-stack AI chat is live. Next up, you will fix a sneaky limitation. Try asking the AI about something you told it earlier. It does not remember.

Add Conversation Memory

Your chat assistant answers questions, but it has a critical gap. Right now, every message you send is treated as a brand new conversation with zero context.

Real chat assistants remember what you said earlier. In this step, you'll discover the memory gap firsthand, then fix it by switching to Gemini's stateful chat sessions.

In this step, get ready to:
  • Discover that your AI has no conversation memory.
  • Replace stateless API calls with stateful chat sessions using conversation IDs.
  • Verify the AI remembers details from earlier in the conversation.
Discover the memory gap

Before changing any code, test your current chat to experience what "no memory" actually feels like.

  • Switch to your browser at http://localhost:5173.
  • Type My name is Alex and press Send.
  • Wait for the AI to respond.

Now, before you send the next message, predict: if you ask "What is my name?" will the AI know the answer?

  • Type What is my name? and press Send.

The AI does not know your name. It responds with something generic because each message is sent in complete isolation. The backend calls client.models.generate_content() with only the current message, so Gemini never sees what you said before. This is the gap you will fix now.

Why does this happen?

LLMs like Gemini do not inherently "remember" past messages. They only know what is included in the current request.

To create the illusion of memory, you must send the full conversation history with every request. Gemini's SDK handles this for you through stateful chat sessions.

Add session management to the backend

The fix is to switch from one-shot generate_content() calls to stateful chat sessions using client.chats.create(). Each session remembers every message automatically.

  • In main.py, find the imports at the top of the file.
  • Find this line:
import os
  • Add import uuid on the next line, so the two lines look like this:
import os
import uuid

Now replace everything below the client = genai.Client(...) line. This includes the Pydantic models and the /chat endpoint.

  • Delete everything from class ChatRequest to the end of the file and replace it with this code:
# Store active chat sessions keyed by conversation ID
conversations: dict[str, object] = {}


class ChatRequest(BaseModel):
    message: str
    conversation_id: str | None = None


class ChatResponse(BaseModel):
    reply: str
    conversation_id: str


@app.post("/chat")
async def chat(request: ChatRequest) -> ChatResponse:
    # Reuse existing conversation or start a new one
    conversation_id = request.conversation_id or str(uuid.uuid4())

    if conversation_id not in conversations:
        conversations[conversation_id] = client.chats.create(
            model="gemini-3.5-flash"
        )

    chat_session = conversations[conversation_id]
    response = chat_session.send_message(request.message)

    return ChatResponse(reply=response.text, conversation_id=conversation_id)

What does this code do?

  • conversations is a dictionary that stores active chat sessions. Each session is keyed by a unique ID.
  • ChatRequest now accepts an optional conversation_id. The first message sends null, and the backend generates a fresh UUID.
  • client.chats.create() creates a stateful session that automatically tracks every message exchanged. No manual history management needed.
  • chat_session.send_message() sends the new message along with all prior messages in the session, giving Gemini full context.
  • Save main.py.
  • The FastAPI dev server auto-reloads. Check the terminal where it's running. You should see a message confirming the reload without any import errors.

Seeing an import error or server crash?

  • Check that import uuid is on its own line below import os.
  • Make sure you deleted the old ChatRequest and ChatResponse classes. Duplicate class definitions will cause errors.
  • Confirm the conversations dict is placed ABOVE the class definitions, not inside a function.

Help me debug my FastAPI server reload error.

Verify conversation memory works

Your React frontend already sends conversation_id with every request and stores the ID returned by the backend. No frontend changes are needed.

Why does the frontend already handle this?

In Step 4, the frontend was built with a conversationId state variable that starts as null. The first request sends null, the backend generates a UUID, and the frontend stores it via setConversationId(data.conversation_id). Every following message sends that same ID, linking the entire conversation to one backend session.

Time to prove the memory works. Before you test, predict: if you tell the AI your name and then ask what it is, will it know this time?

  • Return to your chat at http://localhost:5173.
  • Refresh the page to start a fresh session.
  • Type My name is Alex and press Send.
  • After the AI responds, type What is my name? and press Send.

The AI correctly responds with "Alex" because it now has access to the full conversation history. Your chat assistant has memory.

AI still does not remember your name?

  • Make sure you refreshed the page before testing. An old session from before the code change will not use the new logic.
  • Check that your FastAPI terminal shows a successful reload with no errors.
  • Open http://127.0.0.1:8000/docs and check that the /chat endpoint now shows conversation_id in both the request and response schemas.

Help me debug why my AI chat has no memory.

✔️ Awesome, I've got everything!

Great. Double check you have saved main.py and your server has reloaded without errors.

ⓧ I'd like to double check the full code

Here is the complete main.py after this step. Scroll through and confirm each section matches yours:

  • Imports at the top include import uuid.
  • The conversations dict is defined after the client line.
  • ChatRequest has conversation_id: str | None = None.
  • ChatResponse has conversation_id: str.
  • The /chat endpoint uses client.chats.create() and chat_session.send_message().

Your AI chat assistant now maintains full conversation context. You have a working full-stack application with a FastAPI backend, a React frontend, and an AI that remembers everything you discussed. This is a portfolio-ready project that demonstrates end-to-end AI feature delivery.

Secret mission

Add a New Chat Button and Theme Toggle

Your chat works, but polished portfolio pieces show product thinking. Add a New Chat button that resets the conversation and a dark/light theme toggle that persists across page refreshes. This demonstrates frontend maturity with React state management and localStorage persistence.

Clean Up Your Resources

Clean Up Your Resources

This project runs entirely locally with a free-tier API key, so there are no ongoing costs. Decide whether to keep your resources running, pause them to come back later, or delete them entirely.

Resources you used:

  • The ai-chat-assistant project folder on your Desktop (includes your Python virtual environment, backend code, and React frontend).
  • A Google Gemini API key (free tier, no charges).

Keep everything running

No action needed. Choose this if you're still actively building or plan to push your project to GitHub for your portfolio.

  • The Gemini free tier has no ongoing charges, so your API key costs nothing to keep.
  • Your project files will stay on your Desktop until you decide to move or delete them.
  • To restart the app later, run fastapi dev main.py in the project root and npm run dev in the frontend folder.

Pause - I'll come back to this later

Shut down running processes to free up terminal sessions and memory. Your files stay on disk so you can pick up where you left off.

  • Press Ctrl+C in the terminal running the FastAPI backend to stop it.
  • Press Ctrl+C in the terminal running the Vite frontend dev server to stop it.
  • To restart anytime, activate your virtual environment, run fastapi dev main.py in the project root, then run npm run dev in the frontend folder.

Delete - I don't want to use this again

Remove all project resources and start fresh. This cannot be undone.

  • Stop both servers by pressing Ctrl+C in each terminal window.
  • Delete the project folder. On macOS or Linux, run:
rm -rf ~/Desktop/ai-chat-assistant
  • On Windows, run:
rmdir /s /q %USERPROFILE%\Desktop\ai-chat-assistant
  • Optionally, revoke your Gemini API key. Go to the Google Cloud API Credentials page, find the API key you created for this project, click the Actions icon, and select Delete API key.

Nice Work!

Nice Work!

You just built a full-stack AI chat application from scratch. Your FastAPI backend, React frontend, and Google Gemini integration work together as a complete, demo-ready project.

You've learned how to:

  • Build a complete FastAPI backend with Pydantic request validation and auto-generated API documentation at /docs.
  • Integrate Google Gemini AI via the official Python SDK and implement conversation memory so the AI maintains context across messages.
  • Connect a React frontend to a Python backend using REST APIs with CORS middleware handling cross-origin requests.
  • Secret Mission: Added UX polish with a New Chat button and a persistent dark/light theme toggle using CSS custom properties and localStorage.

Ready to quiz yourself?