Skip to content

feat(mobile): port the Expo app to the Go server - #3

Merged
jayleaton merged 2 commits into
mainfrom
feat/mobile
Oct 22, 2026
Merged

jayleaton merged 2 commits into
mainfrom
feat/mobile

Conversation

@jayleaton

@jayleaton jayleaton commented Oct 22, 2026 •

Copy link
Copy Markdown
Owner

Problem

The Go template has no mobile client yet. The Expo app in ai-patterns-monorepo depends on that repo's Node workspace (@better-stack-monorepo/common, a root vitest config), and it has never run against the Go auth and API.

How

apps/mobile-app is now a standalone pnpm project with its own package.json and pnpm-lock.yaml. Nothing outside this folder needs Node.

apps/mobile-app/
  src/lib/api/types.ts          NEW   envelope + users API types (replace @better-stack-monorepo/common)
  src/lib/api/client.ts               apiFetch: session cookie, envelope, JSON body
  src/lib/auth/authClient.ts          better-auth + @better-auth/expo, UNCHANGED code
  src/lib/services/userService.ts     + updateUserSettings (PATCH), deleteAccount (DELETE, then local sign-out)
  src/lib/validators/userSchemas.ts NEW mirrors validate.UpdateUserSettings
  src/app/(app)/dashboard.tsx         + settings form, delete account (confirm dialog)
  contract/                     NEW   app clients vs a real Go server + Postgres
  vitest.config.mts / vitest.contract.config.mts
.github/workflows/ci.yml              mobile job: npm → pnpm (cached), tsc, expo lint, expo install --check, unit tests
  • @better-auth/expo works unchanged. packages/auth keeps Better Auth's paths, JSON shapes, cookie name and signing format, and trusts expo-origin: betterstack://. So the stock Expo plugin signs up, signs in, restores the session from the keychain, and signs out without any changes. I did not switch to bearer tokens.
  • The contract test imports the app's real modules (authClient, userService, apiFetch) in Node and stubs only the native modules (react-native, expo-secure-store as an in-memory keychain, expo-linking, …). globalSetup builds apps/web/cmd/server from GO_SERVER_SRC (default: this repo), starts a tmpfs postgres:18-alpine on a free port, runs server migrate, then server serve in development mode (dev mailer, verification off). It tears everything down afterwards.
  • CI: the mobile job still runs only when apps/mobile-app/** changes (dorny/paths-filter mobile output) and is skipped on drafts like the other jobs. It does not run the contract test, which needs Go + Docker; run it locally with pnpm test:contract.

Evidence

Before: apps/mobile-app did not exist. The CI mobile job was a placeholder guarded by hashFiles.

After: all of these ran locally on b84f12d (rebased on main c7cf5c5, real auth wired), from a clean pnpm install --frozen-lockfile:

Check Result
pnpm exec tsc --noEmit pass
pnpm exec expo lint 0 problems
pnpm exec expo install --check Dependencies are up to date (bumped react-native 0.86.2 → 0.86.3 to match SDK 57)
pnpm test (vitest) 2 files, 8 tests passed (original envelope tests ported + settings schema)
pnpm test:contract 1 file, 10 tests passed in 8.2 s, server built from this repo's apps/web/cmd/server (no overrides)
expo export --platform ios --platform android iOS 3.6 MB / Android 4 MB Hermes bundles (Metro resolves under pnpm's isolated layout)
repo make lint / make test / make actionlint golangci-lint 0 issues, go test -race ./... ok incl. packages/auth, actionlint ok (go list ./... doesn't see node_modules: .pnpm is a dot-dir)

Contract scenarios: sign-up → get-session → sign-out (keychain cleared) → wrong password (401 INVALID_EMAIL_OR_PASSWORD) → sign-in → GET /users → PATCH /users (set + clear image) → validation 400 and PATCH /users/{other} 403 as envelope errors → cold relaunch restores the session from the keychain (atom pre-filled from cache, then confirmed by get-session) → sign-out → GET /users = {data:null,error:"Unauthorized"}. Delete account: DELETE /users → local session cleared → sign-in rejected.

The Go server's own request log from that run:

POST /api/auth/sign-up/email 200 ×2   POST /api/auth/sign-in/email 200 ×1, 401 ×2
GET  /api/auth/get-session   200 ×8   POST /api/auth/sign-out     200 ×3
GET  /api/core/v1/users      200 ×3, 401 ×1
PATCH /api/core/v1/users     200 ×2, 400 ×1   PATCH /api/core/v1/users/someone-else 403
DELETE /api/core/v1/users    200

Limitation: I didn't run the app on a simulator or device (no simulator on this box), so the screens are verified by typecheck, lint and bundling only.

Merge Danger

Door: two-way. This adds a new folder and changes the mobile CI job. It has no data, schema or deploy effects.

Blast Radius: mobile. It touches only apps/mobile-app/** and the mobile job in ci.yml. Editing ci.yml also trips the go path filter, so this PR runs the Go job once.

🤖 Generated with Claude Code

jayleaton and others added 2 commits October 22, 2026 14:02
Standalone pnpm project in apps/mobile-app (own lockfile, no root Node
workspace). @better-auth/expo works unchanged against packages/auth. Local
API types replace @better-stack-monorepo/common. Adds settings update and
delete account to the dashboard, and a contract test that runs the app's
clients against a real Go server and Postgres.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Typecheck, expo lint, expo install --check and unit tests, with the pnpm
store cached. Still path-filtered to apps/mobile-app/** and skipped on drafts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jayleaton
jayleaton marked this pull request as ready for review October 22, 2026 07:04
@jayleaton
jayleaton merged commit 2fd7510 into main Oct 22, 2026
8 of 12 checks passed
@jayleaton
jayleaton deleted the feat/mobile branch October 22, 2026 07:09
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant