---
title: "Quickstart"
description: "From an empty Nuxt app to a deployed, credit-metered backend in about ten minutes — install, run, sign in, declare a billing catalog, spend credits, deploy."
canonical_url: "https://nuxt-backend.dev/docs/start/quickstart"
---
# Quickstart

> From an empty Nuxt app to a deployed, credit-metered backend in about ten minutes — install, run, sign in, declare a billing catalog, spend credits, deploy.

Ten minutes, six steps, nothing to fill in by hand. Every command below is exactly what the module scaffolds and what the `nuxt-backend` CLI does — see [CLI](https://nuxt-backend.dev/docs/reference/cli) for every flag.

<tip>

Starting a new app? Create it from the [starter template](https://nuxt-backend.dev/docs/start/templates#starter) with `create nuxt` and skip the install step: the module is installed, the backend is scaffolded, and `npm run dev` already runs `nuxt-backend dev`. Or hand the first two steps to a coding agent with the one-paste prompt in [Installation](https://nuxt-backend.dev/docs/start/installation#with-an-ai-agent).

</tip>

<steps level="3">

### Install the module

<code-group>

```bash [nuxt]
npx nuxt module add nuxt-backend
npm install convex
```

```bash [pnpm]
pnpm add nuxt-backend convex
```

```bash [npm]
npm install nuxt-backend convex
```

</code-group>

`nuxt module add` also adds `'nuxt-backend'` to `modules` in `nuxt.config.ts` (with a plain package-manager install, add it yourself — or run `npx nuxt-backend init`, which does the same wiring). In a fresh `create nuxt` app, `app/app.vue` still renders `<NuxtWelcome />` and no `<NuxtPage />` — the pages this module registers (`/login`, `/pricing`, …) resolve but never render. Replace `<NuxtWelcome />` with `<NuxtPage />` (`npx nuxt-backend init` does it while the file is still the starter; the dev console warns while it is not done). `nuxt-backend` brings the Convex integration and every bundled component as its dependencies; `convex` is its peer and the CLI you run, and has to be in your own `package.json` for `npx convex dev` to start (`init` adds it when missing).

<note>

Strict pnpm hides transitive packages from Convex's bundler. Add `publicHoistPattern: ['@convex-dev/*']` to `pnpm-workspace.yaml` first — see [Installation](https://nuxt-backend.dev/docs/start/installation#using-strict-pnpm).

</note>

### Start Convex and Nuxt

One command:

```bash
npx nuxt-backend dev
```

`npx nuxt-backend init` points your `dev` script at it, so from then on `npm run dev` does the same. The first run sets up a Convex dev deployment (log in and pick a project when it asks) and writes `CONVEX_DEPLOYMENT` to `.env.local`. It also writes the Convex functions directory — `backend/` with `convex.config.ts`, `auth.config.ts`, `auth.ts`, `http.ts`, `functions.ts`, `billing.ts`, `billing.catalog.ts`, `billing.generated.ts`, `ai.ts`, `email.ts`, `rateLimiter.ts`, `workflows.ts`, `migrations.ts`, `aggregates.ts`, `search.ts` (fifteen files, listed in [Installation](https://nuxt-backend.dev/docs/start/installation#start-the-app-once-to-scaffold-the-backend)), and runs the same engine as `npx nuxt-backend env push` against the dev deployment: `AUTH_SECRET` is generated, `SITE_URL` defaults to `http://localhost:3000`, and `NUXT_BACKEND_LOG_OTP=1` is set because no email transport exists yet. Then Convex and Nuxt run together in that terminal. There is nothing to copy into `.env`: both Convex URLs derive from the `CONVEX_DEPLOYMENT` slug.

<note>

Prefer two terminals? `npx convex dev` in one and a plain `nuxt dev` in the other works too, in either order: the module provisions the env as soon as `CONVEX_DEPLOYMENT` appears. [`nuxt-backend dev`](https://nuxt-backend.dev/docs/reference/cli#dev) explains why `convex dev --start "nuxt dev"` alone waits forever on a new deployment.

</note>

The dev console ends with a preflight summary: `backend preflight: 8 checks ✓` once everything is set, otherwise the count and ids of the findings that need attention. On a fresh app that is `backend preflight: 4 findings: email-transport, email-webhook-secret, billing-access, billing-webhook-secret` — email and billing are not connected yet, which is expected.

### Sign in with the console OTP

Open `/login` — the module mounts it. Enter your email (and a name), request a code. With no `EMAIL_API_KEY` configured the one-time code is **printed in the terminal running nuxt-backend dev** instead of being emailed. Paste it and you are signed in; the first sign-in creates and activates a personal workspace. Sign-in returns to `/` — a fresh app has no page there yet, so add one first (or open `/settings`, which the module mounts):

```vue [app/pages/index.vue]
<script setup lang="ts">
const { session } = useAuth()
const { current } = useOrganization()
</script>

<template>
  <Authenticated>
    Signed in as {{ session.data?.user.email }} · workspace “{{ current?.name }}”
  </Authenticated>
  <Unauthenticated>
    <NuxtLink to="/login">Sign in</NuxtLink>
  </Unauthenticated>
</template>
```

### Declare a billing catalog and sync it

Billing stays empty until `BILLING_ACCESS_TOKEN` is set. Billing is powered by Polar: open its **sandbox** dashboard at [sandbox.polar.sh](https://sandbox.polar.sh), create an organization access token from the organization settings (with every scope, `members:write` and `customer_sessions:write` among them), add it to `.env.local`, and fill the scaffolded catalog (every entry ships commented out):

```ts [backend/billing.catalog.ts]
import { defineBillingCatalog } from 'nuxt-backend/billing'

export default defineBillingCatalog({
  meters: { credits: {} },
  plans: {
    pro: {
      name: 'Pro', interval: 'month', price: 2900,
      credits: { meter: 'credits', units: 500 },
      features: ['priority_support'],
    },
  },
  packs: {
    credits500: { name: '500 credits', price: 2000, credits: { meter: 'credits', units: 500 } },
  },
  features: {
    priority_support: { description: 'Priority support' },
  },
})
```

In a second terminal — `nuxt-backend dev` keeps running in the first — forward the token to the deployment, then push the catalog:

```bash
echo 'BILLING_ACCESS_TOKEN=...' >> .env.local
npx nuxt-backend env push       # forwards BILLING_ACCESS_TOKEN to the deployment, which reads the products with it
npx nuxt-backend billing sync   # creates the meter, benefits, products and the /billing/events webhook, stores its secret, loads the products
```

`billing sync` is find-or-create: every object it makes is tagged `metadata: { managedBy: 'nuxt-backend', key }`, re-running is idempotent, and nothing remote is ever updated or deleted. It writes the id map to `backend/billing.generated.ts` — the only place provider UUIDs live. It provisions the webhook endpoint at your dev deployment's own `.convex.site` URL and stores its secret there as `BILLING_WEBHOOK_SECRET`, without printing it. Then it runs `billing:syncProducts` on the deployment, so the pricing page lists the products at once — which is why the token goes first: without it on the deployment, `billing sync` skips that step and says what to run. Details in [Catalog as code](https://nuxt-backend.dev/docs/billing/catalog).

### Spend credits

The scaffolded `backend/ai.ts` already ships a metered action on the `credits` meter:

```ts [backend/ai.ts]
export const transform = ai.meteredAction({
  meter: 'credits',
  args: { prompt: v.string() },
  handler: async (ctx, { prompt }) => {
    // const completion = await yourModel(prompt)
    return { echo: prompt.toUpperCase(), chargedTo: ctx.usage.entityId }
  },
})
```

Buy the pack (a sandbox checkout accepts the test card `4242 4242 4242 4242` with any future expiry and CVC), then call it:

```vue [app/pages/credits.vue]
<script setup lang="ts">
import { api } from '#backend/api'

definePageMeta({ middleware: 'auth' })

const { products } = useBilling()
const credits = useCredits('credits')
const transform = useAction(api.ai.transform)
</script>

<template>
  <p>{{ credits.balance.value ?? '—' }} credits</p>
  <button v-if="products?.credits500" @click="credits.topUp(products.credits500.id)">
    Buy 500 credits
  </button>
  <button @click="transform({ prompt: 'hello' })">Spend one credit</button>
</template>
```

Each call reserves one credit against the cached balance, runs the handler, then settles the spend as an ingested usage event on the provider — a failed run releases the reservation and charges nothing. The balance updates live through the webhook-synced cache. See [Credits & top-ups](https://nuxt-backend.dev/docs/billing/credits) and [Metered actions](https://nuxt-backend.dev/docs/ai/metered-actions).

### Deploy

```bash
npx nuxt-backend doctor --prod                        # production posture: missing email/billing config is a failure
CONVEX_DEPLOY_KEY=prod:... npx nuxt-backend env push --prod   # forward .env.local to the production deployment; never invents values
npx convex deploy --cmd 'npm run build'               # push the backend, then build the Nuxt app against it
```

`env push --prod` fails on any missing required variable (`AUTH_SECRET`, `SITE_URL`) instead of inventing one, and never forwards the dev-only `AUTH_TRUST_LOCAL_ORIGINS`. The full story — hosts, the env split, previews — is in [Deployment](https://nuxt-backend.dev/docs/ship/deploy) and the [checklist](https://nuxt-backend.dev/docs/ship/production).

</steps>

## Where next

<card-group>
<card icon="i-lucide-blocks" title="Platform" to="https://nuxt-backend.dev/docs/start/concepts/how-it-works">

Auth, workspaces, billing, AI, email, webhooks — everything the scaffold wired up, capability by capability.

</card>

<card icon="i-lucide-terminal" title="CLI" to="https://nuxt-backend.dev/docs/reference/cli">

`init`, `doctor`, `env push`, `billing sync` — every flag, from the source.

</card>

<card icon="i-lucide-ship" title="Production" to="https://nuxt-backend.dev/docs/ship/deploy">

Hosting, the env split, the pre-launch checklist, security.

</card>

<card icon="i-lucide-layout-template" title="Templates" to="https://nuxt-backend.dev/docs/start/templates">

The starter and the advanced example, one `create nuxt` command each.

</card>

<card icon="i-lucide-flask-conical" title="Playground" to="https://nuxt-backend.dev/playground">

Every capability running live against this site's own deployment.

</card>
</card-group>


## Sitemap

See the full [sitemap](https://nuxt-backend.dev/sitemap.md) for all pages.
