navree
Documentation

Nuxt

@navree/nuxt: a Nuxt module with the backend-for-frontend as Nitro routes, a session gate, and auto-imported composables for sign-in, workspaces, the typed client, list queries, operations, real time and abilities.

On this page

@navree/nuxt puts a Nuxt app on a Navree API with one line in nuxt.config.ts. Nitro serves the backend-for-frontend at /api/** and /broadcasting/auth, a session gate keeps pages behind sign-in and refreshes expired tokens before a page renders, and the composables and <NavreeDataTable> are auto-imported. It works with server-side rendering on and off.

Setting up

  1. Install the packages

    @navree/nuxt, @navree/client and @navree/theme (see installing). For abilities add @casl/ability and @casl/vue; for real time, pusher-js.

  2. Add the module

    web/nuxt.config.ts
  3. Generate the client's schema

    Shell

Options

OptionDefaultMeaning
apiUrlhttp://127.0.0.1:8080The API. NAVREE_API_URL or NUXT_NAVREE_API_URL override it at runtime.
apiPrefix/apiThe prefix the BFF serves and forwards; false mounts nothing (mount the handlers yourself).
broadcastingtruePOST /broadcasting/auth for private channels.
mounts[]More prefixes to forward with the session's token, such as /admin for a server-rendered tool.
sessionGate{ loginPath: "/login" }Pages behind sign-in (loginPath, publicPaths, passThrough), or false for none.
workspaceHeaderX-WorkspaceThe API's workspace.header.
realtimefalse{ key, wsHost, wsPort, forceTLS? }: connect pusher-js to the API's real-time server.
abilitiesfalseOne app-wide CASL ability, loaded after sign-in.

The settings land in runtime config (runtimeConfig.navree on the server, runtimeConfig.public.navree in the app), so NUXT_NAVREE_* variables override them without a rebuild, and settings you put there yourself win over the module's. The BFF also reads NAVREE_COOKIE_SECURE, APP_ORIGINS and NAVREE_TRUSTED_PROXY_HOPS (see the BFF's options), the same names the Next.js package uses.

What the module adds

KindAdded
Nitro routes/api/**, POST /broadcasting/auth, <mount>/** for each of mounts: the BFF's catch-all handler
Nitro middlewareThe session gate, unless sessionGate: false
Nitro auto-importsgetNavreeSession(event), useNavreeBff(event)
App pluginsnavree (sign-in state, workspace, typed client), navree-realtime (client only, with realtime), navree-abilities (with abilities)
Auto-importsuseNavree, useNavreeAuth, useWorkspace, useNavreeClient, useOperation, useListQuery, applyFieldErrors; useChannel and useRealtimeState with realtime; useAbility with abilities
Components<NavreeDataTable>, <NavreePagination>; <Can> with abilities

The session gate

For every page request (a GET or HEAD that accepts text/html), the gate:

  • sends a browser without a session to loginPath?next=<path and query> (302);
  • refreshes an expired access token first, for the browser (Set-Cookie) and for the rest of the request (the rewritten Cookie header that server-side rendering reads);
  • clears an expired session and sends it to sign-in.

/api/, /broadcasting/, /_nuxt/, /__nuxt and /_ipx/ pass untouched, and so do files with an extension, loginPath and the publicPaths (exact paths, or prefixes ending in /*).

Composables

Sign-in

web/app/pages/login.vue
MemberWhat it does
user, status, signedInThe signed-in user; unknown, signed-in, signed-out or two-factor; a computed boolean.
load()GET /api/auth/me: the user, or null when signed out.
signIn(credentials){twoFactor: false, user}, or {twoFactor: true, expiresIn} for a two-factor account. Throws NavreeError (invalid_credentials, too_many_login_attempts).
verifyTwoFactor({code}) or ({recovery_code})Completes a two-factor sign-in.
signOut({all?})Revokes the session (all: true: every session of the user) and goes to loginPath.

During server-side rendering the composables call the BFF through Nitro's event.fetch, which forwards the browser's cookies, so await useNavreeAuth().load() in a page's setup works on the server and in the browser.

The typed client and the workspace

TypeScript

useNavreeClient() is the typed client in cookie mode with the workspace from the navree_workspace cookie; on a 401 it sends the browser to loginPath?next=…. On the server it calls the BFF in-process.

Server routes

web/server/api/me/summary.get.ts

Lists, operations and forms

useListQuery() keeps a list's filters, sort, search and page in the route's query string, and <NavreeDataTable> renders one page of it: see data tables. useOperation(id) follows an operation's event stream:

HTML

applyFieldErrors(error, setFieldError, { fields, onOther }) puts a 400's field errors on vee-validate's fields and hands the rest to onOther.

Real time

With realtime set, one pusher-js connection opens when the app mounts in the browser; private channels are signed through the BFF.

TypeScript

The channel can be a ref or a getter; the listener moves when it changes and stops when the component unmounts. List the web origin in the API's realtime.allowed_origins.

Abilities

With abilities: true, the module installs one CASL ability (@casl/vue) and loads the user's rules from GET /api/me/abilities after sign-in and after every workspace switch; signed out, every rule is dropped.

HTML

Without the module

The pieces are plain Vue and h3, for a Vue app with another server, or to mount routes yourself:

  • @navree/nuxt/vue: createNavreeAuth, createWorkspace, useOperation, useListQuery, NavreeDataTable, useDataTable, applyFieldErrors.
  • @navree/nuxt/realtime: createRealtimeController, installRealtime, useChannel, useRealtimeState.
  • @navree/nuxt/abilities: installAbilities, useAbility, Can.
  • @navree/nuxt/server: createBffHandler(source), createSessionGate(source, options), bffResolver(settings), getNavreeSession(event, bff).

Moving an existing app onto the module

The reference CRM's Nuxt front end, apps/crm-nuxt, has these pieces as its own files. With the module:

apps/crm-nuxt todayWith the module
server/lib/bff.ts, http-errors.ts, instance.tsThe module's BFF (navree.apiUrl, NAVREE_API_URL)
server/api/[...path].ts, server/api/auth/*.post.ts, server/routes/broadcasting/auth.post.tsThe module's routes
server/middleware/session-gate.tsnavree.sessionGate
app/lib/api/client.ts, errors.tsuseNavreeClient<paths>(), NavreeError, unwrap
app/lib/api/form-errors.tsapplyFieldErrors
app/lib/casl/ability.ts, app/plugins/03.casl.tsnavree.abilities: true, useAbility, <Can>
app/lib/realtime/*, app/composables/useRealtime.tsnavree.realtime, useChannel (the app's handlers stay)
app/composables/useUrlState.ts, PaginationBar.vue, SortableHead.vueuseListQuery, <NavreeDataTable>, <NavreePagination>
The tokens and @font-face rules in app/assets/css/main.css@navree/theme/theme.css and fonts.css

The cookie names, routes and error shape are the same as the app's own, so the move can go one piece at a time. The CRM-specific parts (its types, screens, mock API and query hooks) stay in the app. Nuxt's dev port and the API URL keep their current settings.

Testing

@navree/nuxt/server's handlers are h3 handlers, so a test mounts them on an h3 app and calls it with web requests:

TypeScript

The composables mount in @vue/test-utils with a memory router. The package's pnpm smoke builds a small Nuxt app on the module and checks the gate, server rendering and the BFF routes of the running server.