AI Build Your First LLM Application
Table of Contents + −
Now we have learned the important fundamentals.
We know about LLMs, prompts, tokens, context, inference, temperature, hallucinations, model knowledge, and application architecture.
Now let’s build a small application so these ideas become practical.
This tutorial uses a local model through Ollama.
That means:
- We do not need a paid API key.
- The model runs on your computer.
- Our backend talks to Ollama.
- The browser talks to our backend.
🛠️ What We Are Building
We will build a simple browser app where the user can type a prompt and receive an answer from a local LLM.
The flow looks like this:
The app will have:
- A prompt text area
- A temperature input
- A send button
- A loading state
- An answer area
- Error handling
- Basic token-style usage information when available
This is not a production app.
It is a beginner-friendly first project.
📌 What You Should Know Before Starting
You should understand these basics:
- A frontend is the part the user sees.
- A backend is server-side code.
- An API endpoint receives requests.
- JSON is commonly used to send data between frontend and backend.
- An LLM generates text from a prompt.
If some of these words are new, do not worry.
We will keep the project simple and explain the flow step by step.
🧰 Tools We Need
For this project, we need:
- Node.js
- npm
- Ollama
- A local model such as
llama3.2 - A browser
- A code editor
Ollama is a local model runner.
It helps us run models on our own machine.
💻 Step 1: Install Ollama
Install Ollama from the official Ollama website for your operating system.
After installation, open a terminal and check:
ollama --versionIf the command prints a version, Ollama is installed.
📦 Step 2: Download a Local Model
Now download a small model.
For example:
ollama pull llama3.2This downloads the model to your computer.
You can test it directly:
ollama run llama3.2Then type:
Explain AI in one sentence.If the model responds, your local model is working.
🗂️ Step 3: Create the Project Folder
Create a folder:
mkdir llm-mini-appcd llm-mini-appInside it, we will create:
llm-mini-app/ package.json server.js public/ index.html app.js styles.cssThis keeps the project small.
The backend will be in server.js.
The frontend files will be inside public.
🧱 Step 4: Initialize Node.js
Run:
npm init -yThis creates package.json.
Now install Express:
npm install expressExpress helps us create a simple backend server.
📦 Step 5: Update package.json
Open package.json and make sure it has this script:
{ "scripts": { "dev": "node server.js" }}If your file already has other fields, keep them.
Only add the dev script.
🖥️ Step 6: Create the Backend
Create server.js in the project root.
const express = require("express");
const app = express();const PORT = 3000;
app.use(express.json());app.use(express.static("public"));
app.post("/api/chat", async (req, res) => { try { const { prompt, temperature } = req.body;
if (!prompt || prompt.trim().length === 0) { return res.status(400).json({ error: "Prompt is required.", }); }
const ollamaResponse = await fetch("http://localhost:11434/api/chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ model: "llama3.2", messages: [ { role: "system", content: "You are a helpful AI tutor. Explain clearly, use simple English, and keep answers practical.", }, { role: "user", content: prompt, }, ], options: { temperature: Number(temperature ?? 0.7), }, stream: false, }), });
if (!ollamaResponse.ok) { throw new Error("Ollama request failed."); }
const data = await ollamaResponse.json(); const answer = data.message?.content ?? "No answer returned.";
res.json({ answer, usageText: createUsageText(data), }); } catch (error) { res.status(500).json({ error: "Could not get an answer from the local model. Check that Ollama is running.", }); }});
function createUsageText(data) { const promptTokens = data.prompt_eval_count; const outputTokens = data.eval_count;
if (typeof promptTokens === "number" && typeof outputTokens === "number") { return promptTokens + " input tokens, " + outputTokens + " output tokens"; }
return "Token usage not available";}
app.listen(PORT, () => { console.log("LLM mini app running at http://localhost:" + PORT);});This backend does several things.
- It serves the frontend from the
publicfolder. - It receives prompts at
/api/chat. - It validates that the prompt is not empty.
- It sends the prompt to Ollama.
- It returns the model answer to the browser.
- It sends token usage text when Ollama provides the counts.
⚙️ Step 7: Understand the Backend Flow
Let’s look at the backend flow slowly.
The browser does not talk to Ollama directly in this project.
The backend sits in the middle.
This is the same basic idea used in many real AI applications.
🤔 Why Use a Backend?
A backend gives us control.
In a real app, the backend can:
- Protect API keys
- Check user login
- Validate prompts
- Add system instructions
- Add RAG context
- Call tools
- Log usage
- Handle errors
- Apply safety checks
Even though this project uses local Ollama, learning the backend pattern is important.
Later, if you use a cloud API, the same architecture still makes sense.
🖼️ Step 8: Create the HTML
Create public/index.html.
<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>LLM Mini App</title> <link rel="stylesheet" href="/styles.css" /> </head> <body> <main class="app"> <section class="panel"> <h1>LLM Mini App</h1> <p>Ask a local model a question.</p>
<label for="prompt">Prompt</label> <textarea id="prompt" rows="8" placeholder="Explain what machine learning is in simple words." ></textarea>
<div class="control-row"> <label for="temperature">Temperature</label> <input id="temperature" type="number" min="0" max="2" step="0.1" value="0.7" /> </div>
<button id="sendButton">Send</button> </section>
<section class="panel"> <h2>Answer</h2> <p id="status">Ready</p> <pre id="answer"></pre> <p id="usage"></p> </section> </main>
<script src="/app.js"></script> </body></html>This page gives the user a simple interface.
The important elements are:
#prompt#temperature#sendButton#status#answer#usage
Our JavaScript will use these IDs.
🧱 Step 9: Create the Frontend JavaScript
Create public/app.js.
const promptInput = document.querySelector("#prompt");const temperatureInput = document.querySelector("#temperature");const sendButton = document.querySelector("#sendButton");const statusText = document.querySelector("#status");const answerBox = document.querySelector("#answer");const usageText = document.querySelector("#usage");
sendButton.addEventListener("click", askModel);
async function askModel() { const prompt = promptInput.value.trim(); const temperature = Number(temperatureInput.value);
if (!prompt) { statusText.textContent = "Please enter a prompt."; return; }
setLoading(true); statusText.textContent = "Thinking..."; answerBox.textContent = ""; usageText.textContent = "";
try { const response = await fetch("/api/chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ prompt, temperature, }), });
const data = await response.json();
if (!response.ok) { throw new Error(data.error || "Request failed."); }
answerBox.textContent = data.answer; usageText.textContent = data.usageText; statusText.textContent = "Done"; } catch (error) { statusText.textContent = error.message; } finally { setLoading(false); }}
function setLoading(isLoading) { sendButton.disabled = isLoading; sendButton.textContent = isLoading ? "Sending..." : "Send";}This frontend code:
- Reads the user’s prompt
- Reads the temperature
- Sends both values to the backend
- Waits for the response
- Shows the answer
- Shows an error if something fails
💡 Step 10: Add Simple Styling
Create public/styles.css.
* { box-sizing: border-box;}
body { margin: 0; font-family: Inter, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; background: #f4f7fb; color: #172033;}
.app { width: min(1100px, calc(100% - 32px)); margin: 40px auto; display: grid; grid-template-columns: 1fr 1fr; gap: 20px;}
.panel { background: #ffffff; border: 1px solid #d9e2ef; border-radius: 8px; padding: 20px;}
h1,h2 { margin-top: 0;}
label { display: block; margin-top: 16px; margin-bottom: 6px; font-weight: 700;}
textarea,input { width: 100%; border: 1px solid #bcc8d8; border-radius: 6px; padding: 10px; font: inherit;}
.control-row { margin-bottom: 16px;}
button { border: 0; border-radius: 6px; padding: 10px 16px; background: #155dfc; color: white; font: inherit; font-weight: 700; cursor: pointer;}
button:disabled { opacity: 0.6; cursor: not-allowed;}
#answer { min-height: 280px; white-space: pre-wrap; line-height: 1.6; background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 6px; padding: 14px;}
#status,#usage { color: #536179;}
@media (max-width: 800px) { .app { grid-template-columns: 1fr; margin: 20px auto; }}The styling is simple.
The goal is not to make a fancy UI.
The goal is to make the application easy to test.
🚀 Step 11: Run the App
Make sure Ollama is running.
Then run your Node server:
npm run devOpen:
http://localhost:3000Type a prompt:
Explain what an API is using a restaurant example.Click Send.
If everything is working, you should see an answer from the local model.
🌡️ Step 12: Test Temperature
Temperature affects how much variation the model may use during generation.
Try this prompt:
Give me five app ideas for learning JavaScript.First use:
Temperature: 0.2Then use:
Temperature: 1.0You may notice that the higher-temperature response feels more varied.
Remember:
Temperature changes generation behaviour. It does not make the model smarter.
🧩 Step 13: Understand Token Usage
Ollama may return counts such as:
- Prompt evaluation count
- Output evaluation count
In this tutorial, we display them as:
input tokens, output tokensThe exact naming can differ between providers.
But the basic idea is the same:
Token usage matters because it can affect:
- Cost
- Latency
- Context limits
- Output length
✍️ Step 14: Add Better Prompt Instructions
Right now, the backend sends a system message:
You are a helpful AI tutor. Explain clearly, use simple English, and keep answers practical.This instruction guides the model’s answer style.
You can change it.
For example:
You are a beginner-friendly coding teacher. Use short points, simple examples, and avoid advanced words unless you explain them first.Then restart the server and test again.
This shows how prompts and system instructions affect the output.
🛡️ Step 15: Add Input Limits
A real app should not accept unlimited input.
You can add a simple length check:
if (prompt.length > 2000) { return res.status(400).json({ error: "Prompt is too long. Please keep it under 2000 characters.", });}Put this check after the empty prompt check.
Why is this useful?
- It protects the app from huge requests.
- It keeps the model response faster.
- It helps control token usage.
- It gives the user a clear rule.
⚙️ Step 16: Add a Model Wrapper Function
Right now, the model call is inside the route.
For a cleaner app, move it into a function.
async function callLocalModel({ prompt, temperature }) { const response = await fetch("http://localhost:11434/api/chat", { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ model: "llama3.2", messages: [ { role: "system", content: "You are a helpful AI tutor. Explain clearly, use simple English, and keep answers practical.", }, { role: "user", content: prompt, }, ], options: { temperature, }, stream: false, }), });
if (!response.ok) { throw new Error("Ollama request failed."); }
return response.json();}Then your route can call:
const data = await callLocalModel({ prompt, temperature });This makes the code easier to change later.
For example, if you switch from Ollama to a cloud API, you mainly update the wrapper function.
🔁 Step 17: Add Streaming Later
In this tutorial, we used:
stream: falseThat means the backend waits for the full answer.
Streaming works differently.
Streaming is useful because:
- The user sees progress quickly.
- Long answers feel faster.
- You can add a stop button later.
But for a first project, non-streaming is easier to understand.
Build the simple version first.
Then add streaming.
⚠️ Step 18: Common Errors
Ollama Is Not Running
If Ollama is closed, the backend cannot connect to:
http://localhost:11434Start Ollama and try again.
Model Is Not Downloaded
If llama3.2 is not downloaded, run:
ollama pull llama3.2Then try again.
Port Is Already Used
If port 3000 is already used, change:
const PORT = 3000;to another port, such as:
const PORT = 3001;Then open:
http://localhost:3001Browser Shows a Generic Error
Check the terminal where npm run dev is running.
Backend errors usually appear there.
The browser only sees the response your backend sends.
🧠 Step 19: What This App Teaches
This small project teaches the real shape of an LLM application.
It also shows why an AI app is more than a model.
The application must handle:
- Input
- Prompt construction
- Model settings
- Errors
- Usage information
- User experience
🌱 Step 20: How This Can Grow
This same project can grow into many useful applications.
Document Chat
Add file upload and RAG.
Coding Helper
Send code context with the prompt.
Support Bot
Retrieve help articles before answering.
Agent Workflow
Add tools that the application can call.
🛡️ Important Safety Notes
Even for a small app, remember:
- Do not trust every model answer.
- Do not let the model perform risky actions without checks.
- Do not expose private keys in frontend code.
- Do not send private data unless your app is designed to handle it safely.
- Do not assume local models are automatically correct.
The model generates text.
The application is responsible for control.
🧩 Complete Project Recap
Here is the complete project again.
llm-mini-app/ package.json server.js public/ index.html app.js styles.cssRun it with:
npm run devOpen:
http://localhost:3000Test with:
Explain what RAG is in simple words.🧩 Key Points
-
What did we build?
A small browser-based LLM application using a Node.js backend and a local Ollama model.
-
Why did we use a backend?
The backend controls validation, model calls, errors, and future security logic.
-
What does the frontend do?
It collects the prompt and shows the answer.
-
What does Ollama do?
It runs the local model.
-
What can we add later?
Streaming, RAG, tools, memory, authentication, logging, and evaluation.
🧩 Where We Are Now
We started with fundamentals:
Now we have built the first simple application shape:
From here, this same project can grow into a document chatbot, support assistant, coding helper, RAG app, or agent-style workflow.