Skip to content

Storage to GraphQL Backend

Converting Local Storage to a GraphQL Backend

Section titled “Converting Local Storage to a GraphQL Backend”

Context: Domain data is persisted via a single abstraction in shaker/infra/localStorage.ts (MMKV). This document explains why moving to a GraphQL (or other) backend is straightforward and outlines possible strategies.


All domain reads and writes go through getItem and setItem and the fixed set of STORAGE_KEYS. No domain module in shaker/data/* knows whether the backend is local (MMKV) or remote (GraphQL). Swapping the implementation behind that interface is a controlled, infra-only change.

The codebase already has TODOs and comments (e.g. in shaker/app/(tabs)/venues/[id].tsx, shaker/data/products/index.ts) such as “Replace with GraphQL mutation when backend is ready.” The data shape (products, venues, recipes, etc.) is already the natural mapping to GraphQL types and operations. The same TypeScript types used for hydration can be used for API responses.


Introduce a “persistence adapter” (e.g. LocalStorageAdapter vs GraphQLAdapter) that implements the same interface used by the app today:

  • getItem(key) – For each key, map to a GraphQL query (or REST endpoint) that returns the same payload type (e.g. ProductsStoragePayload, Venue[]).
  • setItem(key, value) – Map to a GraphQL mutation (or REST) that updates the server; optionally invalidate caches or trigger refetch.

Hydration and all persist*() calls stay the same; only the implementation of the adapter changes (and possibly environment or config to choose which adapter is used).

Replace direct getItem/setItem per key with a small repository per domain (e.g. ProductRepository, VenueRepository) that:

  • Exposes the same logical operations the app already has (get list, add, update, delete).
  • Today: Implementation uses MMKV (read/write the same JSON blobs).
  • Later: Implementation uses GraphQL queries and mutations. The rest of the app depends on the repository interface, not on storage.

Keep MMKV as a local cache. The repository (or adapter) reads from GraphQL when online and writes to MMKV; reads prefer MMKV for speed and offline use. Sync logic (e.g. queue mutations when offline) can be added later.


What to keep consistent for an easy conversion

Section titled “What to keep consistent for an easy conversion”
  • Key–domain mapping: Keep STORAGE_KEYS (or an equivalent list) as the canonical set of “domain blobs.” GraphQL can mirror these as one query per key or one query per entity type.
  • Payload types: Keep the same TypeScript types (e.g. ProductsStoragePayload, Venue[], Recipe[]) so that when the backend returns JSON from GraphQL, the same types and hydration logic apply.
  • No direct storage in features: Ensure no feature or data code imports MMKV or AsyncStorage directly; all persistence goes through localStorage.ts (or the future adapter/repository). That way swapping to GraphQL only touches the infra layer.

flowchart LR
  subgraph today [Current]
    UI[UI / Screens]
    Data[Data modules]
    Hydrate[hydrateLocalStorage]
    Infra[localStorage.ts]
    MMKV[MMKV]
    UI --> Data
    Hydrate --> Infra
    Data --> Infra
    Infra --> MMKV
  end

  subgraph future [With GraphQL]
    UI2[UI / Screens]
    Data2[Data modules]
    Hydrate2[hydrateLocalStorage]
    Adapter[Persistence adapter]
    GQL[GraphQL API]
    UI2 --> Data2
    Hydrate2 --> Adapter
    Data2 --> Adapter
    Adapter --> GQL
  end

Data modules and hydration stay the same; only the implementation behind localStorage or the adapter changes from MMKV to GraphQL.


  • Local storage options (MMKV) – Current persistence implementation.
  • shaker/infra/localStorage.ts – Single storage abstraction and STORAGE_KEYS.