On this page
One experience only
“A bar pouring Sapporo Black Label on draft within a five-minute walk of Shinjuku station”, found in three seconds, with a menu photo to confirm it is on tap right now. The MVP of みんなの生ビールマップ (minnama for short) was built to deliver that one sentence.
So a lot was left out. No likes, follows or feeds. No rankings or trust scores. No venue management, reservations, payments or coupons. You search by the beer you want, not by the bar’s name, and users build the data by posting photos of what they found on tap.

How it is built
- Next.js (App Router) with Tailwind and TanStack Query. It installs as a PWA; there is no native app
- Supabase: Postgres with PostGIS for radius search, Google as the only auth provider, images served through Storage’s transformations, RLS on every table
- Maps with MapLibre GL JS on OSM tiles
- Hosted on Vercel, with posts and reports notified to Slack
You can post without an account. Signing in with Google adds favourites, editing your own posts and a public page listing your posts. Browsing and search never require sign-in.

Showing freshness
A beer map lives or dies on “is it on tap now”. Travel to a bar on a year-old post and find the beer gone once, and you never use the map again.
So every bar shows how long it has been since the last confirmation, in four bands: within 30 days, 31 to 90, 91 to 365, more than a year. Lists sort bars with recent confirmations first. When information is wrong, a post from someone who actually visited overrides it as the latest. The design does not have the operator vouch for correctness; it shows exactly how old each piece of information is.

Staying inside the Google Places terms
Venue entry uses Google Places Autocomplete (New) to make typing a name easy. Storing Places API responses as they come, though, violates the terms. After reading them, the storage and display rules came out like this.
| Data | Handling |
|---|---|
place_id |
May be stored permanently (an explicit exception in the terms) |
| Name, address, coordinates | Stored as values the user confirmed or edited |
| The Places response itself | Never stored |
In the posting flow, picking a suggestion pre-fills the form, and the user confirms or edits it before posting. At that point it becomes user-provided content. Only values from the database are ever displayed; nothing from Places is shown, and the Google attribution appears only while suggestions are on screen. Since no Places data reaches the map, OSM tiles are fine.
The autocomplete is behind an abstraction so it can be swapped for Nominatim if the interpretation of the terms changes.
Operating an alcohol service
Some things went in from day one because the subject is alcohol.
- A permanent footer notice that drinking under 20 is prohibited by law in Japan, and an age confirmation with its timestamp on the first post
- Reports in five categories, forwarded to Slack, handled from an admin area that only users with
role = admincan enter - Account deletion by anonymization: posts stay, the link to the author is removed
- The brand master cannot be grown freely. Users submit suggestions into a pending state, and an admin promotes them and merges spelling variants
Next
Around 70 brands are registered, each with a column on its history and taste. Editing and deleting posts and end-to-end tests are still to do. The PWA remains the answer to “where is the native app”. minnama.beer
RELATED
Flutter, AWS, homelab. A few side projects and open-source tools. For architecture questions or collaboration, reach out via X DM or the About page. About