Internal implementation guide

Aori API Integration Playbook

Panduan migrasi aplikasi menuju `apps/api` sebagai pusat data, auth, validasi, dan komposisi lintas database. Dokumen ini dibuat untuk web, server components, client components, dan mobile Expo.

RuntimeExpress on Vercel
Authbetter-auth
Data4 Prisma clients
MigrationGradual by route
Aori API Migration

1. Prinsip Arsitektur

Semua aplikasi selain `apps/api` harus berhenti membaca database langsung secara bertahap. Aplikasi UI mengambil data melalui HTTP API, sementara `apps/api` menjadi boundary untuk validasi, auth, rate limit, CORS, dan komposisi data lintas database fisik.

DomainClientKepemilikan data
Identitydb.runnerusers, sessions, organizations, workspaces, memberships, permissions
Catalogdb.catalogproducts, categories, variants, media, storefront material
Commercedb.commercecustomers, carts, orders, invoices, payments, shipments
Inventorydb.inventorywarehouses, stock, batches, movements, transfers

Relasi lintas database disimpan sebagai scalar id seperti `organizationId`, `productId`, atau `memberId`. Jangan membuat aplikasi UI menggabungkan data lintas domain sendiri; buat endpoint komposisi di `apps/api`.

Aori API Migration

2. Environment Aplikasi

Gunakan nama env yang eksplisit per target runtime. Prefix publik hanya dipakai untuk URL aman yang boleh terlihat di browser atau mobile.

# apps/api
DATABASE_IDENTITY_URL=
DATABASE_CATALOG_URL=
DATABASE_COMMERCE_URL=
DATABASE_INVENTORY_URL=
BETTER_AUTH_SECRET=
BETTER_AUTH_URL=https://api.example.com
CORS_ALLOWED_ORIGINS=https://app.example.com,https://landing.example.com
AORI_API_PROXY_SECRET=

# Next.js apps
NEXT_PUBLIC_AORI_API_URL=https://api.example.com
AORI_API_PROXY_SECRET=

# Expo / React Native
EXPO_PUBLIC_AORI_API_URL=https://api.example.com

`AORI_API_PROXY_SECRET` hanya boleh digunakan dari server runtime seperti Next.js route handler/server action. Jangan pernah kirim secret ini ke browser atau React Native.

Aori API Migration

3. Standar Fetching

Buat satu wrapper `apiFetch` per aplikasi. Wrapper harus menambahkan `Accept`, `Content-Type`, `x-request-id`, membaca JSON dengan aman, dan mengubah error API menjadi error yang konsisten di UI.

type ApiFetchOptions = Omit<RequestInit, "body"> & {
  body?: unknown;
  token?: string;
};

const API_URL = process.env.NEXT_PUBLIC_AORI_API_URL!;

export async function apiFetch<T>(path: string, options: ApiFetchOptions = {}) {
  const { body, token, headers, ...init } = options;
  const response = await fetch(new URL(path, API_URL), {
    ...init,
    cache: "no-store",
    credentials: "include",
    headers: {
      Accept: "application/json",
      "Content-Type": "application/json",
      "x-request-id": crypto.randomUUID(),
      ...(token ? { Authorization: `Bearer ${token}` } : {}),
      ...headers
    },
    body: body === undefined ? undefined : JSON.stringify(body)
  });

  const text = await response.text();
  const data = text ? JSON.parse(text) : null;

  if (!response.ok) {
    throw new Error(data?.error?.message ?? "Request failed");
  }

  return data as T;
}

Untuk server-to-server dari Next.js, teruskan user yang sudah divalidasi oleh better-auth dengan header proxy. Pola ini membantu migrasi karena `apps/app` bisa tetap menjadi session holder sementara `apps/api` mulai mengambil alih domain data.

await apiFetch("/api/organization", {
  method: "POST",
  headers: {
    "x-aori-user-id": session.user.id,
    "x-aori-proxy-secret": process.env.AORI_API_PROXY_SECRET!
  },
  body: { name, email, category: "RETAIL" }
});
Aori API Migration

4. better-auth Flow

Target akhir yang disarankan adalah better-auth hidup di `apps/api`, lalu aplikasi lain memakai client better-auth ke origin API. Selama migrasi, `apps/app` boleh tetap memegang route `/api/auth/[...all]` dan meneruskan operasi domain ke `apps/api`.

Sign up

await authClient.signUp.email({
  email,
  password,
  name,
  username
});

Sign in

await authClient.signIn.email({
  email,
  password
});

Sign out

await authClient.signOut();

Operasi user seperti update profile, delete account, change password, email OTP, two-factor, passkey, dan multi-session sebaiknya lewat endpoint better-auth lebih dulu. Setelah session valid, endpoint domain di `apps/api` hanya perlu memanggil `requireCurrentUser`.

// Contoh delete user melalui endpoint domain API, setelah session divalidasi di server caller.
await apiFetch(`/api/auth/users/${userId}`, {
  method: "DELETE",
  headers: {
    "x-aori-user-id": session.user.id,
    "x-aori-proxy-secret": process.env.AORI_API_PROXY_SECRET!
  }
});
Aori API Migration

5. Kontrak Route

Route publik boleh dibaca langsung dari client. Route mutasi harus memakai bearer/session atau proxy header dari server terpercaya.

GroupRoute
SystemGET /api, GET /api/v1/health, GET /api/v1/meta
Auth/api/auth/* through better-auth handler, bearer and session aware
RegionGET /api/62/provinces, /cities, /districts, /villages
Organization/api/organization/:organizationId and nested product/category/faq routes
Future commerce/api/client/store/:storeId/checkout and order status routes

Format error standar adalah `{ "error": { "code": "...", "message": "...", "requestId": "..." } }`. UI tidak perlu membaca stack trace atau string error mentah.

Aori API Migration

6. React Native dan Expo

Mobile tidak punya cookie browser yang sama seperti web. Gunakan bearer token atau storage session yang didukung better-auth client. Jangan mengirim `AORI_API_PROXY_SECRET` dari aplikasi mobile.

const API_URL = process.env.EXPO_PUBLIC_AORI_API_URL!;

export async function mobileApiFetch<T>(path: string, token?: string) {
  const response = await fetch(`${API_URL}${path}`, {
    headers: {
      Accept: "application/json",
      ...(token ? { Authorization: `Bearer ${token}` } : {})
    }
  });

  const data = await response.json();
  if (!response.ok) throw new Error(data?.error?.message ?? "Request failed");
  return data as T;
}

Untuk Expo, simpan token/session di secure storage, refresh saat app kembali aktif, dan selalu handle `401` dengan navigasi ke sign-in. Upload media dari mobile sebaiknya memakai signed upload endpoint di API, bukan credential Cloudinary langsung.

Aori API Migration

7. Security Checklist

Aori API Migration

8. Continuous Integration

Pipeline minimum untuk `apps/api` dan integrasi client harus memastikan schema, tipe, lint, dan build selalu sinkron.

01bun install --frozen-lockfile
02bun run db:generate
03bun run type-check
04bun run lint
05bun run build

Tambahkan smoke test setelah deploy: `GET /healthz`, `GET /api`, preflight CORS dari origin app, lalu satu route publik dan satu route protected dengan token test.