mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
docs: standardize quickstart guides (#48950)
## I have read the [CONTRIBUTING.md](https://github.com/supabase/supabase/blob/master/CONTRIBUTING.md) file. YES ## What kind of change does this PR introduce? Docs update ## What is the new behavior? - All 19 guides follow one step order: create project → set up database → create app → AI tooling → add keys → create client → query data → run it → go to production. Added _template.mdx with structure requirements; it is not enforced with a lint check for now - this will be a separate PR before adding new guides. - 4 new partials replace copy-pasted blocks (AI tooling, connection strings, mobile env vars, going to production). - Error handling: return a message instead of a blank page when a query fails. - All guides verified and tested separately - all work as described. What was fixed: wrong env var names in the Hono sample, a Next.js page that redirected to login, missing database permissions in Refine and Hono, and stale file paths and APIs in SvelteKit, Refine, and TanStack. - Astro, Expo, Python, Laravel, and Rails were live but missing from the quickstart grid or listing page. Added, with two new icons. ## Quick links for review Base preview: https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs **Quickstart discovery**: new Astro/Expo/Python/Laravel/Rails entries and icons - [Docs homepage grid](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs) <img width="1998" height="882" alt="CleanShot 2026-08-12 at 12 06 31@2x" src="https://github.com/user-attachments/assets/942eb7e2-1e85-4b20-a6a7-c2b127d31b2b" /> - [Getting started overview](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started) <img width="856" height="878" alt="CleanShot 2026-08-12 at 12 13 30@2x" src="https://github.com/user-attachments/assets/d48091a9-7daf-4796-a521-14116b7479c9" /> ### New shared files: **[apps/docs/content/guides/getting-started/quickstarts/_template.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/guides/getting-started/quickstarts/_template.mdx?plain=1)** A reference contract the other 19 quickstart guides are checked against. Documents the required frontmatter, the canonical 10-step section order, every guide's deviation from that order (and why), the direct-Postgres exception (Laravel/Rails/RedwoodJS/Spring Boot), and the discovery-surface/icon requirements for adding a new guide. No lint rule enforces it yet; that's a follow-up PR. **[apps/docs/content/_partials/quickstart_ai_tooling.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_ai_tooling.mdx?plain=1)** Example: [Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#4-set-up-ai-tooling-optional) → "Set up AI tooling" section Shared by all 19 guides: astrojs, expo-react-native, flask, flutter, hono, ios-swiftui, kotlin, laravel, nextjs, nuxtjs, reactjs, redwoodjs, refine, ruby-on-rails, solidjs, spring-boot, sveltekit, tanstack, vue **[apps/docs/content/_partials/quickstart_going_to_production.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_going_to_production.mdx?plain=1)** Example: [Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#going-to-production) → "Going to production" section Shared by all 19 guides: same full list as above **[apps/docs/content/_partials/quickstart_connection_string.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_connection_string.mdx?plain=1)** Example: [Laravel](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/laravel#5-set-up-the-postgres-connection-details) → connection string setup step Shared by 3 guides: laravel, ruby-on-rails, spring-boot – the ORM/backend frameworks that connect directly to Postgres rather than through the Data API **[apps/docs/content/_partials/quickstart_mobile_env_note.mdx](https://github.com/supabase/supabase/blob/e311542913cf8da07f322a7586339d6f5de30c61/apps/docs/content/_partials/quickstart_mobile_env_note.mdx?plain=1)** Example: [iOS SwiftUI](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ios-swiftui#get-api-details:~:text=This%20guide%20substitutes%20your%20project%20URL%20and%20key%20directly) → environment variables step Shared by 3 guides: ios-swiftui, flutter, kotlin – note Expo React Native is mobile too but doesn't use this partial, since it has its own `EXPO_PUBLIC_` prefix convention inline instead. ## Per guide changes **[Astro](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/astrojs#9-query-supabase-data-from-astro)** Typed query error in the server client sample. **[Expo React Native](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/expo-react-native#8-query-data-from-the-app)** Added an `error` state alongside instruments. Also removed the broken [`--web` verification path](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/expo-react-native#9-start-the-app): expo-sqlite needs Metro wasm + COEP/COOP config the guide never had (CodeRabbit finding). **[Flask](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flask#7-create-the-supabase-client)** Split "Create the Supabase client" and ["Query data"](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flask#8-query-data-from-the-app) into their own steps. **[Flutter](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flutter#9-setup-deep-links-optional)** Reworded the deep-links section; keeps the framework-specific [Android `INTERNET` permission subsection](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/flutter#android) under "Going to production." **[Hono](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/hono#6-declare-supabase-environment-variables)** Split into "Install dependencies," "Declare environment variables," "Set up anonymous sign-ins," and "Query data" as separate steps. Fixes wrong env var names from the previous sample. **[iOS SwiftUI](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ios-swiftui#8-query-data-from-the-app)** Added an `isLoading` state so the loading overlay doesn't hang forever on a successful empty result (CodeRabbit fix). **[Kotlin](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/kotlin#5-install-dependencies)** Fixed the Compose compiler plugin declaration: `apply false` was missing from the app module (CodeRabbit finding). **[Laravel](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/laravel#5-set-up-the-postgres-connection-details)** Now uses the shared `quickstart_connection_string.mdx` partial for the session-pooler/SSL guidance instead of inline copy. **[Next.js](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nextjs#6-allow-public-access-to-the-instruments-page)** New step fixing the page that previously redirected to login. Its middleware path check is also now segment-aware so it doesn't over-match paths like `/instruments-private` (CodeRabbit finding). **[Nuxt](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nuxtjs#7-create-the-supabase-client)** "Create the Supabase client" and ["Query data"](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/nuxtjs#8-query-data-from-the-app) split out as their own steps. **[React](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/reactjs#7-create-the-supabase-client)** Same client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/reactjs#8-query-data-from-the-app) split as the other Vite-based guides. **[RedwoodJS](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/redwoodjs#2-gather-database-connection-strings)** Expanded into explicit transaction-mode/session-mode connection strings, Prisma schema, migration, seed, and scaffold steps; fixes stale file paths and APIs from the previous version. **[Refine](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/refine#8-allow-writes-to-the-instruments-table)** New step fixing the missing RLS grants that made the scaffolded create/edit pages fail. **[Ruby on Rails](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ruby-on-rails#4-set-up-the-postgres-connection-details)** Now uses `quickstart_connection_string.mdx`; added a [reminder to save the database password](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/ruby-on-rails#1-create-a-supabase-project) before it's needed for the connection string. **[SolidJS](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/solidjs#7-create-the-supabase-client)** Same client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/solidjs#8-query-data-from-the-app) split, adapted to Solid's `resource.error`. **[Spring Boot](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/spring-boot#4-set-up-the-postgres-connection-details)** Connection-string section now uses the shared partial instead of a duplicated inline caution. **[SvelteKit](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/sveltekit#8-query-data-from-the-app)** Updated `load` functions (both `+page.js` and `+page.server.ts` variants) with explicit query-error typing; fixes stale file paths and APIs from the previous version. **[TanStack](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/tanstack#8-query-supabase-data-from-tanstack-start)** `fetchInstruments` now returns and renders the query error instead of silently returning an empty list (CodeRabbit finding); fixes stale file paths and APIs from the previous version. **[Vue](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/vue#7-create-the-supabase-client)** Same client-creation/[query-data](https://docs-git-docs-standardize-framework-quickstarts-supabase.vercel.app/docs/guides/getting-started/quickstarts/vue#8-query-data-from-the-app) split as the other Vite-based guides. <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **New Features** * Added SolidJS, RedwoodJS, Refine, Laravel, and Ruby on Rails quickstarts. * Added framework discovery entries for Astro, Expo React Native, Python, Laravel, and Rails. * Added optional AI tooling, MCP setup, connection-string, mobile configuration, and production-readiness guidance. * Added a Hono authentication example with anonymous sign-in, user details, and instrument data. * **Documentation** * Expanded setup, environment, authentication, RLS, migration, SSL, and deployment guidance. * **Bug Fixes** * Improved sample error handling for failed data requests. <!-- end of auto-generated comment: release notes by coderabbit.ai --> --------- Co-authored-by: Miranda Limonczenko <miranda.limonczenko@supabase.io>
This commit is contained in:
1 parent
ebb8e2336e
commit
ee1eb5dbca
34 files changed
+1089
-431
No files matched your search
@@ -15,6 +15,7 @@
|
||||
"@hono/vite-build": "^1.1.0",
|
||||
"@hono/vite-dev-server": "^0.17.0",
|
||||
"@types/node": "^20.11.17",
|
||||
"typescript": "^5.6.2",
|
||||
"vite": "^5.4.2"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,109 @@
|
||||
import type { AppType } from '.'
|
||||
import { createBrowserClient } from '@supabase/ssr'
|
||||
import { hc } from 'hono/client'
|
||||
import { useEffect, useState } from 'hono/jsx'
|
||||
import { render } from 'hono/jsx/dom'
|
||||
|
||||
const client = hc<AppType>('/')
|
||||
|
||||
const supabase = createBrowserClient(
|
||||
import.meta.env.VITE_SUPABASE_URL!,
|
||||
import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY!
|
||||
)
|
||||
|
||||
function App() {
|
||||
const [user, setUser] = useState<null | { id: string }>(null)
|
||||
// Check client-side if user is logged in:
|
||||
useEffect(() => {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
console.log('Auth event:', event)
|
||||
if (event === 'SIGNED_OUT') {
|
||||
setUser(null)
|
||||
} else {
|
||||
setUser(session?.user!)
|
||||
}
|
||||
})
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
}, [])
|
||||
|
||||
return (
|
||||
<>
|
||||
<h1>Hono Supabase Auth Example!</h1>
|
||||
<h2>Sign in</h2>
|
||||
{!user ? (
|
||||
<SignIn />
|
||||
) : (
|
||||
<form method="post" action="/signout">
|
||||
<button type="submit">Sign out!</button>
|
||||
</form>
|
||||
)}
|
||||
<h2>Example of API fetch()</h2>
|
||||
<UserDetailsButton />
|
||||
<h2>Example of database read</h2>
|
||||
<p>Sign in anonymously, then open the instruments list.</p>
|
||||
<a href="/instruments">Get instruments</a>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
function SignIn() {
|
||||
return (
|
||||
<>
|
||||
<p>
|
||||
Read about and enable{' '}
|
||||
<a href="https://supabase.com/docs/guides/auth/auth-anonymous" target="_blank">
|
||||
anonymous sign-ins here!
|
||||
</a>
|
||||
</p>
|
||||
<button
|
||||
type="button"
|
||||
onClick={async () => {
|
||||
const { data, error } = await supabase.auth.signInAnonymously()
|
||||
if (error) return console.error('Error signing in:', error.message)
|
||||
console.log('Signed in client-side!')
|
||||
alert('Signed in anonymously! User id: ' + data?.user?.id)
|
||||
}}
|
||||
>
|
||||
Anonymous sign in
|
||||
</button>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
const UserDetailsButton = () => {
|
||||
const [response, setResponse] = useState<string | null>(null)
|
||||
|
||||
const handleClick = async () => {
|
||||
const response = await client.api.user.$get()
|
||||
const data = await response.json()
|
||||
const headers = Array.from(response.headers.entries()).reduce<Record<string, string>>(
|
||||
(acc, [key, value]) => {
|
||||
acc[key] = value
|
||||
return acc
|
||||
},
|
||||
{}
|
||||
)
|
||||
const fullResponse = {
|
||||
url: response.url,
|
||||
status: response.status,
|
||||
headers,
|
||||
body: data,
|
||||
}
|
||||
setResponse(JSON.stringify(fullResponse, null, 2))
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
<button type="button" onClick={handleClick}>
|
||||
Get My User Details
|
||||
</button>
|
||||
{response && <pre>{response}</pre>}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
const root = document.getElementById('root')!
|
||||
render(<App />, root)
|
||||
@@ -1,16 +1,19 @@
|
||||
import { Hono } from 'hono'
|
||||
import { csrf } from 'hono/csrf'
|
||||
|
||||
import { getSupabase, supabaseMiddleware } from './middleware/auth.middleware'
|
||||
|
||||
const app = new Hono()
|
||||
app.use('*', csrf())
|
||||
app.use('*', supabaseMiddleware())
|
||||
|
||||
app.get('/api/user', async (c) => {
|
||||
const routes = app.get('/api/user', async (c) => {
|
||||
const supabase = getSupabase(c)
|
||||
const { data, error } = await supabase.auth.getClaims()
|
||||
|
||||
if (error) console.log('error', error)
|
||||
|
||||
if (!data?.user) {
|
||||
if (!data?.claims) {
|
||||
return c.json({
|
||||
message: 'You are not logged in.',
|
||||
})
|
||||
@@ -18,23 +21,49 @@ app.get('/api/user', async (c) => {
|
||||
|
||||
return c.json({
|
||||
message: 'You are logged in!',
|
||||
userId: data.user,
|
||||
userId: data.claims.sub,
|
||||
})
|
||||
})
|
||||
|
||||
app.get('/signout', async (c) => {
|
||||
app.post('/signout', async (c) => {
|
||||
const supabase = getSupabase(c)
|
||||
await supabase.auth.signOut()
|
||||
console.log('Signed out server-side!')
|
||||
return c.redirect('/')
|
||||
return c.redirect('/', 303)
|
||||
})
|
||||
|
||||
// Retrieve data with RLS enabled. The signed in user's auth token is automatically sent.
|
||||
app.get('/countries', async (c) => {
|
||||
app.get('/instruments', async (c) => {
|
||||
const supabase = getSupabase(c)
|
||||
const { data, error } = await supabase.from('countries').select('*')
|
||||
if (error) console.log(error)
|
||||
const { data, error } = await supabase.from('instruments').select('*')
|
||||
|
||||
if (error) {
|
||||
console.error(error)
|
||||
return c.json({ error: error.message }, 500)
|
||||
}
|
||||
|
||||
return c.json(data)
|
||||
})
|
||||
|
||||
export type AppType = typeof routes
|
||||
|
||||
app.get('/', (c) => {
|
||||
return c.html(
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charSet="utf-8" />
|
||||
<meta content="width=device-width, initial-scale=1" name="viewport" />
|
||||
<link rel="stylesheet" href="https://cdn.simplecss.org/simple.min.css" />
|
||||
{import.meta.env.PROD ? (
|
||||
<script type="module" src="/static/client.js" />
|
||||
) : (
|
||||
<script type="module" src="/src/client.tsx" />
|
||||
)}
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" />
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
})
|
||||
|
||||
export default app
|
||||
@@ -3,6 +3,7 @@ import { SupabaseClient } from '@supabase/supabase-js'
|
||||
import type { Context, MiddlewareHandler } from 'hono'
|
||||
import { env } from 'hono/adapter'
|
||||
import { setCookie } from 'hono/cookie'
|
||||
import type { CookieOptions } from 'hono/utils/cookie'
|
||||
|
||||
import type { Database } from '../database.types'
|
||||
|
||||
@@ -17,22 +18,23 @@ export const getSupabase = (c: Context) => {
|
||||
}
|
||||
|
||||
type SupabaseEnv = {
|
||||
SUPABASE_URL: string
|
||||
SUPABASE_PUBLISHABLE_KEY: string
|
||||
VITE_SUPABASE_URL: string
|
||||
VITE_SUPABASE_PUBLISHABLE_KEY: string
|
||||
}
|
||||
|
||||
export const supabaseMiddleware = (): MiddlewareHandler => {
|
||||
return async (c, next) => {
|
||||
const supabaseEnv = env<SupabaseEnv>(c)
|
||||
const supabaseUrl = supabaseEnv.SUPABASE_URL
|
||||
const supabasePublishableKey = supabaseEnv.SUPABASE_PUBLISHABLE_KEY
|
||||
const supabaseUrl = supabaseEnv.VITE_SUPABASE_URL ?? import.meta.env.VITE_SUPABASE_URL
|
||||
const supabasePublishableKey =
|
||||
supabaseEnv.VITE_SUPABASE_PUBLISHABLE_KEY ?? import.meta.env.VITE_SUPABASE_PUBLISHABLE_KEY
|
||||
|
||||
if (!supabaseUrl) {
|
||||
throw new Error('SUPABASE_URL missing!')
|
||||
throw new Error('VITE_SUPABASE_URL missing!')
|
||||
}
|
||||
|
||||
if (!supabasePublishableKey) {
|
||||
throw new Error('SUPABASE_PUBLISHABLE_KEY missing!')
|
||||
throw new Error('VITE_SUPABASE_PUBLISHABLE_KEY missing!')
|
||||
}
|
||||
|
||||
const supabase = createServerClient(supabaseUrl, supabasePublishableKey, {
|
||||
@@ -40,8 +42,11 @@ export const supabaseMiddleware = (): MiddlewareHandler => {
|
||||
getAll() {
|
||||
return parseCookieHeader(c.req.header('Cookie') ?? '')
|
||||
},
|
||||
setAll(cookiesToSet) {
|
||||
cookiesToSet.forEach(({ name, value, options }) => setCookie(c, name, value, options))
|
||||
setAll(cookiesToSet, cacheHeaders) {
|
||||
cookiesToSet.forEach(({ name, value, options }) =>
|
||||
setCookie(c, name, value, options as CookieOptions)
|
||||
)
|
||||
Object.entries(cacheHeaders).forEach(([key, value]) => c.header(key, value))
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"target": "ESNext",
|
||||
"module": "ESNext",
|
||||
"moduleResolution": "Bundler",
|
||||
"strict": true,
|
||||
"lib": ["ESNext", "DOM", "DOM.Iterable"],
|
||||
"types": ["vite/client"],
|
||||
"jsx": "react-jsx",
|
||||
"jsxImportSource": "hono/jsx"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import devServer from '@hono/vite-dev-server'
|
||||
import { defineConfig } from 'vite'
|
||||
|
||||
// Change the import to use your runtime specific build
|
||||
import build from '@hono/vite-build/node'
|
||||
|
||||
export default defineConfig(({ mode }) => {
|
||||
if (mode === 'client')
|
||||
return {
|
||||
esbuild: {
|
||||
jsxImportSource: 'hono/jsx/dom', // Optimized for hono/jsx/dom
|
||||
},
|
||||
build: {
|
||||
rollupOptions: {
|
||||
input: './src/client.tsx',
|
||||
output: {
|
||||
entryFileNames: 'static/client.js',
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
return {
|
||||
plugins: [
|
||||
build({
|
||||
entry: 'src/index.tsx',
|
||||
}),
|
||||
devServer({
|
||||
entry: 'src/index.tsx',
|
||||
}),
|
||||
],
|
||||
}
|
||||
})
|
||||
Reference in new issue
Block a user