AI Build Your First LLM Application

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:

User

Browser UI

Node.js Backend

Ollama

Local LLM

Response

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:

Terminal window
ollama --version

If the command prints a version, Ollama is installed.


📦 Step 2: Download a Local Model

Now download a small model.

For example:

Terminal window
ollama pull llama3.2

This downloads the model to your computer.

You can test it directly:

Terminal window
ollama run llama3.2

Then type:

Explain AI in one sentence.

If the model responds, your local model is working.


🗂️ Step 3: Create the Project Folder

Create a folder:

Terminal window
mkdir llm-mini-app
cd llm-mini-app

Inside it, we will create:

llm-mini-app/
package.json
server.js
public/
index.html
app.js
styles.css

This keeps the project small.

The backend will be in server.js.

The frontend files will be inside public.


🧱 Step 4: Initialize Node.js

Run:

Terminal window
npm init -y

This creates package.json.

Now install Express:

Terminal window
npm install express

Express 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 public folder.
  • 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.

Browser sends prompt

Express receives request

Backend validates prompt

Backend calls Ollama

Ollama returns model response

Backend sends answer to browser

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:

Terminal window
npm run dev

Open:

http://localhost:3000

Type 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.2

Then use:

Temperature: 1.0

You 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 tokens

The exact naming can differ between providers.

But the basic idea is the same:

Input text

Input tokens

Generated answer

Output tokens

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: false

That means the backend waits for the full answer.

Streaming works differently.

Model starts generating

Backend receives chunks

Frontend shows chunks one by one

Answer appears gradually

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:11434

Start Ollama and try again.


Model Is Not Downloaded

If llama3.2 is not downloaded, run:

Terminal window
ollama pull llama3.2

Then 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:3001

Browser 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.

Frontend

Backend

Prompt

Model

Response

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.

PDF

Chunks

Embeddings

Retrieve relevant chunks

LLM answer

Coding Helper

Send code context with the prompt.

Code

Question

LLM

Explanation or suggestion

Support Bot

Retrieve help articles before answering.

Customer question

Search help docs

Relevant policy

Answer

Agent Workflow

Add tools that the application can call.

Goal

Choose tool

Read tool result

Continue or answer


🛡️ 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.css

Run it with:

Terminal window
npm run dev

Open:

http://localhost:3000

Test 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:

AI

Machine Learning

Deep Learning

Transformers

LLMs

Tokens

Context

Inference

Prompting

Now we have built the first simple application shape:

User

Frontend

Backend

Model

Answer

From here, this same project can grow into a document chatbot, support assistant, coding helper, RAG app, or agent-style workflow.