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.
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.
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>;
}
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.
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.
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.
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.
fieldsFromJsonSchema, ResourceFromSchema, ItemFromSchema) — derive the field list from a schema you already have (e.g. from an OpenAPI spec), then plug in an API you write or generate elsewhere.itemApiFromSchema, resourceApiFromSchema, ItemFromApiSchema, ResourceFromApiSchema) — describe both fields and endpoints (get, update, delete, list, create, plus named actions) as a plain object, and get back a fully wired controller with generated request functions. This is the fastest path from “I have REST endpoints” to a working UI.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.
@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.
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.