---
title: Anatomy of an application
description: The structure every application module follows - package layout, app.config.ts, the mountable root, features, routes, and exports.
---

Every application module in `packages/applications/*` follows the same anatomy. Understand it once and you can read any vertical. This page uses `@africaos/retail` as the concrete example.

### The package layout

```txt
packages/applications/retail/
├── app.config.ts          self-description for the registry
├── package.json           @africaos/retail, workspace:* deps
├── tsconfig.json          extends the shared typescript config
├── project.json           Nx project metadata
├── vitest.config.ts       vite-plus test config
└── src/
    ├── index.ts           public exports
    ├── application.tsx    the mountable root component
    ├── queryClient.ts     TanStack Query client
    ├── components/        application-level chrome (shell, sidebar)
    ├── features/          one folder per domain slice
    ├── pages/             larger non-feature pages
    └── routes/            the internal route tree
```

### app.config.ts - the self-description

The config is the single source of truth for the application's metadata:

```ts
import { defineAppConfig } from "@africaos/config";

export default defineAppConfig({
  id: "retail",
  name: "Retail OS",
  icon: "shopping-cart",
  color: "#059669",
  description: "Retail OS for products, inventory, sales, and point of sale",
  route: "/",
  capabilities: { offline: false, desktop: true, mobile: false },
  features: [
    { slug: "products", label: "Products", icon: "package", path: "/products" },
    { slug: "inventory", label: "Inventory", icon: "boxes", path: "/inventory" },
    { slug: "customers", label: "Customers", icon: "users" }, // declared, not yet implemented
    // ...
  ],
});
```

Two audiences read this file:

- The **registry** (`pnpm db:register-apps`) upserts the metadata so the platform never hardcodes per-app knowledge.
- The **application shell** renders the implemented features (those with a `path`) as the application's internal navigation.

A feature without a `path` is declared surface - planned work that is not yet a route.

### The mountable root

`src/application.tsx` exports the host-agnostic root component:

```tsx
export function RetailApplication({ organizationSlug }: RetailApplicationProps) {
  const basePath = organizationSlug ? `/${organizationSlug}/retail` : "";
  const { pathname } = useLocation();
  const relativePath = pathname.startsWith(basePath)
    ? pathname.slice(basePath.length) || "/"
    : "/";

  return (
    <RetailShell basePath={basePath} embedded={Boolean(organizationSlug)} dashboardPath={...}>
      {resolvePage(relativePath, basePath, organizationSlug)}
    </RetailShell>
  );
}
```

It computes the path **relative to the mount prefix** and renders the matching page. This lets the same component serve two hosts:

- The web app passes `organizationSlug`, so deep links like `/:organization/retail/products/:productId` resolve without the host knowing retail's internal routes.
- A standalone host omits the slug, and the request runtime falls back to the active-org cookie.

The component wraps pages in its application shell (`RetailShell`), which shows the app's internal navigation and a back-to-dashboard link.

### The feature folder

A feature is one folder under `src/features/`. The products feature shows the full stack:

```txt
features/products/
├── service.ts                 Effect business program
├── schemas/product.ts         Zod schemas + derived types
├── errors.ts                  typed errors
├── services/products.functions.ts  server functions
├── hooks/useProducts.ts       TanStack Query hooks
├── components/ProductsPage.tsx
├── components/ProductDetailPage.tsx
└── service.test.ts
```

The dependency order is strict: components -> hooks -> server functions -> service -> database. The server function never contains business logic; it validates input, provides the service layer, runs the program through the request runtime, and folds failures into serializable codes.

### The service layer

The domain logic lives in an Effect service (`features/products/service.ts`). It depends on `Database`, `Logger`, and `CurrentPermissions`, and every operation:

1. Guards with `requirePermission("retail.products.<action>")`.
2. Scopes every query structurally with `orgWhere(organizationId)`.
3. Parses rows into a typed domain `Product`.
4. Fails with typed errors (`ProductNotFound`, `InvalidProduct`, `Forbidden`).

The service never constructs its dependencies - it receives them through the layer, so the request runtime decides the implementations.

### Server functions

`features/products/services/products.functions.ts` defines the TanStack Start server functions. Every handler follows the same pattern:

```ts
export const createProductFn = createServerFn({ method: "POST" })
  .validator((data: unknown) => schema.parse(data))
  .handler(async ({ data }) => {
    const headers = getRequestHeaders();
    const program = Effect.gen(function* () {
      const organizationId = yield* requireActiveOrganization;
      const productService = yield* ProductService;
      return toPublicProduct(yield* productService.create(organizationId, input));
    });
    return runRequestFolded(headers, Effect.matchEffect(withProductService(program), {
      onFailure: (error) => Effect.succeed({ error: toProductError(error) } as const),
      onSuccess: (product) => Effect.succeed({ product } as const),
    }), { scope: retailScope(organizationSlug) }, () => ({ error: "unauthenticated" } as const));
  });
```

The pieces:

- **`retailScope(organizationSlug)`** passes both the org slug and the application slug (`retail`) to the request scope, so the runtime re-verifies server-side on every call that the application is enabled and the user holds `platform.applications.access`.
- **`withProductService(program)`** provides `ProductService.Live`, which needs `Database` and `Logger` - both provided by the request runtime.
- **`Effect.matchEffect`** folds typed failures into stable client codes (`not_found`, `invalid`, `forbidden`, `no_organization`, `unknown`).
- **`runRequestFolded`** maps a stale/missing session to the same neutral shape instead of a 500.

### Hooks

`features/products/hooks/useProducts.ts` wraps the server functions in TanStack Query. The organization slug is part of the query key, so switching organizations refetches the correct catalog. Mutations invalidate the list on success.

### The route tree

`src/routes/tree.ts` exports the application's internal routes so hosts can compose them (see [Data fetching](/frontend/data-fetching)). The mounted application resolves the same pages from the URL.

### Public exports

`src/index.ts` is the package's public surface: the root component, the config, the query client, the route tree, the hooks, the service, the server functions, the schemas, and the errors. Hosts import only what they need.

### Consistency rules

- **The feature structure is identical across features.** Copy the products feature to start a new one.
- **Server functions are thin.** All logic lives in the Effect service.
- **Every tenant query is scoped.** `orgWhere`/`orgScope` on every query touching tenant data.
- **Every domain action is guarded.** `requirePermission` before acting.
- **The application is host-agnostic.** No web-app imports; the `organizationSlug` prop drives behavior.

### Next steps

- [Retail reference](/applications/retail-reference) - the products feature end to end.
- [Building an application](/applications/building-an-application) - the recipe for a new vertical.