# Add a backend to Nuxt with one command

> Give a Nuxt app Postgres, auth, storage and realtime: `npx scribase init` provisions the backend and writes .env; one edit exposes the keys server and client.

Source: https://scribase.com/frameworks/nuxt

init does not have Nuxt-specific conventions yet, so it treats a Nuxt app as a Node project: SUPABASE_URL and SUPABASE_ANON_KEY in .env and a client at the root. Nuxt code in the browser cannot read process.env, so move the client into a composable that reads runtimeConfig. That is the one edit.

## `npx scribase init` output

```
$ npx scribase init
Backend ready: https://acme-my-app-production-c2b0ea0a.scribase.com
  framework   node
  project     acme/my-app (production)
  env file    .env (SUPABASE_URL, SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, SCRIBASE_API_URL, SCRIBASE_ORG, SCRIBASE_PROJECT, SCRIBASE_ENV)
  client      supabase.ts

Next:
  - import { supabase } from './supabase' and query: await supabase.from('todos').select()
  - create tables with SQL migrations (supabase/migrations/*.sql) and `scribase db push`, or the MCP server
```

## Environment file: .env

- SUPABASE_URL
- SUPABASE_ANON_KEY
- SUPABASE_SERVICE_ROLE_KEY

## Run init

Run it in the project root. If you are not signed in, it prints a link and a code: approve it in the browser (sign up takes one click plus the $1 card check, credited back), and init carries on. No key is ever pasted. It creates the project, writes the env file, adds the env file to .gitignore, installs @supabase/supabase-js and writes a small client module.

```bash
npx scribase init
```

## Expose the public values through runtimeConfig

Nuxt loads .env in development and reads process.env when it builds the config. Only the public pair goes under public; the service role key stays server-only.

_nuxt.config.ts_
```ts
export default defineNuxtConfig({
  runtimeConfig: {
    supabaseServiceRoleKey: process.env.SUPABASE_SERVICE_ROLE_KEY,
    public: {
      supabaseUrl: process.env.SUPABASE_URL,
      supabaseAnonKey: process.env.SUPABASE_ANON_KEY,
    },
  },
});
```

## Replace the generated client with a composable

Delete supabase.ts at the root and add this. Nuxt auto-imports it everywhere.

_composables/useSupabase.ts_
```ts
import { createClient, type SupabaseClient } from '@supabase/supabase-js';

let client: SupabaseClient | undefined;

export function useSupabase(): SupabaseClient {
  const { supabaseUrl, supabaseAnonKey } = useRuntimeConfig().public;
  client ??= createClient(supabaseUrl as string, supabaseAnonKey as string);
  return client;
}
```

## Create your first table

Open your project in the console, go to Editor, then SQL, and run this. Or ask your coding agent: with the Scribase MCP server connected it applies the same SQL as a migration. Row-level security is on, so the public key can only read what the policy allows.

```sql
create table public.todos (
  id bigint generated always as identity primary key,
  title text not null,
  done boolean not null default false,
  created_at timestamptz not null default now()
);

alter table public.todos enable row level security;

create policy "todos are readable" on public.todos
  for select to anon, authenticated using (true);

insert into public.todos (title) values ('Ship the backend'), ('Tell a friend');
```

## Use it in a page

useAsyncData fetches on the server for the first render and hydrates on the client.

_pages/index.vue_
```vue
<script setup lang="ts">
const { data: todos } = await useAsyncData('todos', async () => {
  const { data, error } = await useSupabase().from('todos').select('id, title');
  if (error) throw error;
  return data;
});
</script>

<template>
  <ul><li v-for="todo in todos" :key="todo.id">{{ todo.title }}</li></ul>
</template>
```

## Agent prompt

```
Add a Scribase backend to this Nuxt app.
1. Run `npx scribase init --yes --json` in the project root. If it returns authorization_pending, show me the link and code, wait for me to approve, then run it again.
2. Create a `todos` table (id, title, done, created_at) with row-level security on and a policy that lets anyone read it; use the Scribase MCP server or a SQL migration, not the service-role key in app code.
3. Expose SUPABASE_URL and SUPABASE_ANON_KEY through runtimeConfig.public in nuxt.config.ts (service role key server-only), replace the generated root supabase.ts with composables/useSupabase.ts, and list the todos on pages/index.vue with useAsyncData.
4. Never print, log or commit the env file; it is already in .gitignore.
```

## FAQ

**In production the values are empty. Why?**

Nuxt reads .env only in development. In production set NUXT_PUBLIC_SUPABASE_URL and NUXT_PUBLIC_SUPABASE_ANON_KEY (Nuxt maps them onto runtimeConfig.public), or set the SUPABASE_ variables at build time.

**Can I use a Nuxt module instead?**

Any module built on supabase-js works, because Scribase speaks the same API. Point it at the same URL and publishable key.