In our previous tutorial, we set up a local Ollama instance: How to Install Ollama locally and run your first model
In this tutorial, we’re going to build a super‑simple chat app using plain HTML and JavaScript. We’ll walk through how the chat logic works, how messages are sent to Ollama, and how the UI updates in real time.
You can download the sample app here:https://github.com/agentic-ai-info/simple-ollama-chat
Overview
Our minimal chat app consists of:
- index.html — a simple UI with a message list, input field and send button
- main.js — the actual chat logic (sending prompts, receiving responses, updating the UI)
- server.js — a tiny Node server that serves the static files and proxies requests to Ollama
The entire app runs locally and communicates with a local Ollama instance.
The Chat Logic (main.js)
Let’s break down the important parts: At the top of the file, we grab references to the DOM elements we need:
const messagesEl = document.getElementById("messages");
const promptEl = document.getElementById("prompt");
const sendBtn = document.getElementById("sendBtn");
const modelEl = document.getElementById("model");
These give us access to:
- the chat message container
- the text input
- the send button
- the model selector
Displaying Messages
Whenever the user or the model sends a message, we append it to the chat window:
function addMessage(text, role) {
const div = document.createElement("div");
div.className = "msg " + role;
div.textContent = text;
messagesEl.appendChild(div);
messagesEl.scrollTop = messagesEl.scrollHeight;
}
This function:
- creates a new <div>
- assigns it a CSS class (user, assistant, or llm)
- inserts the message text
- scrolls the chat window to the bottom
Sending a Message to Ollama
The core of the chat app is the sendMessage() function:
async function sendMessage() {
const prompt = promptEl.value.trim();
if (!prompt || isSending) return;
const model = modelEl.value.trim() || "llama3";
addMessage(prompt, "user");
promptEl.value = "";
promptEl.focus();
}
Here we
- read the user’s input
- prevent double‑sending
- display the user message immediately
- clear the input field
Then we send the request to our proxy endpoint:
const response = await fetch("/proxy/api/chat", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
model: model,
messages: [{ role: "user", content: prompt }],
stream: false
})
});
- We call /proxy/api/chat instead of talking to Ollama directly (to prevent CORS problems)
- The request body matches Ollama’s chat API format
- stream: false keeps things simple (no streaming yet)
Once Ollama replies, we parse the JSON and extract the model’s message:
const data = await response.json();
const content = data.message?.content ?? JSON.stringify(data);
addMessage(content, "llm");
The Minimal Node Server (server.js)
Our ‘server’ does two things:
1. Serves the static files: It delivers index.html, main.js, and any CSS files to the browser.
2. Acts as a reverse proxy to Ollama: Normally the browser is not allowed to call Ollama (http://localhost:11434) directly because of CORS restrictions.
So we forward all requests under /proxy/* to Ollama:
- stripping unnecessary browser headers
- keeping the request clean
- avoiding CORS issues entirely
To run the app, just call
node server.js

Conclusion
This tiny Ollama chat app is an example of how far you can get with just:
- HTML
- pure JavaScript
- a minimal Node server
No frameworks, no build tools, no dependencies.
If you want to extend it, here are some natural next steps:
- add streaming responses
- store chat history
- support multiple models
- add a nicer UI
But even in its minimal form, this setup gives you a fully functional local LLM chat interface that’s easy to understand.
Leave a Reply