Back to blog

Building a chat app with Appwrite and Google Gemini

Learn how to build a chat application using Appwrite and the Google Gemini API.

If you're looking to build a chat app that doesn't just handle basic messages but actually understands and responds intelligently, you found the right tutorial! In this guide, we'll learn how to create a chat application using Appwrite's local function development and the Google Gemini API to generate AI responses.

Appwrite takes care of the backend so you can focus on the functionality, while Google's Gemini provides the AI that will give your app the ability to respond to user messages in real-time. By the end, you'll have a working chat app that does more than just send and receive messages — but interacts with users in a way that feels almost human.

Gif of the chat app in action

This project is practical for anyone interested in enhancing their apps with intelligent, real-time interactions without getting too deep into managing servers or infrastructure. Let's get started!

Prerequisites

Before we begin, make sure you have the following tools installed:

  • Node.js: We'll be using version 18.0 for this project.

  • Docker: Appwrite functions are containerized, and Docker will be required to run them locally.

  • Appwrite CLI: This is necessary for managing Appwrite projects and functions locally.

  • A Google Gemini API key: You'll need this for accessing Google's AI models. We'll cover how to get one in this guide.

  • A code editor: Any code editor, such as VS Code, will work.

Step 1: Setting up Appwrite locally

To get started, we need to set up Appwrite on your local machine. This will enable us to test our serverless function locally before deploying it.

Step 1.1: Installing the Appwrite CLI

Appwrite provides a CLI tool that helps manage functions and projects from your terminal. Install it globally with the following command:

Bash
npm install -g appwrite-cli@latest

To confirm the installation, run:

Bash
appwrite --version

If everything is set up correctly, you should see the version number of the CLI printed in your terminal.

Step 1.2: Logging into Appwrite

Next, log into Appwrite so the CLI can authenticate your session. Run:

Bash
appwrite login

This will prompt you to log in with your Appwrite credentials. If you don't already have an account, head over to our cloud console and create one.

Step 1.3: Creating a new Appwrite project

After logging in, we need to create a new Appwrite project. Navigate to the directory where you want to build the project and run:

Bash
appwrite init project

Appwrite will ask you if you want to create a new project or link to an existing one. Choose Create new project, then provide a name (e.g., AI Chat) and an ID for your project. This will create a new Appwrite project in your current directory.

Step 2: Creating the Appwrite serverless function

With your Appwrite project set up, let's move on to creating the serverless function that will handle the chat logic. This function will accept user messages, forward them to the Google Gemini API, and return AI-generated responses.

Step 2.1: Initializing the function

To create a new function, run the following command:

Bash
appwrite init functions

You'll be asked to provide the following:

  • Function name: Let's call it Chat Gemini.

  • Function ID: You can use something like chat-gemini-id.

  • Runtime: Select Node.js (18.0) as the runtime environment.

This will generate a new functions folder in your project directory with the Chat Gemini function inside. The function will have a src folder containing the main JavaScript file (main.js) for the function logic.

To run the function locally, use the following command:

Bash
appwrite run function

When you run the command, you'll see a prompt to select the function you want to run. Choose the Chat Gemini function, and the function will start running locally. Click the provided link to access the function in your browser or you can use an API client like Postman to test it.

Step 2.2: Writing the function logic

Open the src/main.js file in your editor. This is where we'll handle user input, communicate with the Google Gemini API, and return AI-generated responses. Let's break down the process step by step.

Import required modules

At the top of the file, import the necessary modules. We'll use node-fetch to handle HTTP requests, and we'll import a helper function (getStaticFile) that will serve the HTML frontend later.

React
import fetch from 'node-fetch'
import { getStaticFile } from './utils/staticFile.js'
  • fetch will allow us to send requests to the Google Gemini API.

  • getStaticFile (which we'll write shortly) helps serve static files like HTML for the frontend.

To install node-fetch, run:

Bash
npm install node-fetch

Initialize conversation context

Next, we'll set up an array to store the conversation context. This array will store the last 5 messages exchanged between the user and the AI. Keeping the conversation history allows the AI to respond with more context-aware replies.

React
let conversationContext = []

We're storing the conversation context in memory, so that the chat history is maintained for the current session. For a production app, you might want to use a database to store this data.

Handling GET requests

Now, let's handle GET requests. When a user accesses the root URL (/), we'll serve an HTML page that acts as the frontend for the chat app. To do this, we'll use the getStaticFile function (which we'll define later) to return the index.html file.

