Restaurant discovery

JoyTop

joy-top.uz is live: a food guide to Tashkent with a map, search, editors’ collections and a path for venue owners. The website, the mobile app and the Telegram bot run on one database.

Reach usOpen site · joy-top.uz

JoyTop: joy-top.uz

Find · Taste · Connect: where Tashkent decides where to eat.

A guide to eating in Tashkent, chosen by local writers rather than by advertising: selected places, verified menus and real prices, editors' collections, and a map for when you already know what you want. JoyTop is our own product: Nodirkhon is its co-founder and CTO.

Product strategy · UI/UX design · Full-stack engineering · Editorial system

In restaurant lists, whoever paid ranks higher, and the menus are long out of date.

People in Tashkent choose where to eat from lists where the one who paid comes first. Menus and prices in those lists go stale.

JoyTop was designed as a guide where local writers choose the places and every card carries a real menu with real prices, on the website, in the mobile app and in a Telegram bot at once.

We wrote the guide’s rules into the database and the map, not into people’s memory.

A rule in the code decides how a pin looks: an expert’s pick gets the most prominent pin, and a venue without a menu and photo is a plain dot. The database decides what a venue’s plan includes. The website, the app and the bot read the same records.

  1. 01

    Use one shared place model across web and mobile so every surface reads from the same source.

  2. 02

    Use geographic data for nearby discovery and map browsing instead of maintaining separate location lists.

  3. 03

    Keep ranking editorial, with operator tooling for menus, collections, and verification.

  4. 04

    Set each map pin’s look by rule: an expert’s pick gets the most prominent pin, and a venue without full details is a plain dot.

This is JoyTop’s design, rebuilt from its own code.

The window below is not a picture. The Fraunces and DM Sans type, the blue and orange from its tokens, the bowl-shaped map pin and the rating seal are rebuilt live from JoyTop’s source. The map is drawn and shows no real city, and the venues on it are samples. Beside it are screenshots of the public joy-top.uz to compare against.

Метки на карте

Образец А

Национальная

4,9312 оценок

Образец Б

Плов

4,687 оценок

Образец В

Шашлык

This is not a screenshot: it is live HTML and CSS rebuilt from JoyTop’s source. Pick a pin: it grows the way it does on joy-top.uz, and the venue’s row appears below.

Fonts
Fraunces · 300–700
DM Sans · 300–700
JetBrains Mono · 500
Colours
  • --jt-blue-700 #0f1a6c
  • --jt-blue-900 #060a38
  • --jt-orange-500 #f6a34e
  • --jt-orange-700 #c77e2e
  • --jt-cream-100 #fbf8f4
  • --jt-cream-300 #f3efeb
  • --jt-ink-900 #15140f
  • --jt-ink-500 #514e42
  • --border-default rgba(15, 26, 108, 0.16)
  • primary #2D4AA8
Radius
4px · .seal
12px · .searchBtn
16px · .searchBar
Motion
scale(1.34)setPinActive()
0.18s cubic-bezier(.2,.8,.2,1)pin image
Source
web/src/styles/tokens.css
web/tailwind.config.ts
web/app/fonts.ts
web/app/[locale]/map/map.module.css
web/app/[locale]/map/components/MapNav.tsx
web/app/[locale]/map/markerDom.ts
web/app/[locale]/map/pinAsset.ts
web/public/brand/logomark.svg
web/components/collections/Seal.tsx
web/components/collections/Seal.module.css
The left half of the joy-top.uz home page on a laptop: the JoyTop mark and the headline «Ташкент начинается здесь.»The joy-top.uz home page on a phone: the headline, search and popular requests.
Screenshots of the public site joy-top.uz, taken 26 September 2026. No login, no prices.

We measured before fixing, and put the rules where nobody can get around them.

One spatial data system powers the public web product, native mobile application, Telegram bot and the operational tools behind the catalogue.

Surfaces
Next.js web, Expo mobile and a Telegraf bot
Data
PostgreSQL, Supabase, PostGIS, storage and row-level access policies
Discovery
MapLibre plus full-text, trigram and prefix search fused in Postgres
  • Search is a ranking system, not a text box

    Weighted full-text, fuzzy trigram and prefix results are combined with reciprocal-rank fusion, so exact names, inflected terms and misspellings can resolve through one query path.

  • Location is part of the data model

    PostGIS powers nearby discovery and the tiered map from the same restaurant records used by web, mobile and operations.

  • Every role sees a governed surface

    Reader, owner, expert and administrator workflows share one backend while row-level policies and server functions protect tenant and editorial boundaries.

  • External discovery is a controlled fallback

    When the internal catalogue has too few results, cached Yandex results can be blended only after restaurant-category filtering, without replacing JoyTop's own source of truth.

  1. 01

    The slow map, taken apart by measurement

    Without cached data, the map was slow to open. On 14 August 2026 we measured it on the live site: almost half of the response was the same field names, repeated for every venue. The new format sends each name once, and on the same data the response is half the size. Apps already installed on people’s phones keep receiving the old format.

    The cause was found by measuring, not guessing, and nothing broke for people who already have the app.

  2. 02

    One SMS instead of two — on a test send

    A login code written in Cyrillic went out as two paid SMS parts. We wrote the text in Latin script and checked every character: even the Uzbek ʻ would have brought the second part back. A test send on 4 August 2026 arrived as one part: 110 soʻm instead of 220–320.

    This login text costs two to three times less, according to a real send, not a calculation.

  3. 03

    One complaint, several causes

    To a user, every login failure looks the same: the code did not arrive. We wrote down each cause behind that symptom and the real error for each. The login service hides the error text, so the runbook records how to read it.

    A login failure is found from the record, not from one person’s memory.

  4. 04

    A catalogue without guesses

    The catalogue was filled venue by venue from open sources, and every accepted fact records its source and a confidence level. Automation only fills empty fields; only a person can replace what is already recorded. If a venue has closed, changed its name, lost its website to someone else or duplicates another, the system does not guess: it sets the venue aside for manual review with the reason named.

    An empty field is better than a stranger’s phone number on a venue’s card.

  5. 05

    The database holds the plan

    The database, not the app screen, checks what a venue’s plan includes. When a subscription ends, the venue returns to the basic plan, and there the database will not accept an 81st menu item. If the plan cannot be determined, the basic one applies, and two simultaneous additions cannot slip past the limit together.

    A plan means the same thing on the website, in the app and in the database itself.

429 database migrations · 58 website pages on the repository’s main branch. Counted by us on 26 September 2026.

No card will show a stranger’s phone number, and no one gets around a plan through the app.

The database checks what a plan includes, so the website, the app and the bot cannot drift apart. A doubtful catalogue record waits for a person instead of being guessed. If SMS login breaks, the cause is found from the record. And a slow spot is measured first and rewritten only after.

Want a system like this for your business?

Describe the problem in two or three sentences. We reply the same working day and suggest a time to talk.

Reach usDiscuss your project on Telegram

  • We reply the same working day, Tashkent time.
  • You write to Nodirkhon; he brings in whichever of us your problem needs.