A Point of Sale system built for a coffee shop, running on web, mobile and a customer-facing ordering app. It covers the catalog (categories, products, variants, materials), sales (transactions, carts, tables, tickets, coupons), inventory (suppliers, stock checks), finance (expenses, budgets, wallets, profit calculation) and daily operations (checklists, rentals).
The full feature documentation lives at
gatherloop.github.io/gatherloop-pos (source in
docs-site/).
An Nx monorepo. Apps are thin shells; nearly all frontend code lives in
libs/ui, shared by web, mobile and order.
apps/
api/ Go backend (REST API, MySQL)
web/ Next.js admin/cashier app (Pages Router)
order/ Next.js customer app — scan a table QR, order from your phone
mobile/ React Native (Expo) app for iOS/Android
*-e2e/ Playwright end-to-end tests per app
libs/
ui/ All shared frontend code: entities, use cases, screens, components
api-contract/ OpenAPI spec (src/api.yaml) + generated TS and Go clients
provider/ App-level providers (Tamagui, theme, toast)
docs/ PRDs and TRDs (product and technical design docs)
docs-site/ VitePress feature documentation site
The three frontends share the same UI layer through Tamagui, which renders to both React DOM and React Native.
- Node.js 20+ and npm
- Go 1.24+
- A MySQL database
npm install
cp apps/api/.env.example apps/api/.env # DB credentials, JWT secret, CORS origins
cp apps/web/.env.example apps/web/.env.local
cp apps/order/.env.example apps/order/.env.local
cp apps/mobile/.env.example apps/mobile/.envnpx nx run api:serve # Go API, on the PORT set in apps/api/.env
npx nx run web:dev # POS web app → http://localhost:3000
npx nx run order:dev # customer app → http://localhost:3000
npx nx run mobile:start # React Native dev server (then run-android / run-ios)
npx nx run ui:storybook # component explorer → http://localhost:6006Web and order proxy /api/* to NEXT_PUBLIC_API_BASE_URL, so start the API first. Both default to
port 3000 — to run them side by side, give one another port (npx nx run order:dev --port=3001) and
add that origin to CORS_ALLOWED_ORIGINS in apps/api/.env.
npm test # all unit tests (Jest for TS, go test for the API)
npm run lint
npx nx run web-e2e:e2e # Playwright end-to-end tests
npx nx run api-contract:generate:ts # regenerate TS client after editing src/api.yaml
npx nx run api-contract:generate:go # regenerate Go modelsCodegen runs automatically as a dependency of the dev, build and serve targets — run it
manually only when you want to inspect the output.
In CI, a pull request runs the libs/ui and apps/api unit tests, and only the ones whose area it
touches (.github/workflows/pr-tests.yml). The end-to-end suites are too slow for that loop, so
they run after the merge, against a MySQL service, the real API binary and a real Next.js server
(.github/workflows/e2e-main.yml).
Both sides follow the same Clean Architecture split: domain → data → presentation, where the domain depends on nothing and the outer layers depend inwards through interfaces.
domain/ <entity|repository|usecase>.go per feature
data/mysql/, data/mock/ repository implementations
presentation/restapi/ handler + route + transformer per feature
main.go wires repositories → use cases → handlers
- Domain — entities, business logic in use cases, and repository interfaces. No SQL, no HTTP.
- Data — implements those interfaces against MySQL (or in-memory mocks used by tests).
- Presentation — HTTP handlers built on
gorilla/mux; transformers map between JSON request and response shapes and domain entities.
Dependencies are constructed once in main.go and injected, so use cases are testable in isolation
(see the *_usecase_test.go files next to them).
domain/ entities, repository interfaces, use cases (framework-agnostic)
data/ api/ (OpenAPI client), mock/, memory/, browser/ repository implementations
presentation/ controllers/, screens/, components/
app/ per-route composition: builds repositories + use cases, renders a Handler
- Domain — each use case is a finite state machine: a state union (
idle,loading,loaded,error, …), an action union, and a pure reducer. Contains no React. - Data — implements the repository interfaces, mostly against the generated OpenAPI client with TanStack Query, and maps API types to entities. Swapping in a mock repository is how use cases and screens are tested.
- Presentation — a controller hook binds a use case's state machine to React
(
useReducer+ effects); a*Handlermaps that state to props withts-pattern; a*Screenis pure Tamagui JSX with Storybook stories. - app/ — the composition root: instantiates repositories and use cases, then renders the
handler. Pages in
apps/web,apps/orderandapps/mobilemostly just re-export these.
Every surface builds with the React Compiler, so don't
hand-write useMemo/useCallback/React.memo for re-render performance — opt a misbehaving
component out with "use no memo" instead. (One exception: useCallback around a useFocusEffect
callback is still needed, because the Jest setup has no compiler pass.) See
docs/trd-react-compiler-adoption.md.
docs/— PRDs and TRDs for each feature; read the relevant one before changing behaviour.docs/forms.md— form conventions (react-hook-form + zod).docs/trd-vps-deployment-automation.md— how the API ships: a static binary built in CI and run on a VPS under systemd (.github/workflows/deploy-api.yml). The order app deploys to its own Vercel project (apps/order/vercel.json).docs/trd-storybook-vercel-deployment.md— how the component explorer ships: its own Vercel project rooted atlibs/ui(libs/ui/vercel.json).
