resourceui

@ferrumec/resourceui

API-resource-driven UI for data-heavy React frontends.

resourceui turns a description of your data — its fields, its API — into working list, detail, create, and update views. You describe what an entity looks like and where it lives; the library handles loading state, pagination, sorting, caching, and re-render coordination for you.

It’s built for the kind of screen every admin panel, dashboard, or internal tool eventually needs: a paginated table of records, a form to create one, a detail page, an edit page — repeated for every resource in your system. resourceui lets you stop rebuilding that plumbing by hand.

Why it exists

Most CRUD UIs share the same shape but get rewritten from scratch every time: a list needs paging and sorting, a create form needs validation, an edit form needs to load existing data first. resourceui extracts that shape into two primitives — Item and Resource — so the repetitive parts (state, fetching, re-fetch-after-mutation) are handled once, while the actual markup stays fully swappable.

Core concepts

Item — the base primitive

An Item owns one entity and the three operations you can do to it: get, update, delete. It doesn’t know or care whether that entity belongs to a collection. This is what makes a standalone record — a user’s own profile, account settings, “the active organization” — a first-class case instead of a workaround.

interface ItemApi<T> {
  get: () => Promise<T>;
  update: (data: Partial<T>) => Promise<T>;
  delete: () => Promise<void>;
}

Resource — built on top of Item

A Resource owns a collection: listing, paging, sorting, filtering, creating. Every per-record operation on a Resource — update, delete, or opening a single item — is implemented by asking the resource’s API for that one record’s ItemApi and delegating to it. A Resource never reimplements per-record CRUD; it reuses Item’s contract.

interface ResourceApi<T> {
  list: (query: ListQuery<T>) => Promise<Page<T>>;
  create: (data: Partial<T>) => Promise<T>;
  itemApi: (id: string | number) => ItemApi<T>;
}

This dependency direction — Resource depends on Item, never the reverse — is deliberate. It means resource.item(id) gives you back the exact same kind of object a standalone Item(...) call would, sharing field metadata and staying in sync with the collection’s cache.

Metadata: fields describe your entity once

Both primitives take a metadata object with a fields: FieldMetadata<T>[] array — name, label, type (text | email | number | boolean | date | select), whether it’s required, visible, editable, sortable, filterable, plus optional format (custom read-only rendering) and validate functions. This one field list drives the table columns in a list view, the inputs in a create/update form, and the label/value pairs in a detail view.

Views: controller methods that render themselves

Each controller exposes view methods you call as JSX:

Controller Views
ItemController<T> .DetailView(), .UpdateView()
ResourceController<T> .ListView(), .CreateView()

Every call to a given controller’s view shares the same underlying store, so rendering .ListView() in two places at once still reflects one shared truth — no manual sync required.

Layouts: presentation is fully swappable

Views don’t hardcode markup. Each one accepts a layout component — DefaultListLayout, DefaultCreateLayout, DefaultDetailLayout, DefaultUpdateLayout ship as working (if plain) defaults, but you can pass your own component matching the same props contract (ListLayoutProps<T>, CreateLayoutProps<T>, etc.) to fully restyle a view without touching any data-fetching logic.

Two ways to describe your API

Underneath both, a small REST convenience layer (fetchHttpClient, createRestItemApi, createRestResourceApi) provides sensible defaults, and every layer accepts a swappable HttpClient if you need custom auth headers, retries, or a non-fetch transport.

Package layout

@ferrumec/resourceui
├── index          — everything below, as a single entry point
└── router          — optional react-router-dom bindings (peer dep, optional)

@ferrumec/resourceui/theme.css   — default stylesheet

Peer dependencies: react and react-dom (>=18) are required; react-router-dom (>=6) is optional and only needed if you use the /router entry point.

What this library is not

resourceui is not a component kit — the default layouts are intentionally plain (monospace, minimal styling) so they’re obviously meant to be replaced. It’s also not an ORM or a data-fetching cache like React Query; the built-in store is a minimal pub/sub just large enough to keep every view of one controller in sync, not a general-purpose cache.

Next steps