# Quickstart: Next.js
Source: https://sovaria.in/docs/quickstart-nextjs

Build the notes app with the Next.js App Router: sign up and in from a client component, list and add notes as the signed-in user, and read them in a route handler with the user's own token.

A Next.js 15 app (App Router, TypeScript) where people sign up, sign in, add notes and see only their own. It takes about ten minutes.

## Before you start

Do steps 1 to 3 of the [Quickstart](https://sovaria.in/docs/quickstart) first: create a project, copy its **project URL** and **anon key** ([Quickstart](https://sovaria.in/docs/quickstart#connect)), and create the `notes` table with its row rules ([Quickstart](https://sovaria.in/docs/quickstart#table)). Every signed-in user then sees and changes only their own notes, whatever the app code does.

## 1. Create the app and install the SDK

Terminal:
```bash
npx create-next-app@latest notes --ts --app --eslint --no-tailwind --src-dir --import-alias "@/*"
cd notes
npm install @sovaria/sdk
```

## 2. Add your project URL and anon key

.env.local:
```bash
NEXT_PUBLIC_SOVARIA_URL=https://<project-ref>.sovarias.in
NEXT_PUBLIC_SOVARIA_ANON_KEY=<anon-key>
```

Both values are public: anything prefixed `NEXT_PUBLIC_` ends up in the browser bundle, and the anon key only does what your rules allow visitors to do. Never put the service role key in a `NEXT_PUBLIC_` variable.

## 3. Create the client

src/lib/sovaria.ts:
```ts
import { createClient } from '@sovaria/sdk'

// One client for the browser. The signed-in session is kept in localStorage and refreshed on its own.
export const sovaria = createClient(
  process.env.NEXT_PUBLIC_SOVARIA_URL!,
  process.env.NEXT_PUBLIC_SOVARIA_ANON_KEY!,
)

// data.sql answers { columns, rows, row_count }; rows are arrays in column order.
export function toObjects<T>(result: { columns: string[]; rows: unknown[][] }): T[] {
  return result.rows.map((row) => Object.fromEntries(result.columns.map((c, i) => [c, row[i]])) as T)
}
```

## 4. Sign in and work with notes

src/app/page.tsx:
```ts
'use client'
import { useEffect, useState } from 'react'
import { SovariaError } from '@sovaria/sdk'
import { sovaria, toObjects } from '@/lib/sovaria'

type Note = { id: number; body: string; created_at: string }

export default function Home() {
  const [session, setSession] = useState(() => sovaria.auth.getSession())
  const [notes, setNotes] = useState<Note[]>([])
  const [error, setError] = useState('')

  // INITIAL_SESSION, SIGNED_IN, TOKEN_REFRESHED, SIGNED_OUT. Returns a function that stops listening.
  useEffect(() => sovaria.auth.onAuthStateChange((_event, s) => setSession(s)), [])

  async function load() {
    const result = await sovaria.data.sql('select id, body, created_at from notes order by id desc')
    setNotes(toObjects<Note>(result as never))
  }
  useEffect(() => { if (session) load().catch(show) }, [session])

  function show(err: unknown) {
    setError(err instanceof SovariaError ? err.message : String(err))
  }

  async function auth(form: FormData, mode: 'in' | 'up') {
    setError('')
    const email = String(form.get('email')), password = String(form.get('password'))
    try {
      if (mode === 'up') await sovaria.auth.signUp(email, password)
      else await sovaria.auth.signIn(email, password)
    } catch (err) { show(err) }
  }

  async function add(form: FormData) {
    try {
      await sovaria.data.sql('insert into notes (body) values ($1)', [String(form.get('body'))])
      await load()
    } catch (err) { show(err) }
  }

  if (!session) {
    return (
      <form action={(f) => auth(f, 'in')}>
        <input name="email" type="email" placeholder="Email" required />
        <input name="password" type="password" placeholder="Password" required />
        <button>Sign in</button>
        <button formAction={(f) => auth(f, 'up')}>Create account</button>
        {error && <p role="alert">{error}</p>}
      </form>
    )
  }

  return (
    <main>
      <form action={add}>
        <input name="body" placeholder="Write a note" required maxLength={2000} />
        <button>Add</button>
      </form>
      {error && <p role="alert">{error}</p>}
      <ul>{notes.map((n) => <li key={n.id}>{n.body}</li>)}</ul>
      <button onClick={() => sovaria.auth.signOut()}>Sign out</button>
    </main>
  )
}
```

- `signUp` creates the user and signs them in. If the project asks people to confirm their email first, it answers `email_verification_required: true` and no session until they click the link ([Email and password](https://sovaria.in/docs/email-and-password)).
- Values go in the second argument (`$1`, `$2`, …) and the database binds them, so a note can never change the SQL.
- The query has no `where owner_id = …`: the row rules from the Quickstart add it for every request.

## 5. Read notes on the server as the user

A route handler has no browser session. Send the user's access token in the `Authorization` header and create a client per request with it, so the same rules apply on the server.

src/app/api/notes/route.ts:
```ts
import { createClient } from '@sovaria/sdk'
import { toObjects } from '@/lib/sovaria'

export async function GET(req: Request) {
  const token = req.headers.get('authorization')?.replace(/^Bearer /, '')
  if (!token) return new Response('Sign in first', { status: 401 })

  const sovaria = createClient(process.env.NEXT_PUBLIC_SOVARIA_URL!, process.env.NEXT_PUBLIC_SOVARIA_ANON_KEY!, {
    persistSession: false,
  }).setAccessToken(token)

  const result = await sovaria.data.sql('select id, body from notes order by id desc limit 50')
  return Response.json(toObjects(result as never))
}
```

Calling it:
```ts
const token = sovaria.auth.getSession()?.access_token
const notes = await fetch('/api/notes', { headers: { Authorization: `Bearer ${token}` } }).then((r) => r.json())
```

## 6. Run it

Terminal:
```bash
npm run dev   # http://localhost:3000
```

New projects allow `http://localhost:*`, so local development works at once. Before you deploy, add your site's origin under Project settings › API ([Allowed origins (CORS) for browser apps](https://sovaria.in/docs/allowed-origins)), or the browser gets `403 ORIGIN_NOT_ALLOWED`.

## Next steps

- Check who can read and change `notes` before you ship: [Tutorial: prove your table is private before you ship](https://sovaria.in/docs/tutorial-secure-a-table).
- Push new notes to other devices as they happen: [Database change events](https://sovaria.in/docs/realtime-database-changes).
- Every SDK method: [JavaScript SDK](https://sovaria.in/docs/javascript-reference). Server patterns: [JavaScript: React, Next.js, Vite and Node](https://sovaria.in/docs/javascript-frameworks#next).
- Deploy on Vercel with the integration: [Vercel and Netlify](https://sovaria.in/docs/integration-vercel-and-netlify).
