omni-rest-client
omni-rest-client is a standalone frontend code generator β no Prisma, no schema.prisma, no database connection required. It reads a omni-rest.config.json file produced by your backend and generates a complete React data layer.
This is the recommended way to generate frontend code starting from v0.4.0. The old npx omni-rest generate:frontend still works but is considered legacy.
How it works
The workflow is split cleanly between two CLIs:
Backend project Frontend project
ββββββββββββββββββββββββββββ ββββββββββββββββββββββββββββββββββ
npx omni-rest generate:config β npx omni-rest-client generate:frontend
β β
omni-rest.config.json hooks/, components/, pages/
(model metadata + Zod schemas) schemas.generated.tsThe backend CLI knows about Prisma. The client CLI knows about React. Neither needs to know about the other's internals.
Step-by-step
Install omni-rest in your backend
npm install omni-rest
npx prisma generateGenerate the config file
Run this from your backend project root (where prisma/schema.prisma lives):
npx omni-rest generate:configThis writes omni-rest.config.json β a plain JSON file containing:
- All model names, field names, types, and metadata
- Full Zod schema source (the same as
schemas.generated.ts)
{
"version": "1",
"generatedAt": "2026-03-29T10:00:00.000Z",
"models": [
{
"name": "User",
"routeName": "user",
"idField": "id",
"fields": [
{ "name": "id", "type": "Int", "isId": true, "isRequired": true, ... },
{ "name": "name", "type": "String", "isId": false, "isRequired": true, ... },
{ "name": "email", "type": "String", "isId": false, "isRequired": true, ... }
]
}
],
"zodSchemas": "import { z } from 'zod';\n\nexport const UserCreateSchema = ..."
}Copy the config to your frontend (or reference it by path)
You can commit omni-rest.config.json to your repo, copy it to your frontend project, or reference it by relative path with --config.
Install omni-rest-client in your frontend
npm install -D omni-rest-clientAlso install the runtime dependencies your generated code needs:
npm install @tanstack/react-query @tanstack/react-table react-hook-form zod @hookform/resolversRun the client generator
npx omni-rest-client generate:frontendIf your config is in a different location:
npx omni-rest-client generate:frontend --config ../backend/omni-rest.config.jsonWith autopilot (no prompts):
npx omni-rest-client generate:frontend --autopilotWrap your app with TanStack Query
The generator writes a components/providers.tsx for Next.js App Router. Add it to your layout:
// app/layout.tsx
import { Providers } from '@/src/components/providers'
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html>
<body>
<Providers>{children}</Providers>
</body>
</html>
)
}Use the generated components
// app/users/page.tsx
import { UserTable } from '@/src/components/user/UserTable'
export default function UsersPage() {
return (
<div className="container mx-auto py-10">
<h1 className="text-3xl font-bold mb-6">Users</h1>
<UserTable />
</div>
)
}What gets generated
For a User model:
- schemas.generated.ts
- useUser.ts
- UserColumns.tsx
- UserTable.tsx
- UserForm.tsx
- data-table.tsx
- form-generator.tsx
- providers.tsx
- menu-data.ts
- page.tsx
| File | Description |
|---|---|
schemas.generated.ts | Zod schemas extracted from config (no Prisma needed) |
hooks/useUser.ts | TanStack Query hooks β list, get, create, update, delete, bulk-delete |
UserColumns.tsx | TanStack Table column definitions |
UserTable.tsx | DataTable wired to hooks, bulk delete, export |
UserForm.tsx | FormGenerator with Zod validation and relational dropdowns |
data-table.tsx | Shared DataTable base component (copied once) |
form-generator.tsx | Shared FormGenerator base component (copied once) |
providers.tsx | QueryClientProvider wrapper (Next.js App Router) |
lib/menu-data.ts | Navigation structure for all generated models |
app/.../page.tsx | Next.js App Router page per model |
Generated hook example
Unlike the legacy generator, hooks use generic types β no @prisma/client import:
// src/hooks/useUser.ts
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query";
type User = Record<string, any>;
type UserCreateInput = Record<string, any>;
type UserUpdateInput = Record<string, any>;
const BASE_URL = "/api";
export function useUsers(params?: Record<string, any>) {
return useQuery({
queryKey: ["user", params],
queryFn: () => {
const url = new URL(`${BASE_URL}/user`, window.location.origin);
if (params) {
Object.entries(params).forEach(([k, v]) => {
if (v !== undefined && v !== null) url.searchParams.set(k, String(v));
});
}
return fetch(url.toString()).then((r) => r.json());
},
staleTime: 30000,
gcTime: 300000,
});
}
export function useCreateUser() { /* POST with optimistic update */ }
export function useUpdateUser() { /* PATCH with optimistic update */ }
export function useDeleteUser() { /* DELETE with optimistic removal */ }
export function useBulkDeleteUsers() { /* bulk DELETE */ }Flags reference
| Flag | Default | Description |
|---|---|---|
--config <path> | ./omni-rest.config.json | Path to config file |
--frontend-dir <path> | cwd | Frontend project root |
--out <dir> | src/ or root | Output directory relative to frontend dir |
--models <names> | all | Comma-separated model names to generate |
--base-url <url> | /api | API base URL (overrides env file detection) |
--framework <name> | auto-detect | nextjs | vite-react | react |
--autopilot | false | Skip all prompts, use defaults |
--no-bulk | β | Disable bulk delete |
--no-optimistic | β | Disable optimistic updates |
--no-pages | β | Disable Next.js page generation |
--no-menu | β | Disable menu-data.ts generation |
--no-schemas | β | Skip writing schemas.generated.ts |
--route-group <name> | autogenerated-omni | Next.js route group name |
--stale-time <ms> | 30000 | TanStack Query staleTime |
--gc-time <ms> | 300000 | TanStack Query gcTime |
--steps <mode> | auto | Multi-step forms: auto | always | never |
--help | β | Print usage |
Re-running after schema changes
# Backend: regenerate config after schema changes
npx prisma generate
npx omni-rest generate:config
# Frontend: regenerate components from updated config
npx omni-rest-client generate:frontend --autopilotHook and column files are overwritten on each run. Base components (data-table.tsx, form-generator.tsx) are skipped if they already exist, preserving any customizations.
Monorepo setup
In a monorepo where backend and frontend are siblings:
# From the frontend package
npx omni-rest-client generate:frontend \
--config ../backend/omni-rest.config.json \
--autopilotOr add it as a script in your frontend package.json:
{
"scripts": {
"generate": "omni-rest-client generate:frontend --config ../backend/omni-rest.config.json --autopilot"
}
}Troubleshooting
"Config file not found"
Run npx omni-rest generate:config in your backend project first, then copy or reference the output file.
"Invalid config file: missing models array"
The config file is malformed or from an older version. Regenerate it with npx omni-rest generate:config.
Missing dependency warnings
Install the listed packages: npm install @tanstack/react-query @tanstack/react-table react-hook-form zod @hookform/resolvers
Schemas not found at runtime
Make sure schemas.generated.ts is in your output directory. It's written automatically unless you pass --no-schemas.