@roarexclamation/mobile-base
v0.1.0
Published
Reusable base library for React Native apps using Supabase with domain-driven repositories and services.
Readme
@roar/mobile-base
Reusable base library for React Native apps using Supabase with domain-driven repositories and services. It helps you:
- Configure a single Supabase client for your app
- Implement typed repositories and services per domain
- Use generic CRUD that covers 90% of cases
- Keep features consistent across apps and update from one place
Install
Add as a dependency in each app (recommended via workspace or package registry):
# if using npm workspaces or local path
npm install @roar/mobile-base --save
# or add via Git URL until published
npm install github:roar/mobile-baseThis package expects @supabase/supabase-js v2 as a peer dependency.
Quick start
- Configure Supabase (once at app startup):
import { configureSupabase } from "@roar/mobile-base";
configureSupabase({
url: process.env.EXPO_PUBLIC_SUPABASE_URL!,
anonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
});- Create a typed repository and service for a domain (Users example):
// users.repository.ts
import { BaseRepository } from "@roar/mobile-base";
type UserRow = {
id: string;
email: string;
full_name: string | null;
created_at: string;
};
interface UserInsert {
email: string;
full_name?: string | null;
}
interface UserUpdate {
full_name?: string | null;
}
export class UsersRepository extends BaseRepository<
UserRow,
UserInsert,
UserUpdate
> {
constructor() {
super({ table: "users", primaryKey: "id" });
}
}
// users.service.ts
import { BaseService } from "@roar/mobile-base";
import { UsersRepository } from "./users.repository";
export class UsersService extends BaseService<
Parameters<UsersRepository["create"]>[0] extends infer I ? any : any,
Parameters<UsersRepository["create"]>[0],
Parameters<UsersRepository["update"]>[1]
> {
constructor(repo = new UsersRepository()) {
super(repo);
}
}- Use in your screens/hooks (with React Query example):
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
import { UsersRepository } from "../data/users.repository";
const usersRepo = new UsersRepository();
export function useUsersList() {
return useQuery({
queryKey: ["users"],
queryFn: () => usersRepo.list({ withCount: true }),
});
}
export function useCreateUser() {
const qc = useQueryClient();
return useMutation({
mutationFn: (payload: { email: string; full_name?: string | null }) =>
usersRepo.create(payload),
onSuccess: () => qc.invalidateQueries({ queryKey: ["users"] }),
});
}API Overview
configureSupabase({ url, anonKey, options? })— set up a shared clientsetSupabaseClient(client)— provide your own (typed) client instancegetSupabaseClient()— retrieve the shared client
BaseRepository<Row, Insert = Row, Update = Partial>
Constructor: new BaseRepository({ table, primaryKey = "id", softDeleteColumn? }, client?)
Methods:
getById(id, { select? })findOne({ where, select? })list({ where?, orderBy?, limit?, offset?, select?, withCount?, page?, pageSize? })create(payload, { select? })update(id, patch, { select? })delete(id)— soft deletes whensoftDeleteColumnis provided
BaseService<Row, Insert, Update>
Thin wrapper around a repository; extend to add validation and orchestration.
Expo adapter and Auth
Use an Expo-friendly setup that chooses SecureStore on native and AsyncStorage on web:
import {
configureSupabaseExpo,
AuthProvider,
useAuth,
} from "@roar/mobile-base";
configureSupabaseExpo({
url: process.env.EXPO_PUBLIC_SUPABASE_URL!,
anonKey: process.env.EXPO_PUBLIC_SUPABASE_ANON_KEY!,
});
// In your root component
function AppRoot() {
return <AuthProvider>{/* your app */}</AuthProvider>;
}
// In screens
const { user, session, signIn, signOut, loading } = useAuth();Styling with NativeWind (Tailwind for RN)
This package ships a Tailwind preset. In each app:
- Install deps in the app:
npm i nativewind tailwindcss- Configure Babel (app
babel.config.js):
module.exports = function (api) {
api.cache(true);
return {
presets: ["babel-preset-expo"],
plugins: ["nativewind/babel"],
};
};- Create
tailwind.config.jsin the app:
// tailwind.config.js
/** @type {import('tailwindcss').Config} */
module.exports = {
presets: [require("@roar/mobile-base/tailwind-preset")],
content: [
"./App.{js,jsx,ts,tsx}",
"./src/**/*.{js,jsx,ts,tsx}",
"./components/**/*.{js,jsx,ts,tsx}",
],
theme: { extend: {} },
};- Use prebuilt components or className in RN components:
import { Button, Input, Screen, Card, Text } from "@roar/mobile-base";
<Screen className="bg-neutral-50">
<Card>
<Text className="text-xl font-semibold mb-2">Sign in</Text>
<Input label="Email" placeholder="email" className="mb-3" />
<Input
label="Password"
placeholder="password"
secureTextEntry
className="mb-4"
/>
<Button title="Continue" />
</Card>
</Screen>;Filters & pagination
Use where with operators: eq, neq, gt, gte, lt, lte, like, ilike, is, in, contains, containedBy, overlaps.
repo.list({
where: [
{ column: "email", op: "ilike", value: "%@company.com" },
{ column: "status", op: "eq", value: "active" },
],
orderBy: { column: "created_at", ascending: false },
page: 1,
pageSize: 20,
withCount: true,
});Type-safety with Supabase
For full type-safety, generate your Supabase Database types and create a typed client in your app, then pass it to the library:
import { createClient } from "@supabase/supabase-js";
import type { Database } from "../types/supabase"; // generated
import { setSupabaseClient } from "@roar/mobile-base";
const client = createClient<Database>(SUPABASE_URL, SUPABASE_ANON);
setSupabaseClient(client);You can then extend BaseRepository with strongly-typed row/insert/update interfaces derived from Database["public"]["Tables"]["table_name"].
Keeping apps up to date
Recommended strategies:
- Package: publish
@roar/mobile-baseto a private npm registry; apps just bump the version. - Monorepo: use workspaces (pnpm/npm/yarn) so apps depend on
baselocally. - Git submodule: link this repo as a submodule; update by pulling submodule changes.
This base avoids React or Expo dependencies so it works across RN apps. UI helpers or hooks can live in a separate adapter package if needed.
ORM-like workflow with migrations
Supabase is Postgres under the hood. For an ORM-like developer experience (models + migrations), use the Supabase CLI to manage SQL migrations and generate types. This keeps your schema as code and works great with the repository/service pattern in this base.
See db/README.md for commands. Highlights:
npm run db:migration:new -- <name>: new SQL migration filenpm run db:reset: apply migrations to local dbnpm run db:push: push migrations to remote projectnpm run db:types:localordb:types:remote: generate TypeScript types
You can also scaffold repository/service files quickly:
node ./scripts/scaffold-model.mjs fraud_reports FraudReport ./scaffoldThen move the generated files into your app and replace the row/insert/update types with your generated Database types for full type-safety.
Migration tips for existing apps (fraud-tracker-app, marle)
- Replace
lib/supabase.tswith a call toconfigureSupabaseExpoin your app startup. - Swap local
AuthContextusage to the exportedAuthProvideranduseAuth. - Move direct Supabase calls out of screens into
repositories/andservices/extending the provided base classes. - Adopt NativeWind by adding the Babel plugin and Tailwind config using the preset, then gradually replace StyleSheet styles with className utilities or shared UI components.
License
MIT
