Skip to main content

Architecture overview

Status: Scaffolded 2026-09-24

Vegan Grove is a client-server system with one shared API. Two clients (web and mobile) talk to it over HTTPS and, for messaging, a Socket.IO namespace. The API owns every rule about who can see what. Clients never filter for privacy; they only render what the API already decided to return.

HTTPS Bearer

HTTPS Bearer

Socket.IO /messages

Socket.IO /messages

Web (Next 15 on Amplify)

API (Express 5 on EC2)

Mobile (Expo SDK 57)

MongoDB Atlas

S3 + CloudFront (images)

Bunny Stream (video)

SES (magic links)

Anthropic API (Ivy)

OpenFreeMap tiles

Docs (Amplify)

The four repos​

RepoRoleDeploys to
vegan-grove-apiREST API, Socket.IO, workers, the only thing that reads the databaseEC2 us-east-1, PM2 behind nginx
vegan-grove-webPublic site plus the authenticated /app areaAWS Amplify Hosting
vegan-grove-mobileiOS and Android appEAS Build, App Store, Play
vegan-grove-docsThis siteAWS Amplify Hosting

See the repo dependency map for the contracts between them and ADR-0001 for why four repos and not one.

Boundaries that matter​

  • Principal from the session. Every authenticated request carries an opaque session token. The API resolves it to a user and never trusts an id, email, or role from a request body. See auth and sessions.
  • Visibility on the server. Feed, events, attendees, friends, and messages are filtered by visibility in the query, not after. A client cannot ask for more than it is allowed to see.
  • Bytes bypass the API. Images upload straight to S3 with a presigned PUT, video straight to Bunny with a short-lived TUS credential. The API never proxies media. Metadata is stripped on the device before either.
  • Clients hold one secret. The session token, in SecureStore on mobile and an httpOnly cookie on web. No JWT decoding on the client, no role in local storage.
  • The database is reachable from one place. Atlas allows connections only from the API host's fixed IP. That constraint is the reason the API runs on a box and not on Lambda (ADR-0002).

What is not in v1​

Realtime feed comments, end-to-end encrypted messages, on-device companion inference, and payments. Each has a slot: the /feed Socket.IO namespace is documented but not wired, ADR-0011 reserves the E2E design, and there is no Stripe code to remove later.

Where the Trick Book shape was kept and where it was not​

Kept: one shared API, the router factory pattern, per-feature Socket.IO namespaces with user: and conversation: rooms, the notifications subsystem shape, presigned uploads, the cached public stats endpoint, Amplify with the env allowlist, EAS profiles with a release guard, PokeDocs with an explicit sidebar.

Changed: TypeScript everywhere, Mongoose schemas instead of ad-hoc collections, opaque sessions instead of a seven-day JWT in a custom header, Authorization: Bearer, server-side auth gating on the web, security headers, tests from the first commit, and nothing personal in logs or docs.