React
if (req.method === 'GET') {
  return res.send(getStaticFile('index.html'), 200, {
    'Content-Type': 'text/html; charset=utf-8',
  })
}
  • This checks if the request method is GET. If it is, we use the helper function getStaticFile to serve the index.html page.

  • The 200 status code indicates that the request was successful, and we specify the content type as text/html.

Handling POST requests

Next, let's handle POST requests. When a user submits a message, the function captures that message, appends it to the conversation context, and forwards it to the Google Gemini API.

Here's how the POST request handling is structured:

React
if (req.method === 'POST') {
  const body = req.body

// Add the user's message to the conversation context
  conversationContext.push(`User: ${body.prompt}`)

// Keep only the last 5 messages in the conversation
const context = conversationContext.slice(-5).join('\n')

// Construct the prompt for the AI, providing context and instructions
const prompt = `${context}\nAI: Your output should be HTML. Do not include any heading or body tags. Just the content. Respond to greetings with a polite greeting.`

const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash-latest:generateContent?key=${process.env.GEMINI_API_KEY}`

Let's break it down:

  1. Receive the user's message: We retrieve the user's message from req.body.prompt, then add it to the conversationContext array.

  2. Trim the context: We keep only the last 5 messages in the conversation to avoid overwhelming the AI model and minimize API costs. This is done using the .slice(-5) method.

  3. Build the AI prompt: We generate the prompt that will be sent to the AI. The prompt consists of the conversation context followed by an instruction telling the AI to respond in HTML and avoid using body or heading tags. This is so that our frontend can display the AI's response correctly.

  4. Construct the API URL: We use the Google Gemini API URL and append the API key as a query parameter. The API key is stored in an environment variable (process.env.GEMINI_API_KEY), which we'll set up later.

Send the prompt to Google Gemini API

Next, let's send this prompt to the Google Gemini API to generate an AI response. We'll use the fetch function for this, passing in the API URL and the prompt.

React
  try {
    const response = await fetch(url, {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }),
    })

    const data = await response.json()
    const generatedText = data.candidates?.[0]?.content?.parts?.[0]?.text || 'No response received'

In the above code:

  • Constructing the fetch request: We send a POST request to the Google Gemini API URL with the user's prompt in the request body. The response from the AI is returned in JSON format.

  • Extracting the response: The AI response is extracted from data.candidates[0]. If the response is empty, we return a default message: "No response received."

Return the AI response to the user

Once we have the AI's response, we append it to the conversationContext array and return it to the frontend.

React
// Add the AI's response to the conversation context
conversationContext.push(`AI: ${generatedText}`)

// Return the AI's response to the client
return res.json({ ok: true, output: generatedText })

  } catch (err) {
    return res.json({ ok: false, error: err.message })
  }
}
  • Adding the AI response to the conversation: Just like the user message, the AI's response is added to the conversation context.

  • Returning the response: The AI-generated response is returned to the frontend as JSON. If any error occurs (e.g., network issues or API failures), an error message is returned instead.

Step 3: Getting your Google Gemini API key

Before running your function, you need an API key from Google to access the Gemini API.

Step 3.1: Create your Google Gemini API key

Follow these steps to generate an API key:

  1. Visit Google AI Studio.

  2. Click "Get API Key" in the top-left corner.

Screenshot of the Google AI Studio dashboard with the "Get API Key" button highlighted

  1. On the API key page, click "Create API Key".

*Screenshot of the API key creation page*

  1. Choose your project (or create a new one), then click "Create API Key".

  2. Copy the API key and save it securely—you'll need it in the next step.

*Screenshot of the API key generated*

Step 3.2: Store the API key in an environment file

Now that you have your Google Gemini API key, we need to store it securely. In the root of your function's folder, create a .env file with the following content:

Bash
GEMINI_API_KEY=your-google-gemini-api-key

Replace your-google-gemini-api-key with the actual key you generated earlier.

Step 4: Writing our utility function

Before we can serve the HTML frontend, we need the helper function getStaticFile to read static files from the filesystem. Let's write that utility now.

Step 4.1: Create the utils folder

Inside your src directory, create a new folder named utils. This folder will store helper functions that we'll use in the app.

Step 4.2: Write the static file reader

In the utils folder, create a file called staticFile.js. This file will contain our function that reads and returns static HTML files from disk, which we'll use to serve the frontend:

React
import path from 'path'
import fs from 'fs'
import { fileURLToPath } from 'url'

const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)

const staticFolder = path.join(__dirname, '../../static')

/* Function to read and return the content of a static HTML file */
export function getStaticFile(fileName) {
  return fs.readFileSync(path.join(staticFolder, fileName)).toString()
}

Our getStaticFile function reads files from the static folder and returns their content as a string. We'll use this function to serve the HTML frontend.

Step 5: Building the frontend

Now that we've set up the backend logic, let's create a simple HTML frontend where users can interact with our Gemini chatbot.

Step 5.1: Create the index.html file

In the static folder of your project, create a file named index.html. This file will serve as the user interface for the chat app.

HTML
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Chat Gemini - Appwrite</title>
    <style>
      /* Add basic styles for the chat interface */
    </style>
  </head>
  <body>
    <div class="chat-container">
      <div class="chat-header">Chat with Gemini</div>
      <div class="chat-box" id="chat-box">
        <!-- Chat messages will be appended here -->
      </div>
      <form id="chat-form" class="chat-form">
        <input
          type="text"
          id="prompt"
          name="prompt"
          class="chat-input"
          placeholder="Type your message..."
          required
        />
        <button type="submit" class="chat-submit">Send</button>
      </form>
    </div>
  </body>
</html>

The HTML file contains a basic structure for the chat interface. It has a chat container with a header, a chat box to display messages, and a form for users to input messages.

Next, let's add our frontend JavaScript code. We'll do this in a <script> tag just before we close the <body> tag:

React
<script>
  document
    .getElementById('chat-form')
    .addEventListener('submit', async function (event) {
      event.preventDefault()
      const prompt = document.getElementById('prompt').value
      const chatBox = document.getElementById('chat-box')

      /* Append the user's message */
      const userMessage = document.createElement('div')
      userMessage.classList.add('chat-message', 'user')
      userMessage.textContent = prompt
      chatBox.appendChild(userMessage)

      /* Scroll to the bottom of the chat */
      chatBox.scrollTop = chatBox.scrollHeight

      /* Clear the input field */
      document.getElementById('prompt').value = ''

      /* Display 'Processing...' in bot message view */
      const botMessage = document.createElement('div')
      botMessage.classList.add('chat-message', 'bot')
      botMessage.textContent = 'Processing...'
      chatBox.appendChild(botMessage)

      /* Scroll to the bottom of the chat */
      chatBox.scrollTop = chatBox.scrollHeight

      /* Send the user's message to the serverless function
        and display the AI's response */
      try {
        const response = await fetch('/', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify({ prompt: prompt }),
        })

        const data = await response.json()

        if (data.ok) botMessage.innerHTML = data.output
        else botMessage.textContent = 'Error: ' + data.error
      } catch (error) {
        botMessage.textContent = 'Error: ' + error.message
      }

      /* Scroll to the bottom of the chat */
      chatBox.scrollTop = chatBox.scrollHeight
    })
</script>

Let's breakdown our JavaScript logic:

  • Form submission: When the user submits a message, the form is prevented from reloading the page. Next, the user's message is displayed in the chat box.

  • Sending the message to the serverless function: The user's message is sent to the serverless function as a POST request. While waiting for the AI response, we display a “Processing...” message in the chat. This lets the user know that the AI is working on their request. After receiving a response from Google Gemini, the UI is updated with the AI-generated message. If an error occurs, an error message is displayed instead.

  • Scrolling to the bottom: After each message is displayed, the chat box is scrolled to the bottom so that the latest messages are always visible.

With the current structure, you can open the index.html file directly in your browser to see what it looks like. It won't look pretty just yet, and the chat functionality won't work because we need to run the Appwrite function through the CLI, which will serve the HTML file and handle the Gemini API logic.

Before we run the function, let's add some CSS styles to make the chat interface visually appealing.

Step 5.2: Adding CSS styles

To make the chat interface visually appealing, let's add some CSS styles. Update the <style> section in the index.html file with the following styles:

CSS
<style>
      body {
        font-family: 'Roboto', sans-serif;
        background-color: #1e1e2f;
        color: #ffffff;
        margin: 0;
        display: flex;
        justify-content: center;
        align-items: center;
        min-height: 100vh;
        padding: 10px;
        box-sizing: border-box;
      }
      .chat-container {
        width: 100%;
        max-width: 600px;
        background-color: #2c2c3e;
        border-radius: 10px;
        box-shadow: 0px 10px 30px rgba(0, 0, 0, 0.2);
        overflow: hidden;
        display: flex;
        flex-direction: column;
        max-height: 100%;
      }
      .chat-header {
        background-color: #3b3b51;
        padding: 20px;
        text-align: center;
        font-size: 1.5em;
        font-weight: bold;
        border-bottom: 1px solid #444;
      }
      .chat-box {
        flex-grow: 1;
        padding: 20px;
        overflow-y: auto;
        display: flex;
        flex-direction: column;
      }
      .chat-message {
        margin-bottom: 15px;
        padding: 10px;
        border-radius: 8px;
        background-color: #4a4a66;
      }
      .chat-message.user {
        align-self: flex-end;
        background-color: #5865f2;
      }
      .chat-message.bot {
        align-self: flex-start;
        background-color: #4a4a66;
      }
      .chat-form {
        display: flex;
        border-top: 1px solid #444;
      }
      .chat-input {
        flex-grow: 1;
        padding: 15px;
        border: none;
        border-radius: 0;
        background-color: #3b3b51;
        color: #ffffff;
        font-size: 1em;
        outline: none;
      }
      .chat-input::placeholder {
        color: #ccc;
      }
      .chat-submit {
        background-color: #5865f2;
        color: #ffffff;
        border: none;
        padding: 15px 20px;
        cursor: pointer;
        font-size: 1em;
        transition: background-color 0.3s;
      }
      .chat-submit:hover {
        background-color: #4854d8;
      }
    </style>

This styling will give the chat interface a modern look and feel. The chat messages will be displayed in alternating colors for user and Gemini messages, and the chat box will scroll automatically as new messages are added.

Step 6: Running and testing locally

Now that everything is set up, let's run the application locally and test it. If you already have the function running, you can skip the next step.

Step 6.1: Running the function locally

Ensure Docker is running, then use the following command to start the serverless function locally:

Bash
appwrite run function

This will start the Appwrite function in your local environment. You should see a message indicating that the function is running and a link to access it.

Step 6.2: Testing the application

Open the link provided by the Appwrite CLI in your browser. You should see the chat interface with the message input field. Try sending a message to the AI, and you should receive a response in the chat box!

Gif of the chat app in action

You can also use cURL or Postman to test the serverless function by sending a POST request with a sample prompt:

Bash
curl -X POST http://localhost:3000/ \
-H "Content-Type: application/json" \
-d '{"prompt": "Hello, how are you?"}'

Step 7: Deploying the application

Once you've verified that the application works locally, it's time to deploy it to the Appwrite cloud. This will make your chat app accessible to anyone with an internet connection.

Step 7.1: Deploying the function to the cloud

To deploy the serverless function, run the following command:

Bash
appwrite push function

This will return a prompt asking you to select the function(s) you want to deploy. Choose the Chat Gemini function, and hit Enter. Appwrite will deploy the function to the cloud, with a success message that looks like this:

Bash
(base) user appwrite-project % appwrite push function
? Which functions would you like to push? chat-gemini (chat-gemini)
ℹ  Info: Validating functions ...
ℹ  Info: Checking for changes ...
ℹ  Info: Pushing functions ...
✓  Deployed     • chat-gemini (chat-gemini)
✓  Success: Successfully pushed 1 functions.

You can now navigate to your Appwrite cloud console > Projects (select your project) > Functions to see the deployed function. Click on the function to view its details, including the function URL.

Screenshot of the function details in the Appwrite console

With the function deployed, you now have a live chat application built with Appwrite and Google Gemini API. Users can interact with the AI chatbot in real-time, and you can continue to enhance the app with more features, customizations, and integrations.

Conclusion

In this tutorial, you've learned how to build a chat application using Appwrite local function development and Google Gemini API. We covered everything from setting up the environment to writing the serverless function, creating a frontend, and testing the app locally before deploying it to the cloud.

Appwrite's local function development is a powerful tool for building and testing serverless applications efficiently. Now that your chat app is up and running, you can expand its functionality, refine the frontend, or scale it to handle more complex interactions.

If you have any questions or feedback, feel free to reach out to the Appwrite community on Discord or send me a message on LinkedIn.

You can view the complete codebase for our Gemini + Appwrite chat app on GitHub.

Further reading:

Subscribe to our newsletter

Sign up to our company blog and get the latest insights from Appwrite. Learn more about engineering, product design, building community, and tips & tricks for using Appwrite.