JORVEL

Tutorial — build your first JORVEL app

Start to finish in ~20 minutes: a host shell that loads a federated dashboard remote, a file-based route, server data, a session gate, and a deploy. Assumes Node 20+ and a package manager (pnpm recommended).

Prefer to poke around first?

Open the starters in a browser sandbox, or skim Concepts for the mental model. This page is the guided build.

1 · Scaffold a workspace

bash
npm create jorvel@latest my-shop
cd my-shop
pnpm install

Interactive prompts pick a template, package manager, and Tailwind. You get apps/, libs/, jorvel.config.json, CI workflows, ESLint, Vitest, and Git hooks — ready to run.

2 · Generate a host + a remote

bash
jorvel generate host shell --port 3000
jorvel generate remote dashboard --port 3001
jorvel federation      # wires the host's remotes map to the dashboard

The host owns top-level URLs; the remote owns its sub-paths. jorvel federation reads each app's jorvel.app.json and writes the jorvel.federation.json the runtime consumes.

3 · Run the dev server

bash
jorvel dev --proxy-remotes --hmr-remotes

Open localhost:3000. --proxy-remotes serves the remote on the host origin (so CSP/cookies behave like prod); --hmr-remotes reloads the host when the remote recompiles.

4 · Add a route

Drop a file in the remote's src/pages/ — file-based, Next-style:

apps/dashboard/src/pages/orders/[id].tsx
tsx
export default function Order({ params }: { params: { id: string } }) {
  return <main><h2>Order {params.id}</h2></main>;
}
bash
jorvel routes --watch   # regenerates src/jorvel.routes.ts

The host now matches /dashboard/orders/42.

5 · Load data

Client-side with useQuery (cache + stale-while-revalidate), or server-side with defineLoader for hydration-ready reads.

tsx
import { useQuery } from '@jorvel/runtime';

function Orders() {
  const { data, isLoading } = useQuery({
    queryKey: ['orders'],
    queryFn: () => fetch('/api/orders').then((r) => r.json()),
  });
  if (isLoading) return <p>Loading</p>;
  return <ul>{data.map((o) => <li key={o.id}>{o.total}</li>)}</ul>;
}

Need a database? jorvel add db scaffolds Drizzle (guide).

6 · Gate a route

Add a middleware that redirects unauthenticated users:

apps/shell/src/middleware.ts
ts
import { defineMiddleware, redirect, next } from '@jorvel/runtime';
import { getSession } from '@jorvel/security';

export default defineMiddleware(async (ctx) => {
  if (!ctx.pathname.startsWith('/dashboard')) return next();
  const user = await getSession(ctx.request ?? '', { secret: process.env.SESSION_SECRET! });
  return user ? next() : redirect('/login?from=' + encodeURIComponent(ctx.pathname));
});

7 · Mutate with a form

tsx
import { Form, defineAction } from '@jorvel/runtime';

const createOrder = defineAction(async (fd: FormData) =>
  fetch('/api/orders', { method: 'POST', body: fd }).then((r) => r.json()),
);

function NewOrder({ csrfToken }: { csrfToken: string }) {
  return (
    <Form action={createOrder} csrf={{ token: csrfToken }}>
      {({ pending }) => (<><input name="sku" /><button disabled={pending}>Create</button></>)}
    </Form>
  );
}

8 · Build & deploy

bash
jorvel build                       # all apps
jorvel federation diff --base main # confirm no breaking contract changes
jorvel deploy --target vercel      # or cloudflare | node | docker | bun | deno | netlify | github-pages

You shipped a micro-frontend

Host + independently-deployable remote, routing, data, auth, a form, and a deploy target. Next: Architecture (how it all fits), or the Cookbook for auth/Stripe/AI/kill-switch recipes.