Najimi

Architecture

How Najimi is built and run.

Clients

  • Phone and desktop browsers
  • Link previews (OG images)
  • Docs (this site)

Web

  • Next.js 16 App Router
  • Partial prerendering
  • AI SDK 7 agent
  • Better Auth (guests, email codes, Google)
  • next-intl, 12 languages

Worker

  • pg-boss queues
  • Taste reveal
  • Image and video jobs
  • Chat titles
  • Nightly guest sweep

Data

  • Postgres 17 (Drizzle)
  • RustFS (S3) for photos and creations
  • LISTEN/NOTIFY for live events

Outside

  • Qloo (taste graph)
  • OpenAI (chat)
  • fal (images, video)
  • ElevenLabs (voice)
The system, top to bottom

The repository

A pnpm and Turborepo monorepo:

PathWhat
apps/webThe product: pages, API routes, the chat route, cards.
apps/workerEvery slow job. Runs migrations at start.
apps/docsThis site (Fumadocs, a static export).
packages/agentThe model registry, the tools and the rules.
packages/qlooThe Qloo client: caching, de-duplication, call logging, taste and explore helpers.
packages/dbDrizzle schema, migrations, data ownership (move a guest's data on sign-in, delete all of a person's data).
packages/mediaImage and video providers behind one interface.
packages/sourcesSafe fetching of outside pages, JSON-LD parsing and a Postgres cache for outside sources.
packages/storageS3 objects, signed media links, renditions.
packages/jobs, live, email, env, config, tokens, uiQueues, live events, email codes, typed env, every tunable, design tokens, components.

A chat turn

  1. POST /api/chat

    Only the newest message; the server holds the conversation

  2. Context

    Person, loves, taste words, photos, mode

  3. streamText + tools

    Up to 10 steps; Qloo, making, plans

  4. UI message stream

    Cards stream in; saved after every step

One turn, from the person's message to a saved answer
  • The turn keeps running if the page closes (the stream is drained on the server), and a reload shows it.
  • Images and video need an approval in Ask first mode. Approvals are signed, so a client can't forge one.
  • The daily allowance is taken atomically in Postgres, with a ceiling for the whole server.

Making things

  1. makeImage

    Prompt built from taste words; the person's photo when restyling

  2. media_jobs row

    Allowance taken, job queued

  3. Worker

    Provider call; renditions (thumb, display) with sharp

  4. Live event

    The card swaps its skeleton for the result

An image, from tool call to card

Videos are submitted to the provider and finished by a poller, so a restart picks them up again. A paid request is never sent twice; a failed one is refunded.

Live updates

Workers write run events to Postgres and NOTIFY a channel. The web app holds one LISTEN connection and streams events to open pages over server-sent events, replaying any a reconnecting page missed.

Media

Photos and creations live in a private bucket. Every media link goes through the app, which serves it to its owner, to a provider holding a short-lived signed link, to anyone holding a live share token (creations only), or to everyone when public (sample rooms, the showcase).

Running it

Production runs on Coolify: the web app, the worker, Postgres, RustFS and this docs site, each a container. The worker applies migrations when it starts.

On this page