Thuta Learning
IntermediateAIbeginner

Building a Local AI App (JavaScript/TypeScript)

What you'll walk away with

  • Explain the core ideas behind Building a Local AI App (JavaScript/TypeScript)
  • Read the diagram and trace how data or requests flow through the architecture
  • Decide what this means for your own hardware and use case

Build the mental model

Knowing that a local model exposes an HTTP API is only useful once you know where that call belongs in a real application, and the honest answer is: almost never directly in the browser. A typical small local-AI app has three layers.

  • Frontend — what the user sees and clicks, built with plain HTML/JS or a framework like React, running inside the user's browser.
  • Backend — a small server you control, written in something like Node.js or Express, that receives requests from the frontend and decides what to do with them.
  • Local model API — served by Ollama or a similar runtime, this is what actually generates text.

The correct flow is frontend calls backend, and backend calls the local model API, never frontend calls the local model API directly. This matters for three concrete use cases you'll see repeatedly.

  • Chat feature — the backend forwards each user message to the model and streams the reply back.
  • Summarizer — the backend receives a long document from the frontend, wraps it in a summarization prompt, and returns a shorter version.
  • Translator — the backend builds a translation-specific system prompt around whatever text and target language the frontend sent.

Routing everything through your own backend gives you one place to validate input, rate-limit requests, log usage, and add authentication before anything reaches the model. Skipping the backend and calling the local API straight from client-side JavaScript works fine on your own laptop during development, but it quietly assumes the browser and the model server are the same trusted machine, an assumption that breaks the moment you deploy.

text
SAFE VS INSECURE LOCAL AI ARCHITECTURE
--------------------------------------
------------------------------------------------------------
  SAFE PATTERN
  --------------------------------------------
  BROWSER  -->  YOUR BACKEND  -->  LOCAL MODEL API  -->  MODEL
  (frontend)    (validates,        (Ollama /api/chat)
                 rate-limits,
                 logs, auths)

  INSECURE ANTI-PATTERN -- do not do this
  --------------------------------------------
  BROWSER  ------------------------------->  LOCAL MODEL API
  (frontend)   calls the model API directly,   (exposed, open,
               no backend in between            unauthenticated)

Connect it to a real scenario

Consider building a small internal translator tool: a coworker pastes English text into a web page, picks a target language, and gets a translation back.

Frontend calls the backend

The frontend is a simple form that, on submit, sends a `fetch()` POST request not to the model directly but to your own backend route, something like `/api/translate`, running on a Node.js/Express server you control.

Backend builds the translation prompt

That backend route receives the text and target language, and builds a translation-focused system prompt around them.

Backend calls the local model API

Only then does it call the local model's API at `http://localhost:11434/api/chat`, wait for the JSON response, and extract the translated text.

Backend responds to the frontend

It sends that translated text back to the frontend as its own response.

The frontend never learns the model's address, its port, or that Ollama is even involved; it only knows how to talk to your backend. This indirection is exactly what lets you add a request limit per user, log which translations were requested, or later swap Ollama for a different local runtime, all without touching the frontend code at all.

Never Expose an Unauthenticated Local AI Server

Do not point a public domain, a reverse proxy, or a port-forward directly at Ollama's or llama.cpp's raw API and call it done. These local servers ship with no built-in authentication by default — anyone who can reach the address can send it requests, read whatever the model generates, and potentially rack up compute cost or abuse on your machine. If a local AI feature needs to be reachable from outside your own machine, put your own backend in front of it, and have that backend handle authentication, rate limiting, and input validation before it ever calls the local model API — never the other way around.

Try the working example

typescript
// backend route handler, e.g. Express: app.post("/api/translate", translateHandler)

interface TranslateRequestBody {
  text: string;
  targetLanguage: string;
}

interface OllamaChatResponse {
  message: { role: string; content: string };
}

const OLLAMA_CHAT_URL = "http://localhost:11434/api/chat";

async function translateHandler(
  req: { body: TranslateRequestBody },
  res: { status: (code: number) => { json: (body: unknown) => void } }
): Promise<void> {
  const { text, targetLanguage } = req.body;

  const systemPrompt =
    `You are a translation engine. Translate the user's text into ` +
    `${targetLanguage}. Reply with only the translated text.`;

  const payload = {
    model: "llama3.2",
    messages: [
      { role: "system", content: systemPrompt },
      { role: "user", content: text },
    ],
    stream: false,
  };

  try {
    const response = await fetch(OLLAMA_CHAT_URL, {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify(payload),
    });

    if (!response.ok) {
      res.status(502).json({ error: "Local model server returned an error." });
      return;
    }

    const data = (await response.json()) as OllamaChatResponse;
    res.status(200).json({ translated: data.message.content });
  } catch (error) {
    res.status(503).json({ error: "Local model server is unreachable." });
  }
}

Not runnable here

This code calls your own local AI server (e.g. Ollama) — this site's browser playground cannot reach a local server on your machine (network access is restricted there for safety). Run it yourself in a terminal or file where a local server is actually running.

You should see
This code is not executed here because it depends on a locally running Ollama server and an HTTP framework's request/response objects, neither of which exist in this environment. In a real deployment, a successful call returns HTTP 200 with a JSON body like `{ translated: '<translated text>' }`, where the actual translated string comes from the model and is not something to hardcode or predict. If Ollama is unreachable, the handler responds with HTTP 503 and an error message instead of crashing the server process; if Ollama responds with an HTTP error status, the handler responds with HTTP 502.

5-minute try-it

Add a `sourceLanguage` field to `TranslateRequestBody` (optional, defaulting to 'auto-detect' when absent), interpolate it into the system prompt alongside `targetLanguage`, and write out — without running it — what the `payload.messages` array would look like for translating 'good morning' from English to Burmese.

One important caution

Calling the local model API directly from frontend JavaScript because it 'works on my machine' during development, then shipping that same code to production.

Exposing the local model's raw API through a reverse proxy or port-forward without adding your own authentication and rate limiting in front of it.

MDN: Using the Fetch APILocal AI / Local LLM

Easy traps

  • Calling the local model API directly from frontend JavaScript because it 'works on my machine' during development, then shipping that same code to production.
  • Exposing the local model's raw API through a reverse proxy or port-forward without adding your own authentication and rate limiting in front of it.
  • Try a new model or tool at a small scale before wiring it into a production or daily-use workflow.

Exercise

Add a `sourceLanguage` field to `TranslateRequestBody` (optional, defaulting to 'auto-detect' when absent), interpolate it into the system prompt alongside `targetLanguage`, and write out — without running it — what the `payload.messages` array would look like for translating 'good morning' from English to Burmese.

You'll know it worked when: This code is not executed here because it depends on a locally running Ollama server and an HTTP framework's request/response objects, neither of which exist in this environment. In a real deployment, a successful call returns HTTP 200 with a JSON body like `{ translated: '<translated text>' }`, where the actual translated string comes from the model and is not something to hardcode or predict. If Ollama is unreachable, the handler responds with HTTP 503 and an error message instead of crashing the server process; if Ollama responds with an HTTP error status, the handler responds with HTTP 502.

Building a Local AI App (JavaScript/TypeScript) | Thuta Learning