Nuxt SDK
Package: @loomup/nuxt (SDK repository path: packages/nuxt).
Nuxt 3/4 module on top of @loomup/client: cookie-backed sessions, Nitro server helpers, auth API routes, and Vue composables.
Session model
Loomup Nuxt uses app-owned HttpOnly cookies on your Nuxt origin:
| Cookie | Purpose |
|---|---|
loomup-access | JWT access token (same name as @loomup/next / @loomup/astro) |
loomup-refresh | Refresh token |
Requests to Loomup use the Authorization: Bearer header. Middleware and server clients read cookies, then attach the access token.
This is not the same as Loomup server auth.cookie_mode (which sets loomup_access / loomup_refresh with underscores on the Loomup origin). Prefer the Nuxt cookie approach for cross-origin setups (e.g. Nuxt on :3001, Loomup on :3000).
Install
cd packages/client && npm install && npm run build
cd ../nuxt && npm install && npm run build
# in your Nuxt app
npm install @loomup/client @loomup/nuxt
# or file: paths during monorepo development
Peer: Nuxt ≥ 3, Vue ≥ 3.
For SPA-only Vue (no Nuxt), use @loomup/vue composables with @loomup/client directly. This package adds Nuxt module wiring, cookie sessions, and auth routes.
Module setup
// nuxt.config.ts
export default defineNuxtConfig({
modules: ["@loomup/nuxt"],
loomup: {
url: process.env.LOOMUP_URL ?? "http://127.0.0.1:3000",
// authRoutes: true, // default — /api/auth/*
// sessionMiddleware: true, // default — proactive access refresh
// authBasePath: "/api/auth",
// exposeAccessToken: true, // session/login JSON includes access_token
// skewSeconds: 60,
},
runtimeConfig: {
loomupUrl: process.env.LOOMUP_URL ?? "http://127.0.0.1:3000",
public: {
loomupUrl:
process.env.NUXT_PUBLIC_LOOMUP_URL ?? "http://127.0.0.1:3000",
},
},
});
Module options
| Option | Default | Role |
|---|---|---|
url | env / empty | Loomup base URL |
authRoutes | true | Register login/register/logout/refresh/session handlers |
authBasePath | /api/auth | Prefix for auth routes |
sessionMiddleware | true | Nitro middleware that refreshes near-expired access tokens |
skewSeconds | 60 | Refresh when access JWT expires within this many seconds |
exposeAccessToken | true | Include access_token in auth JSON for client hydrate |
cookies | defaults | Cookie name / maxAge / secure overrides |
Auth routes
When authRoutes is enabled:
| Method | Path | Behavior |
|---|---|---|
POST | /api/auth/login | signIn → Set-Cookie + optional access_token |
POST | /api/auth/register | signUp → same |
POST | /api/auth/logout | signOut + clear cookies |
POST | /api/auth/refresh | refresh + rotate cookies |
GET | /api/auth/session | me + hydrate payload (user, optional access_token) |
Prefer calling these from the browser so refresh tokens never enter localStorage.
Server
// server/api/todos.get.ts
import { createServerClient, resolveLoomupUrl } from "@loomup/nuxt/server";
import { getCookie, setCookie } from "h3";
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig(event);
const client = createServerClient({
url: resolveLoomupUrl({
loomupUrl: config.loomupUrl as string,
public: config.public as { loomupUrl?: string },
}),
event,
cookieAdapter: { getCookie, setCookie },
});
// REST only — do not call subscribe() during SSR
const { data } = await client.from("todos").select({ limit: 20, sort: "-id" });
return data;
});
Object storage (server)
// server/api/upload.post.ts
import {
createServerClient,
resolveLoomupUrl,
uploadFromFormData,
storageDownloadResponse,
} from "@loomup/nuxt";
// or from "@loomup/nuxt/server" for createServerClient + helpers
export default defineEventHandler(async (event) => {
const client = createServerClient({ /* url + event + cookieAdapter */ });
const form = await readFormData(event);
const meta = await uploadFromFormData(client, "avatars", form, {
pathPrefix: "uploads/",
upsert: true,
});
return { data: meta };
});
Direct API: await client.storage.from("avatars").upload(...). See storage.md.
You can also pass a framework-agnostic cookie jar:
createServerClient({
url,
cookies: {
getAll: () => [...],
setAll: (records) => { /* set cookies */ },
},
});
Token rotation during a request (401 → refresh) invokes onTokens, which writes cookies via the adapter. Proactive refresh runs in session middleware.
Realtime on the server
Do not open WebSocket subscriptions during a request-scoped SSR render. Use REST on the server; use client composables for realtime.
Client composables
Auto-imported when the module is enabled:
| Composable | Role |
|---|---|
useLoomup() | Shared browser LoomupClient |
useLoomupState() | Client + user + setSession / refreshSession |
useAuth() | signIn / signUp / signOut via Nuxt auth routes; hydrates access token |
useLiveQuery(table, opts?) | Initial select + subscribe (refetch | merge) |
<script setup lang="ts">
const { user, signIn, signOut, loading, error } = useAuth();
const { data: todos, ready } = useLiveQuery("todos", {
limit: 50,
strategy: "merge",
});
async function add(title: string) {
const client = useLoomup();
await client.from("todos").insert({ title, completed: 0 });
}
</script>
The client plugin hydrates from GET /api/auth/session on load so realtime can use an in-memory access token while the refresh token stays HttpOnly.
Package exports
| Import | Purpose |
|---|---|
@loomup/nuxt | Nuxt module (modules: ['@loomup/nuxt']) |
@loomup/nuxt/server | createServerClient, resolveLoomupUrl, cookie helpers |
Security
- Keep refresh tokens HttpOnly (default cookie helpers do this).
- Use
Securein production (NODE_ENV=productionsets Secure by default). SameSite=Laxis the default.- Prefer auth through your Nuxt origin rather than storing refresh tokens in
localStorage.
Develop / test this package
cd packages/nuxt
npm install
npm test # tsc + node:test
Example
See examples/nuxt-todo.
See also
- TypeScript SDK — core client API
- Next.js SDK — same cookie session model on Next
- Auth — server token endpoints
- Realtime — WebSocket protocol