A framework-free chat app with replies that stream as they arrive

A framework-free chat app with streamed Ollama replies, saved conversation context and cancellable requests.

Neptune chat interface with conversation history

A conversation with a reliable request lifecycle

Neptune is a chat interface built against Ollama’s API, with no framework on the front end and a small Express service behind it. The work was less about the chat bubbles than the request lifecycle around them: replies stream in as they are generated, a response can be cancelled or timed out mid-stream, and the interface reports whether the backend is actually reachable.

What it does

  • Streamed replies. Fetch, a streamed reader and TextDecoder render Ollama responses as they arrive.
  • Cancellation and timeouts. An AbortController ties stopping and timing out to each request.
  • Saved context. Conversation context stays available between messages.
  • Visible backend health. A heartbeat checks a health endpoint so the interface can report when the backend is unavailable.
  • A compact mobile composer. Starting prompts, model controls and message actions remain within reach.
  • Separated API responsibilities. Express routing, controllers, middleware and inference have clear boundaries.

The Express backend separates routing, controllers, middleware and inference, so the parts that validate a request are not tangled with the parts that talk to the model. Replies stream to the browser through the Fetch API and a streamed reader, decoded with TextDecoder and rendered as they arrive rather than waiting for the full response.

An AbortController ties cancellation and timeouts to each request, and a heartbeat polls a health endpoint so the interface can show when the backend is unavailable instead of failing silently. The API is rate-limited per IP with proxy-aware client detection, validated input and an environment-configured CORS allowlist, and it logs requests with timing and user-agent data. The front end deploys on GitHub Pages and the API on Render.