mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 01:15:03 +03:00
Merge branch 'master' into docs/database-api
This commit is contained in:
commit
eda821e21b
322 files changed
+13928
-4808
No files matched your search
+19
-6
@@ -1,7 +1,20 @@
|
||||
node_modules/
|
||||
# Root folder of this turbo monorepo
|
||||
# Doesn't send useless context to Docker daemon
|
||||
.git
|
||||
.github
|
||||
about
|
||||
apps
|
||||
docker
|
||||
examples
|
||||
i18n
|
||||
**/node_modules
|
||||
spec
|
||||
supabase
|
||||
tests
|
||||
|
||||
**/.next
|
||||
.vercel
|
||||
.next
|
||||
.env.local
|
||||
.env.production
|
||||
.env.dev
|
||||
.env.*
|
||||
.env.*
|
||||
|
||||
.dockerignore
|
||||
studio/Dockerfile
|
||||
@@ -0,0 +1,20 @@
|
||||
name: reviewdog
|
||||
on:
|
||||
pull_request:
|
||||
types: [opened, synchronize, reopened, edited]
|
||||
|
||||
jobs:
|
||||
misspell:
|
||||
name: runner / misspell
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Check out code.
|
||||
uses: actions/checkout@v1
|
||||
- name: misspell
|
||||
uses: reviewdog/action-misspell@v1
|
||||
with:
|
||||
github_token: ${{ secrets.github_token }}
|
||||
locale: "US"
|
||||
reporter: github-pr-review
|
||||
level: error
|
||||
exclude: "*.css"
|
||||
@@ -19,12 +19,16 @@ jobs:
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
id-token: write
|
||||
steps:
|
||||
- name: configure aws credentials
|
||||
uses: aws-actions/configure-aws-credentials@v1
|
||||
with:
|
||||
role-to-assume: ${{ secrets.PROD_AWS_ROLE }}
|
||||
aws-region: us-east-1
|
||||
- uses: docker/login-action@v2
|
||||
with:
|
||||
registry: public.ecr.aws
|
||||
username: ${{ secrets.PROD_ACCESS_KEY_ID }}
|
||||
password: ${{ secrets.PROD_SECRET_ACCESS_KEY }}
|
||||
- uses: docker/login-action@v2
|
||||
with:
|
||||
registry: ghcr.io
|
||||
|
||||
@@ -186,7 +186,7 @@
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 Supabase
|
||||
Copyright 2023 Supabase
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
|
||||
@@ -14,12 +14,22 @@ import { useInView } from 'react-intersection-observer'
|
||||
*/
|
||||
|
||||
interface Props {
|
||||
tag: string
|
||||
tag?: string
|
||||
parseAnchors?: boolean
|
||||
customAnchor?: string
|
||||
}
|
||||
|
||||
const Heading: React.FC<Props> = ({ tag, children }) => {
|
||||
/**
|
||||
* This TOC is used in .mdx files and in .tsx files.
|
||||
* In mdx files, we need to parse the content and format them to match the
|
||||
* expected tocList format (text, link,level).
|
||||
*
|
||||
* In tsx files, we can generate this tocList directly. For these files, we don't
|
||||
* need to parse the <a> and generate anchors
|
||||
*/
|
||||
const Heading: React.FC<Props> = ({ tag, parseAnchors = false, customAnchor, children }) => {
|
||||
const HeadingTag = `${tag}` as any
|
||||
const anchor = getAnchor(children)
|
||||
const anchor = customAnchor ? customAnchor : getAnchor(children)
|
||||
const link = `#${anchor}`
|
||||
|
||||
const { ref } = useInView({
|
||||
@@ -33,7 +43,7 @@ const Heading: React.FC<Props> = ({ tag, children }) => {
|
||||
|
||||
return (
|
||||
<HeadingTag id={anchor} ref={ref} className="group scroll-mt-24">
|
||||
{removeAnchor(children)}
|
||||
{parseAnchors ? removeAnchor(children) : children}
|
||||
{anchor && (
|
||||
<a href={link} className="ml-2 opacity-0 group-hover:opacity-100 transition">
|
||||
#
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import React, { useState } from 'react'
|
||||
import KJUR from 'jsrsasign'
|
||||
import CodeBlock from './CodeBlock/CodeBlock'
|
||||
import { Button } from 'ui'
|
||||
import { Button, Select, Input } from 'ui'
|
||||
|
||||
const JWT_HEADER = { alg: 'HS256', typ: 'JWT' }
|
||||
const now = new Date()
|
||||
@@ -46,9 +46,8 @@ export default function JwtGenerator({}) {
|
||||
<div>
|
||||
<div className="grid mb-8">
|
||||
<label for="secret">JWT Secret:</label>
|
||||
<input
|
||||
<Input
|
||||
id="secret"
|
||||
className="border rounded bg-gray-200 w-full"
|
||||
type="text"
|
||||
placeholder="JWT Secret (at least 32 characters)"
|
||||
value={jwtSecret}
|
||||
@@ -57,19 +56,15 @@ export default function JwtGenerator({}) {
|
||||
</div>
|
||||
<div className="grid mb-8">
|
||||
<label for="service">Preconfigured Payload:</label>
|
||||
<select
|
||||
id="service"
|
||||
onChange={handleKeySelection}
|
||||
className="border rounded bg-gray-200 w-full"
|
||||
>
|
||||
<option value="anon">ANON_KEY</option>
|
||||
<option value="service">SERVICE_KEY</option>
|
||||
</select>
|
||||
<Select id="service" onChange={handleKeySelection}>
|
||||
<Select.Option value="anon">ANON_KEY</Select.Option>
|
||||
<Select.Option value="service">SERVICE_KEY</Select.Option>
|
||||
</Select>
|
||||
</div>
|
||||
|
||||
<div className="grid mb-8">
|
||||
<label for="token">Payload:</label>
|
||||
<textarea
|
||||
<Input.TextArea
|
||||
id="token"
|
||||
type="text"
|
||||
rows="5"
|
||||
|
||||
@@ -0,0 +1,108 @@
|
||||
If you upload additional profile photos, they'll accumulate
|
||||
in the `avatars` bucket because of their random names with only the latest being referenced
|
||||
from `public.profiles` and the older versions getting orphaned.
|
||||
|
||||
To automatically remove obsolete storage objects, extend the database
|
||||
triggers. Note that it is not sufficient to delete the objects from the
|
||||
`storage.objects` table because that would orphan and leak the actual storage objects in
|
||||
the S3 backend. Instead, invoke the storage API within Postgres via the `http` extension.
|
||||
|
||||
Enable the [http extension for the `extensions` schema](https://app.supabase.com/project/_/database/extensions) in the Dashboard.
|
||||
Then, define the following SQL functions in the SQL Editor to delete
|
||||
storage objects via the API:
|
||||
|
||||
```SQL
|
||||
create or replace function delete_storage_object(bucket text, object text, out status int, out content varchar)
|
||||
returns record
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
project_url varchar := '<YOURPROJECTURL>';
|
||||
service_role_key varchar := '<YOURSERVICEROLEKEY>'; -- full access needed
|
||||
url varchar := project_url||'/storage/v1/object/'||bucket||'/'||object;
|
||||
begin
|
||||
select
|
||||
into status, content
|
||||
result.status::int, result.content::varchar
|
||||
FROM extensions.http((
|
||||
'DELETE',
|
||||
url,
|
||||
ARRAY[extensions.http_header('authorization','Bearer '||service_role_key)],
|
||||
NULL,
|
||||
NULL)::extensions.http_request) as result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create or replace function delete_avatar(avatar_url text, out status int, out content varchar)
|
||||
returns record
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
begin
|
||||
select
|
||||
into status, content
|
||||
result.status, result.content
|
||||
from public.delete_storage_object('avatars', avatar_url) as result;
|
||||
end;
|
||||
$$;
|
||||
|
||||
```
|
||||
|
||||
Next, add a trigger that removes any obsolete avatar whenever the
|
||||
profile is updated or deleted:
|
||||
|
||||
```SQL
|
||||
create or replace function delete_old_avatar()
|
||||
returns trigger
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
declare
|
||||
status int;
|
||||
content varchar;
|
||||
begin
|
||||
if coalesce(old.avatar_url, '') <> ''
|
||||
and (tg_op = 'DELETE' or (old.avatar_url <> new.avatar_url)) then
|
||||
select
|
||||
into status, content
|
||||
result.status, result.content
|
||||
from public.delete_avatar(old.avatar_url) as result;
|
||||
if status <> 200 then
|
||||
raise warning 'Could not delete avatar: % %', status, content;
|
||||
end if;
|
||||
end if;
|
||||
if tg_op = 'DELETE' then
|
||||
return old;
|
||||
end if;
|
||||
return new;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger before_profile_changes
|
||||
before update of avatar_url or delete on public.profiles
|
||||
for each row execute function public.delete_old_avatar();
|
||||
|
||||
```
|
||||
|
||||
Finally, delete the `public.profile` row before a user is deleted.
|
||||
If this step is omitted, you won't be able to delete users without
|
||||
first manually deleting their avatar image.
|
||||
|
||||
```SQL
|
||||
create or replace function delete_old_profile()
|
||||
returns trigger
|
||||
language 'plpgsql'
|
||||
security definer
|
||||
as $$
|
||||
begin
|
||||
delete from public.profiles where id = old.id;
|
||||
return old;
|
||||
end;
|
||||
$$;
|
||||
|
||||
create trigger before_delete_user
|
||||
before delete on auth.users
|
||||
for each row execute function public.delete_old_profile();
|
||||
|
||||
```
|
||||
@@ -264,6 +264,7 @@ export const menuItems: NavMenu = {
|
||||
{ name: 'Compute Add-ons', url: '/guides/platform/compute-add-ons', items: [] },
|
||||
{ name: 'Custom Domains', url: '/guides/platform/custom-domains', items: [] },
|
||||
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
|
||||
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
|
||||
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
|
||||
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
|
||||
{
|
||||
@@ -275,6 +276,7 @@ export const menuItems: NavMenu = {
|
||||
{ name: 'Performance Tuning', url: '/guides/platform/performance', items: [] },
|
||||
{ name: 'Permissions', url: '/guides/platform/permissions', items: [] },
|
||||
{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] },
|
||||
{ name: 'Troubleshooting', url: '/guides/platform/troubleshooting', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -339,6 +341,7 @@ export const menuItems: NavMenu = {
|
||||
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
|
||||
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
|
||||
{ name: 'WeWeb', url: '/guides/integrations/weweb', items: [] },
|
||||
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
|
||||
@@ -95,7 +95,13 @@ const home = [
|
||||
level: 'reference_dart',
|
||||
},
|
||||
{
|
||||
label: 'Tools Reference',
|
||||
label: 'Python',
|
||||
icon: '/img/icons/menu/reference-python',
|
||||
href: '/reference/python/introduction',
|
||||
level: 'reference_python',
|
||||
},
|
||||
{
|
||||
label: 'Tools',
|
||||
},
|
||||
{
|
||||
label: 'Management API',
|
||||
@@ -106,7 +112,7 @@ const home = [
|
||||
{
|
||||
label: 'Supabase CLI',
|
||||
icon: '/img/icons/menu/reference-cli',
|
||||
href: '/reference/cli/introduction',
|
||||
href: '/guides/cli',
|
||||
level: 'reference_javascript',
|
||||
},
|
||||
],
|
||||
|
||||
@@ -48,6 +48,9 @@ export const gettingstarted = {
|
||||
{ name: 'ReactJS', url: '/guides/getting-started/quickstarts/reactjs', items: [] },
|
||||
{ name: 'NextJS', url: '/guides/getting-started/quickstarts/nextjs', items: [] },
|
||||
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter', items: [] },
|
||||
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit', items: [] },
|
||||
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs', items: [] },
|
||||
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -320,6 +323,11 @@ export const auth = {
|
||||
{ name: 'Overview', url: '/guides/auth/auth-helpers', items: [] },
|
||||
{ name: 'Auth UI', url: '/guides/auth/auth-helpers/auth-ui', items: [] },
|
||||
{ name: 'Next.js', url: '/guides/auth/auth-helpers/nextjs', items: [] },
|
||||
{
|
||||
name: 'Next.js Server Components',
|
||||
url: '/guides/auth/auth-helpers/nextjs-server-components',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'Remix', url: '/guides/auth/auth-helpers/remix', items: [] },
|
||||
{ name: 'SvelteKit', url: '/guides/auth/auth-helpers/sveltekit', items: [] },
|
||||
],
|
||||
@@ -421,14 +429,30 @@ export const functions = {
|
||||
label: 'Edge Functions',
|
||||
url: '/guides/functions',
|
||||
items: [
|
||||
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
|
||||
{ name: 'Auth', url: '/guides/functions/auth', items: [] },
|
||||
{ name: 'CI/CD Workflow', url: '/guides/functions/cicd-workflow', items: [] },
|
||||
{ name: 'Best Practices', url: '/guides/functions/best-practices', items: [] },
|
||||
{ name: 'Quickstart', url: '/guides/functions/quickstart', items: [] },
|
||||
{ name: 'CORS', url: '/guides/functions/cors', items: [] },
|
||||
{ name: 'Debugging', url: '/guides/functions/debugging', items: [] },
|
||||
{ name: 'Import Maps', url: '/guides/functions/import-maps', items: [] },
|
||||
{ name: 'Local Development', url: '/guides/functions/local-development', items: [] },
|
||||
{ name: 'Managing Secrets', url: '/guides/functions/secrets', items: [] },
|
||||
{ name: 'Schedule Functions', url: '/guides/functions/schedule-functions', items: [] },
|
||||
{
|
||||
name: 'Examples',
|
||||
url: '/guides/functions/examples',
|
||||
items: [{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] }],
|
||||
items: [
|
||||
{
|
||||
name: 'Cloudflare Turnstile',
|
||||
url: '/guides/functions/examples/cloudflare-turnstile',
|
||||
items: [],
|
||||
},
|
||||
{ name: 'GitHub Actions', url: '/guides/functions/examples/github-actions', items: [] },
|
||||
{ name: 'OG Image', url: '/guides/functions/examples/og-image', items: [] },
|
||||
{ name: 'Storage Caching', url: '/guides/functions/examples/storage-caching', items: [] },
|
||||
{ name: 'Stripe Webhooks', url: '/guides/functions/examples/stripe-webhooks', items: [] },
|
||||
{ name: 'Telegram Bot', url: '/guides/functions/examples/telegram-bot', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
@@ -464,6 +488,25 @@ export const storage = {
|
||||
],
|
||||
}
|
||||
|
||||
export const supabase_cli = {
|
||||
icon: 'reference-cli',
|
||||
title: 'Supabase CLI',
|
||||
url: '/guides/cli',
|
||||
items: [
|
||||
{ name: 'Overview', url: '/guides/cli', items: [] },
|
||||
{ name: 'Local Development', url: '/guides/cli/local-development', items: [] },
|
||||
{ name: 'Managing Environments', url: '/guides/cli/managing-environments', items: [] },
|
||||
{
|
||||
name: 'Reference',
|
||||
url: undefined,
|
||||
items: [
|
||||
{ name: 'Commands', url: '/reference/cli/introduction', items: [] },
|
||||
{ name: 'Configuration', url: '/reference/cli/config', items: [] },
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
export const platform = {
|
||||
icon: 'platform',
|
||||
label: 'Platform',
|
||||
@@ -484,6 +527,7 @@ export const platform = {
|
||||
items: [
|
||||
{ name: 'Access Control', url: '/guides/platform/access-control', items: [] },
|
||||
{ name: 'Database Usage', url: '/guides/platform/database-usage', items: [] },
|
||||
{ name: 'HTTP Status Codes', url: '/guides/platform/http-status-codes', items: [] },
|
||||
{ name: 'Logging', url: '/guides/platform/logs', items: [] },
|
||||
{ name: 'Metrics', url: '/guides/platform/metrics', items: [] },
|
||||
{
|
||||
@@ -519,6 +563,17 @@ export const platform = {
|
||||
url: undefined,
|
||||
items: [{ name: 'Production Readiness', url: '/guides/platform/going-into-prod', items: [] }],
|
||||
},
|
||||
{
|
||||
name: 'Troubleshooting',
|
||||
url: undefined,
|
||||
items: [
|
||||
{
|
||||
name: 'HTTP and Project Issues',
|
||||
url: '/guides/platform/troubleshooting',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -556,26 +611,6 @@ export const resources = {
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
name: 'Supabase CLI',
|
||||
items: [
|
||||
{
|
||||
name: 'Overview',
|
||||
url: '/guides/resources/supabase-cli',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Local Development',
|
||||
url: '/guides/resources/supabase-cli/local-development',
|
||||
items: [],
|
||||
},
|
||||
{
|
||||
name: 'Managing Environments',
|
||||
url: '/guides/resources/supabase-cli/managing-environments',
|
||||
items: [],
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
}
|
||||
|
||||
@@ -672,6 +707,7 @@ export const integrations = {
|
||||
{ name: 'Directus', url: '/guides/integrations/directus', items: [] },
|
||||
{ name: 'Draftbit', url: '/guides/integrations/draftbit', items: [] },
|
||||
{ name: 'Plasmic', url: '/guides/integrations/plasmic', items: [] },
|
||||
{ name: 'ILLA', url: '/guides/integrations/illa', items: [] },
|
||||
],
|
||||
},
|
||||
{
|
||||
@@ -748,17 +784,24 @@ export const reference_javascript_v2 = {
|
||||
|
||||
export const reference_dart_v0 = {
|
||||
icon: 'reference-dart',
|
||||
title: 'dart',
|
||||
title: 'Flutter',
|
||||
url: '/guides/reference/dart',
|
||||
parent: '/reference',
|
||||
}
|
||||
export const reference_dart_v1 = {
|
||||
icon: 'reference-dart',
|
||||
title: 'dart',
|
||||
title: 'Flutter',
|
||||
url: '/guides/reference/dart',
|
||||
parent: '/reference',
|
||||
}
|
||||
|
||||
export const reference_python_v2 = {
|
||||
icon: 'reference-python',
|
||||
title: 'Python',
|
||||
url: '/guides/reference/python',
|
||||
parent: '/reference',
|
||||
}
|
||||
|
||||
export const reference_cli = {
|
||||
icon: 'reference-cli',
|
||||
title: 'Supabase CLI',
|
||||
|
||||
@@ -11,6 +11,9 @@ import spec_js_v1 from '~/../../spec/supabase_js_v1.yml' assert { type: 'yml' }
|
||||
import spec_dart_v1 from '~/../../spec/supabase_dart_v1.yml' assert { type: 'yml' }
|
||||
// @ts-expect-error
|
||||
import spec_dart_v0 from '~/../../spec/supabase_dart_v0.yml' assert { type: 'yml' }
|
||||
// @ts-expect-error
|
||||
import spec_python_v2 from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
|
||||
|
||||
// import { gen_v3 } from '~/lib/refGenerator/helpers'
|
||||
import apiCommonSections from '~/../../spec/common-api-sections.json'
|
||||
import cliCommonSections from '~/../../spec/common-cli-sections.json'
|
||||
@@ -47,6 +50,7 @@ export type RefIdOptions =
|
||||
| 'reference_javascript_v2'
|
||||
| 'reference_dart_v0'
|
||||
| 'reference_dart_v1'
|
||||
| 'reference_python_v2'
|
||||
| 'reference_cli'
|
||||
| 'reference_api'
|
||||
| 'reference_self_hosting_auth'
|
||||
@@ -56,6 +60,7 @@ export type RefIdOptions =
|
||||
export type RefKeyOptions =
|
||||
| 'javascript'
|
||||
| 'dart'
|
||||
| 'python'
|
||||
| 'cli'
|
||||
| 'api'
|
||||
| 'self-hosting-auth'
|
||||
@@ -65,16 +70,6 @@ export type RefKeyOptions =
|
||||
const NavigationMenu = () => {
|
||||
const router = useRouter()
|
||||
|
||||
let version = ''
|
||||
|
||||
if (router.asPath.includes('v1')) {
|
||||
version = '_v1'
|
||||
}
|
||||
|
||||
if (router.asPath.includes('v0')) {
|
||||
version = '_v0'
|
||||
}
|
||||
|
||||
function handleRouteChange(url: string) {
|
||||
switch (url) {
|
||||
case `/docs`:
|
||||
@@ -110,6 +105,9 @@ const NavigationMenu = () => {
|
||||
case url.includes(`/docs/guides/integrations`) && url:
|
||||
menuState.setMenuLevelId('integrations')
|
||||
break
|
||||
case url.includes(`/docs/guides/cli`) && url:
|
||||
menuState.setMenuLevelId('supabase_cli')
|
||||
break
|
||||
// JS v1
|
||||
case url.includes(`/docs/reference/javascript/v1`) && url:
|
||||
menuState.setMenuLevelId('reference_javascript_v1')
|
||||
@@ -126,6 +124,13 @@ const NavigationMenu = () => {
|
||||
case url.includes(`/docs/reference/dart`) && url:
|
||||
menuState.setMenuLevelId('reference_dart_v1')
|
||||
break
|
||||
// puthon v2 (latest)
|
||||
case url.includes(`/docs/reference/python`) && url:
|
||||
menuState.setMenuLevelId('reference_python_v2')
|
||||
break
|
||||
case url.includes(`/docs/reference/cli/config`) && url:
|
||||
menuState.setMenuLevelId('supabase_cli')
|
||||
break
|
||||
case url.includes(`/docs/reference/cli`) && url:
|
||||
menuState.setMenuLevelId('reference_cli')
|
||||
break
|
||||
@@ -165,6 +170,7 @@ const NavigationMenu = () => {
|
||||
const isFunctionsActive = 'functions' === level
|
||||
const isRealtimeActive = 'realtime' === level
|
||||
const isStorageActive = 'storage' === level
|
||||
const issupabase_cliActive = 'supabase_cli' === level
|
||||
const isPlatformActive = 'platform' === level
|
||||
const isResourcesActive = 'resources' === level
|
||||
const isSelfHosting = 'self_hosting' === level
|
||||
@@ -175,6 +181,7 @@ const NavigationMenu = () => {
|
||||
const isReference_Javascript_V2 = 'reference_javascript_v2' === level
|
||||
const isReference_Dart_V0 = 'reference_dart_v0' === level
|
||||
const isReference_Dart_V1 = 'reference_dart_v1' === level
|
||||
const isReference_Python_V2 = 'reference_python_v2' === level
|
||||
const isReference_Cli = 'reference_cli' === level
|
||||
const isReference_Api = 'reference_api' === level
|
||||
const isReference_Self_Hosting_Auth = 'reference_self_hosting_auth' === level
|
||||
@@ -191,6 +198,7 @@ const NavigationMenu = () => {
|
||||
<NavigationMenuGuideList id={'functions'} active={isFunctionsActive} />
|
||||
<NavigationMenuGuideList id={'realtime'} active={isRealtimeActive} />
|
||||
<NavigationMenuGuideList id={'storage'} active={isStorageActive} />
|
||||
<NavigationMenuGuideList id={'supabase_cli'} active={issupabase_cliActive} />
|
||||
<NavigationMenuGuideList id={'platform'} active={isPlatformActive} />
|
||||
<NavigationMenuGuideList id={'resources'} active={isResourcesActive} />
|
||||
<NavigationMenuGuideList id={'self_hosting'} active={isSelfHosting} />
|
||||
@@ -229,6 +237,14 @@ const NavigationMenu = () => {
|
||||
lib="dart"
|
||||
spec={spec_dart_v1}
|
||||
/>
|
||||
<NavigationMenuRefList
|
||||
key={'reference-python-menu-v2'}
|
||||
id={'reference_python_v2'}
|
||||
active={isReference_Python_V2}
|
||||
commonSections={libCommonSections}
|
||||
lib="python"
|
||||
spec={spec_python_v2}
|
||||
/>
|
||||
{/* // Tools */}
|
||||
<NavigationMenuRefList
|
||||
key={'reference-cli-menu'}
|
||||
|
||||
@@ -25,6 +25,10 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
|
||||
active,
|
||||
spec,
|
||||
}) => {
|
||||
const filteredSections = commonSections.filter((section) => {
|
||||
return !section.excludes?.includes(id)
|
||||
})
|
||||
|
||||
return (
|
||||
<div
|
||||
className={[
|
||||
@@ -38,7 +42,7 @@ const NavigationMenuRefList: React.FC<INavigationMenuRefList> = ({
|
||||
!active ? 'opacity-0 invisible absolute h-0 overflow-hidden' : '',
|
||||
].join(' ')}
|
||||
>
|
||||
<NavigationMenuRefListItems id={id} lib={lib} commonSections={commonSections} spec={spec} />
|
||||
<NavigationMenuRefListItems id={id} lib={lib} commonSections={filteredSections} spec={spec} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -179,7 +179,7 @@ const Content: React.FC<INavigationMenuRefList> = ({ id, lib, commonSections, sp
|
||||
if (!sections) console.error('no common sections imported')
|
||||
|
||||
const menu = NavItems[id]
|
||||
|
||||
//console.log(menu)
|
||||
const databaseFunctions = find(sections, { title: 'Database' })
|
||||
? find(sections, { title: 'Database' }).items
|
||||
: []
|
||||
|
||||
@@ -20,6 +20,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
|
||||
import ProjectSetup from './MDX/project_setup.mdx'
|
||||
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
|
||||
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
|
||||
import StorageManagement from './MDX/storage_management.mdx'
|
||||
import { Mermaid } from 'mdx-mermaid/lib/Mermaid'
|
||||
import InlineCodeTag from './CustomHTMLElements/InlineCode'
|
||||
import React from 'react'
|
||||
@@ -49,6 +50,7 @@ const components = {
|
||||
ProjectSetup,
|
||||
SocialProviderSetup,
|
||||
SocialProviderSettingsSupabase,
|
||||
StorageManagement,
|
||||
Mermaid,
|
||||
Extensions,
|
||||
Alert: (props: any) => (
|
||||
|
||||
@@ -1,25 +1,12 @@
|
||||
import { IconChevronRight } from '~/../../packages/ui'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import { IconChevronRight, Tabs } from '~/../../packages/ui'
|
||||
// @ts-expect-error
|
||||
import spec from '~/../../spec/cli_v1_commands.yaml' assert { type: 'yml' }
|
||||
import CodeBlock from '~/components/CodeBlock/CodeBlock'
|
||||
import Options from '~/components/Options'
|
||||
import Param from '~/components/Params'
|
||||
import RefSubLayout from '~/layouts/ref/RefSubLayout'
|
||||
|
||||
interface ICommonFunc {
|
||||
id: string
|
||||
title: string
|
||||
slug: string
|
||||
product: string
|
||||
libs: string
|
||||
items: ICommonFunc[]
|
||||
}
|
||||
|
||||
interface IRefFunctionSection {
|
||||
funcData: ICommonFunc
|
||||
commonFuncData: ICommonFunc
|
||||
spec: any
|
||||
}
|
||||
import RefDetailCollapse from './RefDetailCollapse'
|
||||
|
||||
export type Flag = {
|
||||
id: string
|
||||
@@ -37,16 +24,25 @@ export type AcceptedValue = {
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type Example = {
|
||||
id: string
|
||||
name: string
|
||||
code: string
|
||||
response: string
|
||||
description?: string
|
||||
}
|
||||
|
||||
export type Command = {
|
||||
id: string
|
||||
title: string
|
||||
description: string
|
||||
flags?: Flag[]
|
||||
summary: string
|
||||
tags?: []
|
||||
links?: []
|
||||
subcommands?: []
|
||||
tags?: string[]
|
||||
links?: string[]
|
||||
subcommands?: string[]
|
||||
usage?: string
|
||||
examples?: Example[]
|
||||
}
|
||||
|
||||
const CliCommandSection = (props) => {
|
||||
@@ -70,17 +66,17 @@ const CliCommandSection = (props) => {
|
||||
' mb-16',
|
||||
].join(' ')}
|
||||
>
|
||||
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
|
||||
{command.summary}
|
||||
</p>
|
||||
{command.description ? (
|
||||
<div className="prose">
|
||||
<ReactMarkdown>{command.description}</ReactMarkdown>
|
||||
</div>
|
||||
) : (
|
||||
<p className="capitalize mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
|
||||
{command.summary}
|
||||
</p>
|
||||
)}
|
||||
</header>
|
||||
|
||||
{/* {command.usage && (
|
||||
<CodeBlock language="bash" className="relative">
|
||||
{command.usage}
|
||||
</CodeBlock>
|
||||
)} */}
|
||||
|
||||
{command.subcommands.length > 0 && (
|
||||
<div className="mb-3">
|
||||
<h3 className="text-lg text-scale-1200 mb-3">Available Commands</h3>
|
||||
@@ -126,13 +122,82 @@ const CliCommandSection = (props) => {
|
||||
</div>
|
||||
</div>
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
{command.usage && (
|
||||
<CodeBlock language="bash" className="relative">
|
||||
{command.usage}
|
||||
</CodeBlock>
|
||||
)}
|
||||
</RefSubLayout.Examples>
|
||||
{(command.examples || command.usage) && (
|
||||
<RefSubLayout.Examples>
|
||||
<div className="overflow-hidden w-full">
|
||||
<Tabs
|
||||
defaultActiveId={`${command.id}-basic-usage`}
|
||||
size="tiny"
|
||||
type="rounded-pills"
|
||||
scrollable
|
||||
>
|
||||
{command.examples ? (
|
||||
command.examples.map((example) => {
|
||||
const exampleId = `${command.id}-${example.id}`
|
||||
return (
|
||||
<Tabs.Panel
|
||||
id={exampleId}
|
||||
key={exampleId}
|
||||
label={example.name}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<CodeBlock
|
||||
className="useless-code-block-class"
|
||||
language="bash"
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
{example.code}
|
||||
</CodeBlock>
|
||||
|
||||
<RefDetailCollapse
|
||||
id={`${exampleId}-response`}
|
||||
label="Response"
|
||||
defaultOpen={false}
|
||||
>
|
||||
<CodeBlock
|
||||
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
|
||||
language="bash"
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
{example.response}
|
||||
</CodeBlock>
|
||||
</RefDetailCollapse>
|
||||
|
||||
{example.description && (
|
||||
<RefDetailCollapse
|
||||
id={`${exampleId}-notes`}
|
||||
label="Notes"
|
||||
defaultOpen={false}
|
||||
>
|
||||
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
|
||||
<ReactMarkdown className="text-sm">{example.description}</ReactMarkdown>
|
||||
</div>
|
||||
</RefDetailCollapse>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
)
|
||||
})
|
||||
) : (
|
||||
// TODO: remove this block once all commands have examples
|
||||
<Tabs.Panel
|
||||
id={`${command.id}-basic-usage`}
|
||||
key={`${command.id}-basic-usage`}
|
||||
label="Basic usage"
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
<CodeBlock
|
||||
className="useless-code-block-class"
|
||||
language="bash"
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
{command.usage}
|
||||
</CodeBlock>
|
||||
</Tabs.Panel>
|
||||
)}
|
||||
</Tabs>
|
||||
</div>
|
||||
</RefSubLayout.Examples>
|
||||
)}
|
||||
</RefSubLayout.Section>
|
||||
)
|
||||
}
|
||||
|
||||
@@ -143,18 +143,24 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
|
||||
label={example.name}
|
||||
className="flex flex-col gap-3"
|
||||
>
|
||||
{example.description && (
|
||||
<div className="prose">
|
||||
<ReactMarkdown className="text-sm">
|
||||
{example.description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
)}
|
||||
<CodeBlock
|
||||
className="useless-code-block-class"
|
||||
language={codeBlockLang}
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
{exampleString +
|
||||
(example.code &&
|
||||
example.code
|
||||
.replace(/```/g, '')
|
||||
.replace('js', '')
|
||||
.replace('ts', '')
|
||||
.replace('dart', ''))}
|
||||
</CodeBlock>
|
||||
|
||||
{((tables && tables.length > 0) || sql) && (
|
||||
<RefDetailCollapse
|
||||
id={`${example.id}-${exampleIndex}-data`}
|
||||
label="Example data source"
|
||||
label="Data source"
|
||||
defaultOpen={false}
|
||||
>
|
||||
<>
|
||||
@@ -189,28 +195,14 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
|
||||
</RefDetailCollapse>
|
||||
)}
|
||||
|
||||
<CodeBlock
|
||||
className="useless-code-block-class"
|
||||
language={codeBlockLang}
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
{exampleString +
|
||||
(example.code &&
|
||||
example.code
|
||||
.replace(/```/g, '')
|
||||
.replace('js', '')
|
||||
.replace('ts', '')
|
||||
.replace('dart', ''))}
|
||||
</CodeBlock>
|
||||
|
||||
{response && (
|
||||
<RefDetailCollapse
|
||||
id={`${example.id}-${exampleIndex}-response`}
|
||||
label="Example response"
|
||||
label="Response"
|
||||
defaultOpen={false}
|
||||
>
|
||||
<CodeBlock
|
||||
className="useless-code-block-class"
|
||||
className="useless-code-block-class rounded !rounded-tl-none !rounded-tr-none border border-scale-500"
|
||||
language={codeBlockLang}
|
||||
hideLineNumbers={true}
|
||||
>
|
||||
@@ -218,6 +210,20 @@ const RefFunctionSection: React.FC<IRefFunctionSection> = (props) => {
|
||||
</CodeBlock>
|
||||
</RefDetailCollapse>
|
||||
)}
|
||||
|
||||
{example.description && (
|
||||
<RefDetailCollapse
|
||||
id={`${example.id}-${exampleIndex}-notes`}
|
||||
label="Notes"
|
||||
defaultOpen={false}
|
||||
>
|
||||
<div className="bg-scale-300 border border-scale-500 rounded !rounded-tl-none !rounded-tr-none prose max-w-none px-5 py-2">
|
||||
<ReactMarkdown className="text-sm">
|
||||
{example.description}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</RefDetailCollapse>
|
||||
)}
|
||||
</Tabs.Panel>
|
||||
)
|
||||
})}
|
||||
|
||||
@@ -15,6 +15,7 @@ const CliGlobalFlagsHandler = () => {
|
||||
return (
|
||||
<Param
|
||||
{...flag}
|
||||
id={`${spec.id}-${flag.id}`}
|
||||
isOptional={flag.required === undefined ? true : !flag.required}
|
||||
></Param>
|
||||
)
|
||||
|
||||
@@ -468,6 +468,13 @@
|
||||
"installed_version": "1.1",
|
||||
"comment": "generate universally unique identifiers (UUIDs)"
|
||||
},
|
||||
{
|
||||
"name": "vector",
|
||||
"schema": "extensions",
|
||||
"default_version": "0.4.0",
|
||||
"installed_version": null,
|
||||
"comment": "vector data type with similarity search"
|
||||
},
|
||||
{
|
||||
"name": "xml2",
|
||||
"schema": null,
|
||||
|
||||
@@ -2,7 +2,7 @@ const nonGeneratedReferencePages = [
|
||||
'docs/reference/javascript',
|
||||
'docs/reference/javascript/installing',
|
||||
'docs/reference/javascript/release-notes',
|
||||
'docs/reference/javascript/upgrade-guide',
|
||||
'docs/reference/javascript/v1/upgrade-guide',
|
||||
'docs/reference/javascript/typescript-support',
|
||||
'docs/reference/javascript/v1',
|
||||
'docs/reference/javascript/v1/installing',
|
||||
@@ -11,7 +11,7 @@ const nonGeneratedReferencePages = [
|
||||
'docs/reference/dart',
|
||||
'docs/reference/dart/installing',
|
||||
'docs/reference/dart/initializing',
|
||||
'docs/reference/dart/upgrade-guide',
|
||||
'docs/reference/dart/v0/upgrade-guide',
|
||||
'docs/reference/dart/v0',
|
||||
'docs/reference/api',
|
||||
'docs/reference/api/release-notes',
|
||||
|
||||
@@ -17,7 +17,7 @@ hideTitle: true
|
||||
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
|
||||
The CLI is still under development, but it contains all the functionality for working with your Supabase projects and the Supabase Platform.
|
||||
|
||||
- Run Supabase locally: [`supabase start`](/docs/reference/cli/usage#supabase-start)
|
||||
- Run Supabase locally: [`supabase init`](/docs/reference/cli/usage#supabase-init) and [`supabase start`](/docs/reference/cli/usage#supabase-start)
|
||||
- Manage database migrations: [`supabase migration`](/docs/reference/cli/usage#supabase-migration)
|
||||
- CI/CD for releasing to production: [`supabase db push`](/docs/reference/cli/usage#supabase-db-push)
|
||||
- Manage your Supabase projects: [`supabase projects`](/docs/reference/cli/usage#supabase-projects)
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
---
|
||||
id: initializing
|
||||
title: 'Initializing'
|
||||
slug: initializing
|
||||
---
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
Initialize Supabase with the static `initialize()` method on `Supabase` class.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="flutter"
|
||||
>
|
||||
<TabPanel id="flutter" label="Flutter">
|
||||
|
||||
```dart main.dart
|
||||
Future<void> main() async {
|
||||
WidgetsFlutterBinding.ensureInitialized();
|
||||
|
||||
await Supabase.initialize(
|
||||
url: 'https://xyzcompany.supabase.co',
|
||||
anonKey: 'public-anon-key',
|
||||
);
|
||||
|
||||
runApp(MyApp());
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="dart" label="Other Dart Project">
|
||||
|
||||
```dart
|
||||
final supabase = SupabaseClient(
|
||||
'https://xyzcompany.supabase.co',
|
||||
'public-anon-key',
|
||||
);
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -13,11 +13,12 @@ hideTitle: true
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
This reference documents every object and method available in Supabase's Flutter
|
||||
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
|
||||
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
|
||||
Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
<br />
|
||||
<br />
|
||||
We also provide [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
|
||||
|
||||
This reference documents every object and method available in Supabase's Flutter
|
||||
library, [supabase-flutter](https://pub.dev/packages/supabase_flutter). You can
|
||||
use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
|
||||
Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
|
||||
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
|
||||
|
||||
</div>
|
||||
@@ -0,0 +1,28 @@
|
||||
---
|
||||
id: introduction
|
||||
title: Introduction
|
||||
hideTitle: true
|
||||
---
|
||||
|
||||
<div className="flex items-start gap-6 not-prose" id="introduction">
|
||||
<img src="/docs/img/icons/menu/reference-dart.svg" className="w-8 h-8 rounded" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl text-scale-1200 m-0">Flutter Client Library</h1>
|
||||
<h2 className="text-base font-mono text-scale-1100">supabase-flutter</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
|
||||
<Admonition type="caution">
|
||||
You're viewing the docs for an older version of the `supabase-flutter` library. Learn how to
|
||||
[upgrade to the latest version](/docs/reference/dart/v0/upgrade-guide).
|
||||
</Admonition>
|
||||
|
||||
This reference documents every object and method available in Supabase's Flutter library, [supabase-flutter](https://pub.dev/packages/supabase_flutter).
|
||||
You can use supabase-flutter to interact with your Postgres database, listen to database changes, invoke
|
||||
Deno Edge Functions, build login and user management functionality, and manage large files.
|
||||
|
||||
We also provide a [supabase](https://pub.dev/packages/supabase) package for non-Flutter projects.
|
||||
|
||||
</div>
|
||||
+167
-10
@@ -4,23 +4,35 @@ title: Upgrade to supabase-flutter v1
|
||||
description: 'Learn how to upgrade to supabase-flutter v1.'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide will help you upgrade from supabase-flutter v0 to v1.
|
||||
supabase-flutter focuses on improving the developer experience and making it easier to use. This guide demonstrates how to upgrade from supabase-flutter v0 to v1.
|
||||
|
||||
## Upgrade the client library
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
Update the package in your pubspec.yaml file.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
```yaml
|
||||
supabase_flutter: ^1.0.0
|
||||
```
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
## Error handling
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
The way supabase-flutter throws error has changed in v1. In v0, errors were returned as a response. In v1, errors are thrown as exceptions. This makes it more intuitive as a Flutter developer to handle errors.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -52,11 +64,20 @@ try {
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Auth classes / methods
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
## Auth classes and methods
|
||||
|
||||
### Usage of `SupabaseAuthState` and `SupabaseAuthRequiredState` classes
|
||||
|
||||
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](/docs/reference/dart/upgrade-guide#listening-to-auth-state-change) can be used to action on auth state change.
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
In v0, `SupabaseAuthState` and `SupabaseAuthRequiredState` were required to handle automatic token refresh and to listen to auth state change. In v1, `SupabaseAuthState` and `SupabaseAuthRequiredState` are deprecated, and token refresh will happen automatically just by initializing Supabase. [`onAuthStateChange`](#listening-to-auth-state-change) can be used to action on auth state change.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -97,10 +118,19 @@ await Supabase.initialize(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Listening to auth state change
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
`onAuthStateChange` now returns a `Stream`.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -135,9 +165,18 @@ authSubscription.cancel();
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
### Sign in with email and password
|
||||
|
||||
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
`signIn()` has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -161,8 +200,17 @@ await supabase.auth.signInWithPassword(email: email, password: password);
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Sign in with magic link
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -185,8 +233,17 @@ await supabase.auth.signInWithOtp(email: email);
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Sign in with a third-party OAuth provider
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -218,8 +275,17 @@ await supabase.auth.signInWithOAuth(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Sign in with phone
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -248,8 +314,17 @@ await supabase.auth.signInWithPassword(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Sign in with phone using OTP
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -281,8 +356,17 @@ await supabase.auth.verifyOTP(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Reset password for email
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -312,8 +396,17 @@ await supabase.auth.resetPasswordForEmail(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Get the user's current session
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -336,8 +429,17 @@ final Session? session = supabase.auth.currentSession;
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Get the logged-in user
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -360,8 +462,17 @@ final User? user = supabase.auth.currentUser;
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Update user data for a logged-in user
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -390,16 +501,23 @@ await supabase.updateUser(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
## Data methods
|
||||
|
||||
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default.
|
||||
`.insert()` / `.upsert()` / `.update()` / `.delete()` no longer return rows by default. Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
|
||||
|
||||
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
|
||||
Also, calling `.execute()` at the end of the query was a requirement in v0, but deprecated in v1.
|
||||
|
||||
Also, calling `.execute()` at the end of the query was a requirement in v0, but in v1 `.execute` is deperecated.
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Insert without returning inserted data
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -426,8 +544,17 @@ await supabase.from('my_table').insert(data);
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Insert with returning inserted data
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -453,12 +580,21 @@ final insertedData = await supabase.from('my_table').insert(data).select();
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
## Realtime methods
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Stream
|
||||
|
||||
`.stream()` no longer needs the `.execute()` at the end. Also, filtering by `eq` is a lot easier now. `primaryKey` is now a named parameter to make it more obvious what to pass.
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -486,8 +622,17 @@ supabase.from('my_table')
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Subscribe
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -522,8 +667,17 @@ channel.on(
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
|
||||
<RefSubLayout.EducationRow>
|
||||
<RefSubLayout.Details>
|
||||
|
||||
### Unsubscribe
|
||||
|
||||
</RefSubLayout.Details>
|
||||
<RefSubLayout.Examples>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -545,3 +699,6 @@ await supabase.removeChannel(channel);
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
</RefSubLayout.Examples>
|
||||
</RefSubLayout.EducationRow>
|
||||
@@ -13,8 +13,15 @@ hideTitle: true
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript
|
||||
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
|
||||
database changes, invoke Deno Edge Functions, build login and user management functionality, and
|
||||
manage large files.
|
||||
|
||||
<Admonition type="caution">
|
||||
You're viewing the docs for an older version of the `supabase-js` library. Learn how to [upgrade
|
||||
to the latest version](/docs/reference/javascript/v1/upgrade-guide).
|
||||
</Admonition>
|
||||
|
||||
This reference documents every object and method available in Supabase's isomorphic JavaScript
|
||||
library, supabase-js. You can use supabase-js to interact with your Postgres database, listen to
|
||||
database changes, invoke Deno Edge Functions, build login and user management functionality, and
|
||||
manage large files.
|
||||
|
||||
</div>
|
||||
File renamed without changes.
@@ -0,0 +1,21 @@
|
||||
---
|
||||
id: introduction
|
||||
title: Introduction
|
||||
hideTitle: true
|
||||
---
|
||||
|
||||
<div className="flex items-start gap-6 not-prose" id="introduction">
|
||||
<img src="/docs/img/icons/menu/reference-python.svg" className="w-8 h-8 rounded" />
|
||||
<div className="flex flex-col gap-2">
|
||||
<h1 className="text-3xl text-scale-1200 m-0">Python Client Library</h1>
|
||||
<h2 className="text-base font-mono text-scale-1100">@supabase-community/supabase-py</h2>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div className="max-w-xl">
|
||||
This reference documents every object and method available in the
|
||||
[supabase-py](https://github.com/supabase-community/supabase-py) library from the Supabase
|
||||
community. You can use `supabase-py` to interact with your Postgres database, listen to database
|
||||
changes, invoke Deno Edge Functions, build login and user management functionality, and manage
|
||||
large files.
|
||||
</div>
|
||||
@@ -0,0 +1,10 @@
|
||||
---
|
||||
id: release-notes
|
||||
title: Release Notes
|
||||
---
|
||||
|
||||
The community is actively working on the library and we will be upgrading the Authentication library, `gotrue-py`, to mirror the Supabase-js v2 lib.
|
||||
|
||||
## Storage Transformations
|
||||
|
||||
We currently support [image transformations](https://supabase.com/docs/guides/storage/image-transformations) in our storage library.
|
||||
@@ -1,439 +0,0 @@
|
||||
---
|
||||
id: upgrade-guide
|
||||
title: Upgrade to supabase-js v2
|
||||
description: 'Learn how to upgrade to supabase-js v2.'
|
||||
---
|
||||
|
||||
import Tabs from '@theme/Tabs'
|
||||
import TabItem from '@theme/TabItem'
|
||||
|
||||
supabase-js v2 focuses on "quality-of-life" improvements for developers and addresses some of the largest pain points in v1. v2 includes type support, a rebuilt Auth library with async methods, improved errors, and more.
|
||||
|
||||
No new features will be added to supabase-js v1 , but we'll continuing merging security fixes to v1, with maintenance patches for the next 3 months.
|
||||
|
||||
## Upgrade the client library
|
||||
|
||||
```sh
|
||||
npm install @supabase/supabase-js@2
|
||||
```
|
||||
|
||||
_Optionally_ if you are using custom configuration with `createClient` then follow below:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts title=src/supabaseClient.ts
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
schema: 'custom',
|
||||
persistSession: false,
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts title=src/supabaseClient.ts
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
db: {
|
||||
schema: 'custom',
|
||||
},
|
||||
auth: {
|
||||
persistSession: true,
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Read more about the [constructor options](/docs/reference/javascript/release-notes#explicit-constructor-options).
|
||||
|
||||
### Auth methods
|
||||
|
||||
The signIn() method has been deprecated in favor of more explicit method signatures to help with type hinting. Previously it was difficult for developers to know what they were missing (e.g., a lot of developers didn't realize they could use passwordless magic links).
|
||||
|
||||
#### Sign in with email and password
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { user, error } = await supabase.auth.signIn({ email, password })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.signInWithPassword({ email, password })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with magic link
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ email })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithOtp({ email })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with a third-party provider
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ provider })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithOAuth({ provider })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with phone
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signIn({ phone, password })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.signInWithPassword({ phone, password })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Sign in with phone using OTP
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { error } = await supabase.auth.api.sendMobileOTP(phone)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.signInWithOtp({ phone })
|
||||
|
||||
// After receiving a SMS with a OTP.
|
||||
const { data, error } = await supabase.auth.verifyOtp({ phone, token })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Reset password for email
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.api.resetPasswordForEmail(email)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.resetPasswordForEmail(email)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Get the user's current session
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const session = supabase.auth.session()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Get the logged-in user
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const user = supabase.auth.user()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
const { user } = session
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Update user data for a logged-in user
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { user, error } = await supabase.auth.update({ attributes })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const {
|
||||
data: { user },
|
||||
error,
|
||||
} = await supabase.auth.updateUser({ attributes })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Use a custom `access_token` JWT with Supabase
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { user, error } = supabase.auth.setAuth(access_token)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY, {
|
||||
global: {
|
||||
headers: {
|
||||
Authorization: `Bearer ${access_token}`,
|
||||
},
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Cookie methods
|
||||
|
||||
The cookie-related methods like `setAuthCookie` and `getUserByCookie` have been removed.
|
||||
|
||||
For Next.js you can use the [Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to help you manage cookies.
|
||||
If you can't use the Auth Helpers, you can use [server-side rendering](https://supabase.com/docs/guides/auth/server-side-rendering).
|
||||
|
||||
Some the [PR](https://github.com/supabase/gotrue-js/pull/340) for additional background information.
|
||||
|
||||
### Data methods
|
||||
|
||||
`.insert()` / `.upsert()` / `.update()` / `.delete()` don't return rows by default: [PR](https://github.com/supabase/postgrest-js/pull/276).
|
||||
|
||||
Previously, these methods return inserted/updated/deleted rows by default (which caused [some confusion](https://github.com/supabase/supabase/discussions/1548)), and you can opt to not return it by specifying `returning: 'minimal'`. Now the default behavior is to not return rows. To return inserted/updated/deleted rows, add a `.select()` call at the end.
|
||||
|
||||
#### Insert and return data
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.insert({ new_data })
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.insert({ new_data }).select()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Update and return data
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const { data, error } = await supabase.auth.update({ new_data }).eq('id', id).select()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
### Realtime methods
|
||||
|
||||
#### Subscribe
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
const userListener = supabase
|
||||
.from('users')
|
||||
.on('*', (payload) => handleAllEventsPayload(payload.new))
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
const userListener = supabase
|
||||
.channel('public:user')
|
||||
.on('postgres_changes', { event: '*', schema: 'public', table: 'user' }, (payload) =>
|
||||
handleAllEventsPayload()
|
||||
)
|
||||
.subscribe()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
#### Unsubscribe
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="1.0x"
|
||||
>
|
||||
<TabPanel id="1.0x" label="Before">
|
||||
|
||||
```ts
|
||||
userListener.unsubscribe()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
<TabPanel id="2.0x" label="After">
|
||||
|
||||
```ts
|
||||
supabase.removeChannel(userListener)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
@@ -39,6 +39,10 @@ const levelsData = {
|
||||
icon: '/docs/img/icons/menu/storage',
|
||||
name: 'Storage',
|
||||
},
|
||||
supabase_cli: {
|
||||
icon: '/docs/img/icons/menu/reference-cli',
|
||||
name: 'Supabase CLI',
|
||||
},
|
||||
platform: {
|
||||
icon: '/docs/img/icons/menu/platform',
|
||||
name: 'Platform',
|
||||
@@ -71,6 +75,10 @@ const levelsData = {
|
||||
icon: '/docs/img/icons/menu/reference-dart',
|
||||
name: 'Dart Reference v0.0',
|
||||
},
|
||||
reference_python_v2: {
|
||||
icon: '/docs/img/icons/menu/reference-python',
|
||||
name: 'Python Reference v2.0',
|
||||
},
|
||||
reference_cli: {
|
||||
icon: '/docs/img/icons/menu/reference-cli',
|
||||
name: 'CLI Reference',
|
||||
|
||||
@@ -1,161 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Overview',
|
||||
description: 'An overview of Supabase\'s architecture and product principles.',
|
||||
}
|
||||
|
||||
Supabase is open source. We choose open source tools which are scalable and make them simple to use.
|
||||
|
||||
Supabase is not a 1-to-1 mapping of Firebase. While we are building many of the features that Firebase offers, we are not going about it the same way:
|
||||
our technological choices are quite different; everything we use is open source; and wherever possible, we use and support existing tools rather than developing from scratch.
|
||||
|
||||
Most notably, we use Postgres rather than a NoSQL store. This choice was deliberate. We believe that no other database offers the functionality required to compete with Firebase,
|
||||
while maintaining the scalability required to go beyond it.
|
||||
|
||||
## Architecture
|
||||
|
||||
Each Supabase project consists of several tools:
|
||||
|
||||

|
||||
|
||||
### PostgreSQL (Database)
|
||||
|
||||
PostgreSQL is the core of Supabase. We do not abstract the PostgreSQL database — you can access it and use it with full privileges. We simply provide tools which makes PostgreSQL as easy to use as Firebase.
|
||||
|
||||
- Official Docs: [postgresql.org/docs](https://www.postgresql.org/docs/current/index.html)
|
||||
- Source code: [github.com/postgres/postgres](https://github.com/postgres/postgres) (mirror)
|
||||
- License: [PostgreSQL Licence](https://www.postgresql.org/about/licence/)
|
||||
- Language: C
|
||||
|
||||
### Studio (Dashboard)
|
||||
|
||||
An open source Dashboard for managing your database and services.
|
||||
|
||||
- Official Docs: [Supabase docs](/docs)
|
||||
- Source code: [github.com/supabase/supabase](https://github.com/supabase/supabase/tree/master/studio)
|
||||
- License: [Apache 2](https://github.com/supabase/supabase/blob/master/LICENSE)
|
||||
- Language: TypeScript
|
||||
|
||||
### GoTrue (Auth)
|
||||
|
||||
A JWT-based API for managing users and issuing access tokens. This integrates with PostgreSQL's Row Level Security and the API servers.
|
||||
|
||||
- Official Docs: [Supabase Auth reference docs](/docs/reference/auth)
|
||||
- Source code: [github.com/supabase/gotrue](https://github.com/supabase/gotrue)
|
||||
- License: [MIT](https://github.com/supabase/gotrue/blob/master/LICENSE)
|
||||
- Language: Go
|
||||
|
||||
### PostgREST (API)
|
||||
|
||||
A standalone web server that turns your PostgreSQL database directly into a RESTful API.
|
||||
We use this with our [pg_graphql](https://github.com/supabase/pg_graphql) extension to provide a GraphQL API.
|
||||
|
||||
- Official Docs: [postgrest.org](https://postgrest.org/)
|
||||
- Source code: [github.com/PostgREST/postgrest](https://github.com/PostgREST/postgrest)
|
||||
- License: [MIT](https://github.com/PostgREST/postgrest/blob/main/LICENSE)
|
||||
- Language: Haskel
|
||||
|
||||
### Realtime (API & multiplayer)
|
||||
|
||||
A scalable websocket engine for managing user Presence, broadcasting messages, and streaming database changes.
|
||||
|
||||
- Official Docs: [Supabase Realtime docs](/docs/guides/realtime)
|
||||
- Source code: [github.com/supabase/realtime](https://github.com/supabase/realtime)
|
||||
- License: [Apache 2](https://github.com/supabase/realtime/blob/main/LICENSE)
|
||||
- Language: Elixir
|
||||
|
||||
### Storage API (large file storage)
|
||||
|
||||
An S3-compatible object storage service that stores metadata in Postgres.
|
||||
|
||||
- Official Docs: [Supabase Storage reference docs](/docs/reference/storage)
|
||||
- Source code: [github.com/supabase/storage-api](https://github.com/supabase/storage-api)
|
||||
- License: [Apache 2.0](https://github.com/supabase/storage-api/blob/master/LICENSE)
|
||||
- Language: NodeJS / TypeScript
|
||||
|
||||
### Deno (Edge Functions)
|
||||
|
||||
A modern runtime for JavaScript and TypeScript.
|
||||
|
||||
- Official Docs: [deno.land](https://deno.land/)
|
||||
- Source code: [github.com/denoland/deno](https://github.com/denoland/deno)
|
||||
- License: [MIT](https://github.com/denoland/deno/blob/main/LICENSE.md)
|
||||
- Language: TypeScript / Rust
|
||||
|
||||
### postgres-meta (Database management)
|
||||
|
||||
A RESTful API for managing your Postgres. Fetch tables, add roles, and run queries.
|
||||
|
||||
- Official Docs: [supabase.github.io/postgres-meta](https://supabase.github.io/postgres-meta/)
|
||||
- Source code: [github.com/supabase/postgres-meta](https://github.com/supabase/postgres-meta)
|
||||
- License: [Apache 2.0](https://github.com/supabase/postgres-meta/blob/master/LICENSE)
|
||||
- Language: NodeJS / TypeScript
|
||||
|
||||
### PgBouncer
|
||||
|
||||
A lightweight connection pooler for PostgreSQL. This is useful for connecting to Postgres when using Serverless functions.
|
||||
|
||||
- Official Docs: [pgbouncer.org](http://www.pgbouncer.org/)
|
||||
- Source code: [pgbouncer/pgbouncer](https://github.com/pgbouncer/pgbouncer)
|
||||
- License: [ISC](https://github.com/pgbouncer/pgbouncer/blob/master/COPYRIGHT)
|
||||
- Language: C
|
||||
|
||||
### Kong (API Gateway)
|
||||
|
||||
A cloud-native API gateway, built on top of Nginx.
|
||||
|
||||
- Official Docs: [docs.konghq.com](https://docs.konghq.com/)
|
||||
- Source code: [github.com/kong/kong](https://github.com/kong/kong)
|
||||
- License: [Apache 2.0](https://github.com/Kong/kong/blob/master/LICENSE)
|
||||
- Language: Lua
|
||||
|
||||
## Product Principles
|
||||
|
||||
It is our goal to provide an architecture that any large-scale company would design for themselves,
|
||||
and then provide tooling around that architecture that is easy-to-use for indie-developers and small teams.
|
||||
|
||||
We use a series of principles to ensure that scalability and usability are never mutually exclusive:
|
||||
|
||||
### Everything works in isolation
|
||||
|
||||
Each system must work as a standalone tool with as few moving parts as possible.
|
||||
The litmus test for this is: "Can a user run this product with nothing but a Postgres database?"
|
||||
|
||||
### Everything is integrated
|
||||
|
||||
Supabase is composable. Even though every product works in isolation, each product on the platform needs to 10x the other products.
|
||||
For integration, each tool should expose an API and Webhooks.
|
||||
|
||||
### Everything is extensible
|
||||
|
||||
We're deliberate about adding a new tool, and prefer instead to extend an existing one.
|
||||
This is the opposite of many cloud providers whose product offering expands into niche use-cases. We provide _primitives_ for developers, which allow them to achieve any goal.
|
||||
Less, but better.
|
||||
|
||||
### Everything is portable
|
||||
|
||||
To avoid lock-in, we make it easy to migrate in and out. Our cloud offering is compatible with our self-hosted product.
|
||||
We use existing standards to increase portability (like pg_dump an CSV files). If a new standard emerges which competes with a "Supabase" approach, we will deprecate the approach in favor of the standard.
|
||||
This forces us compete on experience. We aim to be the best Postgres hosting service.
|
||||
|
||||
### Play the long game
|
||||
|
||||
We sacrifice short-term wins for long-term gains. For example, it is tempting to run a fork of Postgres with additional functionality which only our customers need.
|
||||
Instead, we prefer to support efforts to upstream missing functionality so that the entire community benefits. This has the additional benefit of ensuring portability and longevity.
|
||||
|
||||
### Build for developers
|
||||
|
||||
"Developers" are a specific profile of user: they are _builders_.
|
||||
When assessing impact as a function of effort, developers have a large efficiency due to the type of products and systems they can build.
|
||||
As the profile of a developer changes over time, Supabase will continue to evolve the product to fit this evolving profile.
|
||||
|
||||
### Support existing tools
|
||||
|
||||
Supabase supports existing tools and communities wherever possible. Supabase is more like a "community of communities" - each tool typically has its own community
|
||||
which we work with.
|
||||
Open source is something we approach [collaboratively](https://supabase.com/blog/supabase-series-b#giving-back): we employ maintainers, sponsor projects, invest in businesses, and develop our own open source tools.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -10,6 +10,8 @@ export const meta = {
|
||||
|
||||
This submodule provides experimental convenience helpers for implementing user authentication in Next.js Server Components - the `app` directory. For examples using the `pages` directory check out [Auth Helpers in Next.js](/docs/guides/auth/auth-helpers/nextjs).
|
||||
|
||||
> For a complete implementation example, check out [this repo](https://github.com/supabase/auth-helpers/tree/main/examples/nextjs-server-components).
|
||||
|
||||
> To learn more about fetching and caching Supabase data with Next.js 13 Server Components, check out our [blog](/blog/fetching-and-caching-supabase-data-in-next-js-server-components) or [live stream](https://www.youtube.com/watch?v=QH0P5xZt5wY).
|
||||
|
||||
## Install the Next.js helper library
|
||||
@@ -52,6 +54,8 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
|
||||
## Creating a Supabase Client
|
||||
|
||||
### Server-side
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -60,23 +64,13 @@ NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new file at `/utils/supabase-browser.js` and populate with the following:
|
||||
|
||||
```js title="/utils/supabase-browser.js"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default createBrowserSupabaseClient()
|
||||
```
|
||||
|
||||
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
|
||||
|
||||
Create a new file at `/utils/supabase-server.js` and populate with the following:
|
||||
|
||||
```js title="/utils/supabase-server.js"
|
||||
import { headers, cookies } from 'next/headers'
|
||||
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
|
||||
export default () =>
|
||||
export const createClient = () =>
|
||||
createServerComponentSupabaseClient({
|
||||
headers,
|
||||
cookies,
|
||||
@@ -85,33 +79,19 @@ export default () =>
|
||||
|
||||
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
|
||||
|
||||
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
|
||||
|
||||
```ts
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Database } from '../lib/database.types'
|
||||
|
||||
export default createBrowserSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
This will be used any time we need to create a Supabase client _client-side_ - in `useEffect`, for example.
|
||||
|
||||
Create a new file at `/utils/supabase-server.ts` and populate with the following:
|
||||
|
||||
```ts title="/utils/supabase-server.ts"
|
||||
import { headers, cookies } from 'next/headers'
|
||||
import { createServerComponentSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Database } from '../lib/database.types'
|
||||
|
||||
export default () =>
|
||||
import type { Database } from '../lib/database.types'
|
||||
|
||||
export const createClient = () =>
|
||||
createServerComponentSupabaseClient<Database>({
|
||||
headers,
|
||||
cookies,
|
||||
@@ -122,14 +102,12 @@ export default () =>
|
||||
|
||||
> This needs to export a function, as the headers and cookies are not populated with values until the Server Component is requesting data.
|
||||
|
||||
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Middleware
|
||||
This will be used any time we need to create a Supabase client _server-side_ - in a Server Component, for example.
|
||||
|
||||
Middleware runs before every route declared in the `matcher` array. Since we don't have access to set cookies or headers from Server Components, we need to create a Middleware Supabase client and refresh the user's session by calling `getSession()`.
|
||||
Next, we need a middleware file to refresh the user's session on navigation.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -139,7 +117,7 @@ Middleware runs before every route declared in the `matcher` array. Since we don
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a new file at `/app/middleware.js` and populate with the following:
|
||||
Create a new `/app/middleware.js` file and populate with the following:
|
||||
|
||||
```jsx title="middleware.js"
|
||||
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
@@ -156,27 +134,25 @@ export async function middleware(req) {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/optional-session', '/required-session', '/realtime'],
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new file at `/app/middleware.ts` and populate with the following:
|
||||
Create a new `/app/middleware.ts` file and populate with the following:
|
||||
|
||||
```tsx title="middleware.ts"
|
||||
import { createMiddlewareSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { NextResponse } from 'next/server'
|
||||
|
||||
import type { NextRequest } from 'next/server'
|
||||
import type { Database } from '../lib/database.types'
|
||||
|
||||
export async function middleware(req: NextRequest) {
|
||||
const res = NextResponse.next()
|
||||
|
||||
const supabase = createMiddlewareSupabaseClient({ req, res })
|
||||
const supabase = createMiddlewareSupabaseClient<Database>({ req, res })
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
@@ -184,20 +160,34 @@ export async function middleware(req: NextRequest) {
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: ['/optional-session', '/required-session', '/realtime'],
|
||||
}
|
||||
```
|
||||
|
||||
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createMiddlewareSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
> Any Server Component route that uses a Supabase client must be added to this middleware's `matcher` array. Without this, the Server Component may try to make a request to Supabase with an expired `access_token`.
|
||||
We can now use our server-side Supabase client to fetch data in Server Components.
|
||||
|
||||
## Supabase Listener
|
||||
```jsx
|
||||
import 'server-only'
|
||||
|
||||
We need to set up a listener to fetch fresh data whenever our user logs in or out.
|
||||
import { createClient } from '../../utils/supabase-server'
|
||||
|
||||
// do not cache this page
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function ServerComponent() {
|
||||
const supabase = createClient()
|
||||
const { data } = await supabase.from('posts').select('*')
|
||||
|
||||
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
### Client-side
|
||||
|
||||
We still need a Supabase instance client-side for authentication and realtime subscriptions. It is important, when using Supabase client-side, to have a single instance of a client. We can share this singleton instance across our components using providers and React context.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
@@ -207,286 +197,291 @@ We need to set up a listener to fetch fresh data whenever our user logs in or ou
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Create a `/components/supabase-listener.jsx` file and add the following:
|
||||
Create a new file at `/utils/supabase-browser.js` and populate with the following:
|
||||
|
||||
```js title="/utils/supabase-browser.js"
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
export const createClient = () => createBrowserSupabaseClient()
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a new file at `/utils/supabase-browser.ts` and populate with the following:
|
||||
|
||||
```ts
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { Database } from '../lib/database.types'
|
||||
|
||||
export const createClient = () => createBrowserSupabaseClient<Database>()
|
||||
```
|
||||
|
||||
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.jsx` and populate with the following:
|
||||
|
||||
```jsx title=components/supabase-provider.jsx
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
import { createClient } from '../utils/supabase-browser'
|
||||
|
||||
const Context = createContext()
|
||||
|
||||
export default function SupabaseProvider({ children }) {
|
||||
const [supabase] = useState(() => createClient())
|
||||
|
||||
useEffect(() => {
|
||||
const { data: { subscription } } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== accessToken) {
|
||||
router.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
}, [accessToken])
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ supabase }}>
|
||||
<>{children}</>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSupabase = () => useContext(Context)
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Next, we need to create a single instance of Supabase to use client-side. Let's create a new Provider for Supabase at `/components/supabase-provider.tsx` and populate with the following:
|
||||
|
||||
```tsx title=components/supabase-provider.tsx
|
||||
'use client'
|
||||
|
||||
import { createContext, useContext, useState } from 'react'
|
||||
import { createClient } from '../utils/supabase-browser'
|
||||
|
||||
import type { SupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import type { Database } from '../lib/database.types'
|
||||
|
||||
type SupabaseContext = {
|
||||
supabase: SupabaseClient<Database>
|
||||
}
|
||||
|
||||
const Context = createContext<SupabaseContext>()
|
||||
|
||||
export default function SupabaseProvider({ children }: { children: React.ReactNode }) {
|
||||
const [supabase] = useState(() => createClient())
|
||||
|
||||
return (
|
||||
<Context.Provider value={{ supabase }}>
|
||||
<>{children}</>
|
||||
</Context.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export const useSupabase = () => useContext(Context)
|
||||
```
|
||||
|
||||
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createBrowserSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
We need to set up a listener to fetch fresh data whenever our user logs in or out. For this we need to check whether our client and server sessions match. Let's start by installing the `server-only` package.
|
||||
|
||||
```bash
|
||||
npm install server-only
|
||||
```
|
||||
|
||||
This will ensure that any component that imports this package will be a Server Component, and excluded from the browser bundle.
|
||||
|
||||
Next, let's modify our root layout to fetch the user's session, wrap our application in our Supabase Provider, and pass the server access token as a prop to the `<SupabaseListener />` component (we will create this next).
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx title=app/layout.jsx
|
||||
import 'server-only'
|
||||
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import SupabaseProvider from '../components/supabase-provider'
|
||||
import './globals.css'
|
||||
import { createClient } from '../utils/supabase-server'
|
||||
|
||||
// do not cache this layout
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
{/*
|
||||
<head /> will contain the components returned by the nearest parent
|
||||
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
|
||||
*/}
|
||||
<head />
|
||||
<body>
|
||||
<SupabaseProvider>
|
||||
<SupabaseListener serverAccessToken={session?.access_token} />
|
||||
{children}
|
||||
</SupabaseProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx title=app/layout.tsx
|
||||
import 'server-only'
|
||||
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import SupabaseProvider from '../components/supabase-provider'
|
||||
import './globals.css'
|
||||
import { createClient } from '../utils/supabase-server'
|
||||
|
||||
// do not cache this layout
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
{/*
|
||||
<head /> will contain the components returned by the nearest parent
|
||||
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
|
||||
*/}
|
||||
<head />
|
||||
<body>
|
||||
<SupabaseProvider>
|
||||
<SupabaseListener serverAccessToken={session?.access_token} />
|
||||
{children}
|
||||
</SupabaseProvider>
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
And now create our Supabase listener component that uses the singleton Supabase instance to listen for auth changes.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx title="/components/supabase-listener.jsx"
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
import supabase from '../utils/supabase-browser'
|
||||
import { useSupabase } from './supabase-provider'
|
||||
|
||||
export default function SupabaseListener({ accessToken }) {
|
||||
export default function SupabaseListener({ serverAccessToken }) {
|
||||
const { supabase } = useSupabase()
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
const { data: subscription } = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== accessToken) {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== serverAccessToken) {
|
||||
router.refresh()
|
||||
}
|
||||
})
|
||||
|
||||
return () => subscription.unsubscribe()
|
||||
}, [accessToken])
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}, [serverAccessToken, router, supabase])
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
|
||||
|
||||
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
|
||||
|
||||
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
|
||||
|
||||
```jsx title="/app/layout.jsx"
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import createClient from '../utils/supabase-server'
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
// html and head section omitted
|
||||
<body>
|
||||
<SupabaseListener accessToken={session?.access_token} />
|
||||
{children}
|
||||
</body>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0` in the layout file.
|
||||
|
||||
```jsx
|
||||
export const revalidate = 0
|
||||
```
|
||||
|
||||
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
|
||||
|
||||
```bash
|
||||
npm install server-only
|
||||
```
|
||||
|
||||
And importing it at the top of our component.
|
||||
|
||||
```jsx
|
||||
import 'server-only'
|
||||
```
|
||||
|
||||
The entire Layout component should look something like this:
|
||||
|
||||
```jsx title="/app/layout.jsx"
|
||||
import 'server-only'
|
||||
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import './globals.css'
|
||||
import createClient from '../utils/supabase-server'
|
||||
|
||||
// do not cache this layout
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function RootLayout({ children }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
{/*
|
||||
<head /> will contain the components returned by the nearest parent
|
||||
head.jsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
|
||||
*/}
|
||||
<head />
|
||||
<body>
|
||||
<SupabaseListener accessToken={session?.access_token} />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
Create a `/components/supabase-listener.tsx` file and add the following:
|
||||
|
||||
```tsx title="/components/supabase-listener.tsx"
|
||||
'use client'
|
||||
|
||||
import { useRouter } from 'next/navigation'
|
||||
import { useEffect } from 'react'
|
||||
import supabase from '../utils/supabase-browser'
|
||||
import { useSupabase } from './supabase-provider'
|
||||
|
||||
export default function SupabaseListener({ accessToken }: { accessToken?: string }) {
|
||||
export default function SupabaseListener({ serverAccessToken }: { serverAccessToken?: string }) {
|
||||
const { supabase } = useSupabase()
|
||||
const router = useRouter()
|
||||
|
||||
useEffect(() => {
|
||||
supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== accessToken) {
|
||||
const {
|
||||
data: { subscription },
|
||||
} = supabase.auth.onAuthStateChange((event, session) => {
|
||||
if (session?.access_token !== serverAccessToken) {
|
||||
router.refresh()
|
||||
}
|
||||
})
|
||||
}, [accessToken])
|
||||
|
||||
return () => {
|
||||
subscription.unsubscribe()
|
||||
}
|
||||
}, [serverAccessToken, router, supabase])
|
||||
|
||||
return null
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
> `use client` tells Next.js that this is a [Client Component](https://beta.nextjs.org/docs/rendering/server-and-client-components#client-components). Only Client Components can use hooks like `useEffect` and `useRouter`.
|
||||
|
||||
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `accessToken` prop, which will be the server's state for our user (we'll set this up next). If the `accessToken` from the server and the new `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
|
||||
The function we pass to `onAuthStateChange` is automatically called by Supabase whenever a user's session changes. This component takes an `serverAccessToken` prop, which is the server's state for our user. If the `serverAccessToken` and the new session's `access_token` do not match then the client and server are out of sync, therefore, we want to reload the active route.
|
||||
|
||||
Lastly, fetch the server-side session in the `RootLayout` and pass it to our new `<SupabaseListener />` component.
|
||||
|
||||
```tsx title="/app/layout.tsx"
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import createClient from '../utils/supabase-server'
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
// html and head section omitted
|
||||
<body>
|
||||
<SupabaseListener accessToken={session?.access_token} />
|
||||
{children}
|
||||
</body>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
> Again, TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerComponentSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
We don't want Next.js to cache this `session` value, so we need to export a `revalidate` value of `0`.
|
||||
|
||||
```tsx
|
||||
export const revalidate = 0
|
||||
```
|
||||
|
||||
We also want to tell Next.js to explicitly exclude this component's code from the client bundle by installing the `server-only` package:
|
||||
|
||||
```bash
|
||||
npm install server-only
|
||||
```
|
||||
|
||||
And importing it at the top of our component.
|
||||
|
||||
```tsx
|
||||
import 'server-only'
|
||||
```
|
||||
|
||||
The entire Layout component should look something like this:
|
||||
|
||||
```tsx title="/app/layout.tsx"
|
||||
import 'server-only'
|
||||
|
||||
import SupabaseListener from '../components/supabase-listener'
|
||||
import './globals.css'
|
||||
import createClient from '../utils/supabase-server'
|
||||
|
||||
// do not cache this layout
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function RootLayout({ children }: { children: React.ReactNode }) {
|
||||
const supabase = createClient()
|
||||
|
||||
const {
|
||||
data: { session },
|
||||
} = await supabase.auth.getSession()
|
||||
|
||||
return (
|
||||
<html lang="en">
|
||||
{/*
|
||||
<head /> will contain the components returned by the nearest parent
|
||||
head.tsx. Find out more at https://beta.nextjs.org/docs/api-reference/file-conventions/head
|
||||
*/}
|
||||
<head />
|
||||
<body>
|
||||
<SupabaseListener accessToken={session?.access_token} />
|
||||
{children}
|
||||
</body>
|
||||
</html>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
Supabase can now be used in any Client or Server component.
|
||||
|
||||
## Server Components
|
||||
|
||||
Next.js recommends fetching data in Server Components whenever possible.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<TabPanel id="js" label="JavaScript">
|
||||
|
||||
```jsx
|
||||
import 'server-only'
|
||||
|
||||
import createClient from '../../utils/supabase-server'
|
||||
|
||||
// do not cache this page
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function ServerComponent() {
|
||||
const supabase = createClient()
|
||||
const { data } = await supabase.from('posts').select('*')
|
||||
|
||||
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
|
||||
<TabPanel id="ts" label="TypeScript">
|
||||
|
||||
```tsx
|
||||
import 'server-only'
|
||||
|
||||
import createClient from '../../utils/supabase-server'
|
||||
|
||||
// do not cache this page
|
||||
export const revalidate = 0
|
||||
|
||||
export default async function ServerComponent() {
|
||||
const supabase = createClient()
|
||||
const { data } = await supabase.from('posts').select('*')
|
||||
|
||||
return <pre>{JSON.stringify({ data }, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## Client Components
|
||||
|
||||
While Next.js recommend doing all data fetching in Server Components, we still need Supabase client-side for things like authentication and subscribing to realtime updates.
|
||||
Now we can use our `useSupabase` hook throughout our client-side components.
|
||||
|
||||
### Authentication
|
||||
|
||||
We can call any of [Supabase's authentication methods](https://supabase.com/docs/reference/javascript/v1/auth-signin) - such as `supabase.auth.signInWithOAuth` - from a client component.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
@@ -498,30 +493,33 @@ We can call any of [Supabase's authentication methods](https://supabase.com/docs
|
||||
```jsx title="/components/login.jsx"
|
||||
'use client'
|
||||
|
||||
import supabase from '../utils/supabase-browser'
|
||||
import { useSupabase } from './supabase-provider'
|
||||
|
||||
// Supabase auth needs to be triggered client-side
|
||||
export default function Login() {
|
||||
const handleLogin = async () => {
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
const { supabase, session } = useSupabase()
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'jon@supabase.com',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
|
||||
const handleGitHubLogin = async () => {
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
|
||||
if (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
await supabase.auth.signOut()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleLogin}>Login</button>
|
||||
<button onClick={handleEmailLogin}>Email Login</button>
|
||||
<button onClick={handleGitHubLogin}>GitHub Login</button>
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</>
|
||||
)
|
||||
@@ -535,30 +533,33 @@ export default function Login() {
|
||||
```tsx title="/components/login.tsx"
|
||||
'use client'
|
||||
|
||||
import supabase from '../utils/supabase-browser'
|
||||
import { useSupabase } from './supabase-provider'
|
||||
|
||||
// Supabase auth needs to be triggered client-side
|
||||
export default function Login() {
|
||||
const handleLogin = async () => {
|
||||
const { error } = await supabase.auth.signInWithOAuth({
|
||||
const { supabase, session } = useSupabase()
|
||||
|
||||
const handleEmailLogin = async () => {
|
||||
await supabase.auth.signInWithPassword({
|
||||
email: 'jon@supabase.com',
|
||||
password: 'password',
|
||||
})
|
||||
}
|
||||
|
||||
const handleGitHubLogin = async () => {
|
||||
await supabase.auth.signInWithOAuth({
|
||||
provider: 'github',
|
||||
})
|
||||
|
||||
if (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
}
|
||||
|
||||
const handleLogout = async () => {
|
||||
const { error } = await supabase.auth.signOut()
|
||||
|
||||
if (error) {
|
||||
console.log({ error })
|
||||
}
|
||||
await supabase.auth.signOut()
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
<button onClick={handleLogin}>Login</button>
|
||||
<button onClick={handleEmailLogin}>Email Login</button>
|
||||
<button onClick={handleGitHubLogin}>GitHub Login</button>
|
||||
<button onClick={handleLogout}>Logout</button>
|
||||
</>
|
||||
)
|
||||
@@ -588,10 +589,11 @@ Create a new file at `/app/realtime/posts.jsx` and populate with the following:
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import supabase from '../../utils/supabase-browser'
|
||||
import { useSupabase } from '../../components/supabase-provider'
|
||||
|
||||
export default function Posts({ serverPosts }) {
|
||||
const [posts, setPosts] = useState(serverPosts)
|
||||
const { supabase } = useSupabase()
|
||||
|
||||
useEffect(() => {
|
||||
setPosts(serverPosts)
|
||||
@@ -608,14 +610,12 @@ export default function Posts({ serverPosts }) {
|
||||
return () => {
|
||||
supabase.removeChannel(channel)
|
||||
}
|
||||
}, [serverPosts])
|
||||
}, [supabase, setPosts, posts])
|
||||
|
||||
return <pre>{JSON.stringify(posts, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
|
||||
|
||||
This can now be used in a Server Component to subscribe to realtime updates.
|
||||
|
||||
Create a new file at `/app/realtime/page.jsx` and populate with the following:
|
||||
@@ -623,7 +623,7 @@ Create a new file at `/app/realtime/page.jsx` and populate with the following:
|
||||
```jsx title="/app/realtime/page.jsx"
|
||||
import 'server-only'
|
||||
|
||||
import createClient from '../../utils/supabase-server'
|
||||
import { createClient } from '../../utils/supabase-server'
|
||||
import Posts from './posts'
|
||||
|
||||
// do not cache this page
|
||||
@@ -647,13 +647,15 @@ Create a new file at `/app/realtime/posts.tsx` and populate with the following:
|
||||
'use client'
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { Database } from '../lib/database.types'
|
||||
import supabase from '../../utils/supabase-browser'
|
||||
import { useSupabase } from '../../components/supabase-provider'
|
||||
|
||||
import type { Database } from '../../lib/database.types'
|
||||
|
||||
type Post = Database['public']['Tables']['posts']['Row']
|
||||
|
||||
export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
|
||||
const [posts, setPosts] = useState(serverPosts)
|
||||
const { supabase } = useSupabase()
|
||||
|
||||
useEffect(() => {
|
||||
setPosts(serverPosts)
|
||||
@@ -670,13 +672,13 @@ export default function Posts({ serverPosts }: { serverPosts: Post[] }) {
|
||||
return () => {
|
||||
supabase.removeChannel(channel)
|
||||
}
|
||||
}, [serverPosts])
|
||||
}, [supabase, setPosts, posts])
|
||||
|
||||
return <pre>{JSON.stringify(posts, null, 2)}</pre>
|
||||
}
|
||||
```
|
||||
|
||||
> The first `useEffect` is required to overwrite posts if the `serverPosts` prop changes.
|
||||
> TypeScript types can be [generated with the Supabase CLI](https://supabase.com/docs/reference/javascript/typescript-support) and passed to `createServerSupabaseClient` to add type support to the Supabase client.
|
||||
|
||||
This can now be used in a Server Component to subscribe to realtime updates.
|
||||
|
||||
@@ -685,7 +687,7 @@ Create a new file at `/app/realtime/page.tsx` and populate with the following:
|
||||
```tsx title="/app/realtime/page.tsx"
|
||||
import 'server-only'
|
||||
|
||||
import createClient from '../../utils/supabase-server'
|
||||
import { createClient } from '../../utils/supabase-server'
|
||||
import Posts from './posts'
|
||||
|
||||
// do not cache this page
|
||||
|
||||
@@ -584,7 +584,7 @@ export async function middleware(req: NextRequest) {
|
||||
}
|
||||
|
||||
export const config = {
|
||||
matcher: '/middleware-protected',
|
||||
matcher: '/middleware-protected/:path*',
|
||||
}
|
||||
```
|
||||
|
||||
@@ -782,7 +782,7 @@ export const config = {
|
||||
|
||||
### Migrating to v0.4.X and supabase-js v2
|
||||
|
||||
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/upgrade-guide) for the full set of changes within supabase-js.
|
||||
With the update to `supabase-js` v2 the `auth` API routes are no longer required, therefore you can go ahead and delete your `auth` directory under the `/pages/api/` directory. Please refer to the [v2 migration guide](/docs/reference/javascript/v1/upgrade-guide) for the full set of changes within supabase-js.
|
||||
|
||||
The `/api/auth/logout` API route has been removed, please use the `signout` method instead:
|
||||
|
||||
|
||||
@@ -12,9 +12,7 @@ Even though Supabase provides an `auth.users` table, it can be helpful to create
|
||||
|
||||
## Creating user tables
|
||||
|
||||
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key. This ensures data integrity.
|
||||
|
||||
For example, a `public.profiles` table might look like this:
|
||||
When you create tables to store user data, it's helpful to reference the `auth.users` table in the primary key to ensure data integrity. Also specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users. For example, a `public.profiles` table might look like this:
|
||||
|
||||
```sql
|
||||
create table public.profiles (
|
||||
@@ -28,9 +26,11 @@ create table public.profiles (
|
||||
alter table public.profiles enable row level security;
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
<Admonition type="caution">
|
||||
|
||||
Make sure to specify the `on delete cascade` clause when referencing `auth.users`. Omitting it may cause problems when deleting users.
|
||||
Only use primary keys as [foreign key references](https://www.postgresql.org/docs/current/tutorial-fk.html) for schemas and tables like `auth.users` which are managed by Supabase. PostgreSQL lets you specify a foreign key reference for columns backed by a unique index (not necessarily primary keys).
|
||||
|
||||
Primary keys are **guaranteed not to change**. Columns, indices, constraints or other database objects managed by Supabase **may change at any time** and you should be careful when referencing them directly.
|
||||
|
||||
</Admonition>
|
||||
|
||||
|
||||
@@ -74,14 +74,12 @@ When your user signs in, call [signInWithOAuth()](/docs/reference/javascript/aut
|
||||
|
||||
```js
|
||||
async function signInWithAzure() {
|
||||
const { data, error } = await supabase.auth.signInWithOAuth(
|
||||
{
|
||||
provider: 'azure',
|
||||
},
|
||||
{
|
||||
const { data, error } = await supabase.auth.signInWithOAuth({
|
||||
provider: 'azure',
|
||||
options: {
|
||||
scopes: 'email',
|
||||
}
|
||||
)
|
||||
},
|
||||
})
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@@ -1,12 +1,9 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'cli',
|
||||
title: 'Supabase CLI',
|
||||
description:
|
||||
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
|
||||
sidebar_label: 'Overview',
|
||||
toc_max_heading_level: 2,
|
||||
}
|
||||
|
||||
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
|
||||
@@ -113,7 +110,8 @@ brew upgrade supabase
|
||||
|
||||
## See also
|
||||
|
||||
- [Supabase CLI Reference](/docs/reference/cli/usage)
|
||||
- [Supabase CLI Reference](/docs/reference/cli/introduction)
|
||||
- [Supabase CLI Configuration](/docs/reference/cli/config)
|
||||
- [Local Development](/docs/guides/cli/local-development)
|
||||
- [Managing Environments](/docs/guides/cli/managing-environments)
|
||||
|
||||
+1
-2
@@ -34,9 +34,8 @@ supabase login
|
||||
<Admonition type="note">
|
||||
|
||||
If you installed the Supabase CLI via NPM you may have to use `npx supabase login`.
|
||||
|
||||
</Admonition>
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Initialize your project
|
||||
|
||||
-6
@@ -215,8 +215,6 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: 1.0.0
|
||||
|
||||
- name: Start Supabase local development setup
|
||||
run: supabase start
|
||||
@@ -256,8 +254,6 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: 1.0.0
|
||||
|
||||
- run: |
|
||||
supabase link --project-ref $STAGING_PROJECT_ID
|
||||
@@ -289,8 +285,6 @@ jobs:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: 1.0.0
|
||||
|
||||
- run: |
|
||||
supabase link --project-ref $PRODUCTION_PROJECT_ID
|
||||
@@ -217,7 +217,7 @@ import { createClient, useQuery } from 'urql'
|
||||
// Prepare API key and Authorization header
|
||||
const headers = {
|
||||
apikey: <SUPABASE_ANON_KEY>,
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY}`>
|
||||
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
|
||||
}
|
||||
|
||||
// Create GraphQL client
|
||||
|
||||
@@ -8,6 +8,15 @@ export const meta = {
|
||||
|
||||
Supabase APIs are generated from your database, which means that we can use database introspection to generate type-safe API definitions.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/7CqlTU9aOR4"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Generating types using Supabase CLI
|
||||
|
||||
The Supabase CLI is a single binary Go application that provides everything you need to setup a local development environment.
|
||||
|
||||
@@ -63,6 +63,8 @@ The schedule uses the standard cron syntax, in which \* means "run every time pe
|
||||
* * * * *
|
||||
```
|
||||
|
||||
You can use [crontab.guru](https://crontab.guru/) to help validate your cron schedules.
|
||||
|
||||
## Examples
|
||||
|
||||
### Delete data every week
|
||||
@@ -71,8 +73,8 @@ Delete old data on Saturday at 3:30am (GMT):
|
||||
|
||||
```sql
|
||||
select cron.schedule (
|
||||
'webhook-every-minute', -- name of the cron job
|
||||
'* * * * *', -- every minute
|
||||
'saturday-cleanup', -- name of the cron job
|
||||
'30 3 * * 6', -- Saturday at 3:30am (GMT)
|
||||
$$ delete from events where event_time < now() - interval '1 week' $$
|
||||
);
|
||||
```
|
||||
@@ -85,6 +87,26 @@ Vacuum every day at 3:00am (GMT)
|
||||
SELECT cron.schedule('nightly-vacuum', '0 3 * * *', 'VACUUM');
|
||||
```
|
||||
|
||||
### Invoke Supabase Edge Function every minute
|
||||
|
||||
Make a POST request to a Supabase Edge Function every minute. Note: this requires the [`pg_net` extension](/docs/guides/database/extensions/pgnet) to be enabled.
|
||||
|
||||
```sql
|
||||
select
|
||||
cron.schedule(
|
||||
'invoke-function-every-minute',
|
||||
'* * * * *', -- every minute
|
||||
$$
|
||||
select
|
||||
net.http_post(
|
||||
url:='https://project-ref.functions.supabase.co/function-name',
|
||||
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
|
||||
body:=concat('{"time": "', now(), '"}')::jsonb
|
||||
) as request_id;
|
||||
$$
|
||||
);
|
||||
```
|
||||
|
||||
### Unschedule a job
|
||||
|
||||
Unschedules a job called `'nightly-vacuum'`
|
||||
|
||||
@@ -53,11 +53,11 @@ Procedural languages are automatically installed within `pg_catalog`, so you don
|
||||
</TabPanel>
|
||||
</Tabs>
|
||||
|
||||
## `http_get` [#http_get]
|
||||
## `http_get`
|
||||
|
||||
Creates an HTTP GET request returning the request's ID. HTTP requests are not started until the transaction is committed.
|
||||
|
||||
### Signature
|
||||
### Signature [#get-signature]
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
@@ -86,7 +86,7 @@ net.http_get(
|
||||
language plpgsql
|
||||
```
|
||||
|
||||
### Usage
|
||||
### Usage [#get-usage]
|
||||
|
||||
```sql
|
||||
select net.http_get('https://news.ycombinator.com') as request_id;
|
||||
@@ -98,13 +98,13 @@ request_id
|
||||
|
||||
After triggering `http_get`, use [`http_get_result`](#http_get_result) to get the result of the request.
|
||||
|
||||
## `http_post` [#http_post]
|
||||
## `http_post`
|
||||
|
||||
Creates an HTTP POST request with a JSON body, returning the request's ID. HTTP requests are not started until the transaction is committed.
|
||||
|
||||
The body's character set encoding matches the database's `server_encoding` setting.
|
||||
|
||||
### Signature
|
||||
### Signature [#post-signature]
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
@@ -134,7 +134,7 @@ net.http_post(
|
||||
language plpgsql
|
||||
```
|
||||
|
||||
### Usage
|
||||
### Usage [#post-usage]
|
||||
|
||||
```sql
|
||||
select
|
||||
@@ -150,6 +150,21 @@ request_id
|
||||
|
||||
After triggering `http_post`, use [`http_get_result`](#http_get_result) to get the result of the request.
|
||||
|
||||
## Examples
|
||||
|
||||
### Invoke a Supabase Edge Function
|
||||
|
||||
Make a POST request to a Supabase Edge Function with auth header and JSON body payload:
|
||||
|
||||
```sql
|
||||
select
|
||||
net.http_post(
|
||||
url:='https://project-ref.functions.supabase.co/function-name',
|
||||
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
|
||||
body:='{"name": "pg_net"}'::jsonb
|
||||
) as request_id;
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- Source code: [github.com/supabase/pg_net](https://github.com/supabase/pg_net/)
|
||||
|
||||
@@ -8,6 +8,15 @@ export const meta = {
|
||||
|
||||
Postgres has built-in functions to handle `Full Text Search` queries. This is like a "search engine" within Postgres.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/b-mgca_2Oe4"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Preparation
|
||||
|
||||
For this guide we'll use the following example data:
|
||||
|
||||
@@ -77,7 +77,7 @@ You must define the "data type" when you create a column.
|
||||
### Data types
|
||||
|
||||
Every column is a predefined type. PostgreSQL provides many [default types](https://www.postgresql.org/docs/current/datatype.html), and you can even design your own (or use extensions)
|
||||
if the default types don't fit your needs.
|
||||
if the default types don't fit your needs. You can use any data type that Postgres supports via the SQL editor. We only support a subset of these in the Table Editor in an effort to keep the experience simple for people with less experience with databases.
|
||||
|
||||
<details>
|
||||
<summary>Show/Hide default data types</summary>
|
||||
|
||||
@@ -35,6 +35,15 @@ jobs:
|
||||
- run: supabase functions deploy your-function-name --project-ref $PROJECT_ID
|
||||
```
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/.github/workflows/deploy.yaml).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
@@ -0,0 +1,59 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'functions-cors',
|
||||
title: 'CORS (Cross-Origin Resource Sharing)',
|
||||
description: 'Add CORS headers to invoke functions from the browser.',
|
||||
}
|
||||
|
||||
To invoke the functions from the browser, you need to handle [CORS Preflight](https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request) requests.
|
||||
|
||||
See the [example on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/browser-with-cors/index.ts).
|
||||
|
||||
### Recommended setup
|
||||
|
||||
We recommend adding a `corst.ts` file within a [`_shared` folder](/docs/guides/functions/quickstart#organizing-your-edge-functions) which makes it easy to reuse the CORS headers across functions:
|
||||
|
||||
```ts cors.ts
|
||||
export const corsHeaders = {
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Allow-Headers': 'authorization, x-client-info, apikey, content-type',
|
||||
}
|
||||
```
|
||||
|
||||
You can then import and use the CORS headers within your functions:
|
||||
|
||||
```ts index.ts
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
|
||||
console.log(`Function "browser-with-cors" up and running!`)
|
||||
|
||||
serve(async (req) => {
|
||||
// This is needed if you're planning to invoke your function from a browser.
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
try {
|
||||
const { name } = await req.json()
|
||||
const data = {
|
||||
message: `Hello ${name}!`,
|
||||
}
|
||||
|
||||
return new Response(JSON.stringify(data), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 200,
|
||||
})
|
||||
} catch (error) {
|
||||
return new Response(JSON.stringify({ error: error.message }), {
|
||||
headers: { ...corsHeaders, 'Content-Type': 'application/json' },
|
||||
status: 400,
|
||||
})
|
||||
}
|
||||
})
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,20 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'functions-debugging',
|
||||
title: 'Debugging Functions',
|
||||
description: 'Debug functions in production.',
|
||||
}
|
||||
|
||||
You can debug your deployed Edge Functions using the ["Functions" section](https://app.supabase.com/project/_/functions) of the Dashboard. There are two debugging tools available:
|
||||
|
||||
- Invocations: shows the Request and Response for each execution.
|
||||
- Logs: shows any platform events, including deployments and errors.
|
||||
|
||||

|
||||
|
||||
When [developing locally](/docs/guides/functions/local-development) you will see error messages and console log statements printed to your local terminal window.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,96 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'examples-cloudflare-turnstile',
|
||||
title: 'Cloudflare Turnstile',
|
||||
description: 'Protecting Forms with Cloudflare Turnstile.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/OwW0znboh60"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
[Clouflare Turnstile](https://www.cloudflare.com/products/turnstile/) is a friendly, free CAPTCHA replacement, and it works seamlessly with Supabase Edge Functions to protect your forms. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/cloudflare-turnstile).
|
||||
|
||||
## Setup
|
||||
|
||||
- Follow these steps to set up a new site: https://developers.cloudflare.com/turnstile/get-started/
|
||||
- Add the Cloudflare Turnstile widget to your site: https://developers.cloudflare.com/turnstile/get-started/client-side-rendering/
|
||||
|
||||
## Code
|
||||
|
||||
Create a new function in your project:
|
||||
|
||||
```bash
|
||||
supabase functions new cloudflare-turnstile
|
||||
```
|
||||
|
||||
And add the code to the `index.ts` file:
|
||||
|
||||
```ts index.ts
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import { corsHeaders } from '../_shared/cors.ts'
|
||||
|
||||
console.log('Hello from Cloudflare Trunstile!')
|
||||
|
||||
function ips(req: Request) {
|
||||
return req.headers.get('x-forwarded-for')?.split(/\s*,\s*/)
|
||||
}
|
||||
|
||||
serve(async (req) => {
|
||||
// This is needed if you're planning to invoke your function from a browser.
|
||||
if (req.method === 'OPTIONS') {
|
||||
return new Response('ok', { headers: corsHeaders })
|
||||
}
|
||||
|
||||
const { token } = await req.json()
|
||||
const clientIps = ips(req) || ['']
|
||||
const ip = clientIps[0]
|
||||
|
||||
// Validate the token by calling the
|
||||
// "/siteverify" API endpoint.
|
||||
let formData = new FormData()
|
||||
formData.append('secret', Deno.env.get('CLOUDFLARE_SECRET_KEY') ?? '')
|
||||
formData.append('response', token)
|
||||
formData.append('remoteip', ip)
|
||||
|
||||
const url = 'https://challenges.cloudflare.com/turnstile/v0/siteverify'
|
||||
const result = await fetch(url, {
|
||||
body: formData,
|
||||
method: 'POST',
|
||||
})
|
||||
|
||||
const outcome = await result.json()
|
||||
console.log(outcome)
|
||||
if (outcome.success) {
|
||||
return new Response('success', { headers: corsHeaders })
|
||||
}
|
||||
return new Response('failure', { headers: corsHeaders })
|
||||
})
|
||||
```
|
||||
|
||||
## Deploy the server-side validation Edge Functions
|
||||
|
||||
- https://developers.cloudflare.com/turnstile/get-started/server-side-validation/
|
||||
|
||||
```bash
|
||||
supabase functions deploy cloudflare-turnstile
|
||||
supabase secrets set CLOUDFLARE_TURNSTILE_SECRET_KEY=your_secret_key
|
||||
```
|
||||
|
||||
## Invoke the function from your site
|
||||
|
||||
```js
|
||||
const { data, error } = await supabase.functions.invoke('cloudflare-turnstile', {
|
||||
body: { token },
|
||||
})
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,49 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'examples-github-actions',
|
||||
title: 'GitHub Actions',
|
||||
description: 'Deploying Edge Functions with GitHub Actions.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Use the Supabase CLI together with GitHub Actions to automatically deploy our Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/github-action-deploy).
|
||||
|
||||
```yaml deploy.yaml
|
||||
name: Deploy Function
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- main
|
||||
workflow_dispatch:
|
||||
|
||||
jobs:
|
||||
deploy:
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
env:
|
||||
SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
|
||||
PROJECT_ID: zdtdtxajzydjqzuktnqx
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
|
||||
- uses: supabase/setup-cli@v1
|
||||
with:
|
||||
version: 1.0.0
|
||||
|
||||
- run: supabase functions deploy github-action-deploy --project-ref $PROJECT_ID
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -17,6 +17,10 @@ export const meta = {
|
||||
|
||||
Generate Open Graph images with Deno and Supabase Edge Functions. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/opengraph).
|
||||
|
||||
## Code
|
||||
|
||||
Create a `handler.tsx` file to construct the OG image in React:
|
||||
|
||||
```tsx handler.tsx
|
||||
import React from 'https://esm.sh/react@18.2.0'
|
||||
import { ImageResponse } from 'https://deno.land/x/og_edge@0.0.4/mod.ts'
|
||||
@@ -42,6 +46,8 @@ export default function handler(req: Request) {
|
||||
}
|
||||
```
|
||||
|
||||
Create an `index.ts` file to execute the handler on incoming requests:
|
||||
|
||||
```ts index.ts
|
||||
import { serve } from 'https://deno.land/std@0.131.0/http/server.ts'
|
||||
import handler from './handler.tsx'
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'examples-storage-caching',
|
||||
title: 'Caching Images with Supabase Storage CDN',
|
||||
description: 'Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN).',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/wW6L52v9Ldo"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Integrate Edge Functions with Supabase Storage to cache images on the Edge (CDN). [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/og-image-with-storage-cdn).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,22 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'examples-stripe-webhooks',
|
||||
title: 'Handling Stripe Webhooks',
|
||||
description: 'Handling signed Stripe Webhooks with Edge Functions.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/6OMVWiiycLs"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Handling signed Stripe Webhooks with Edge Functions. [View on GitHub](https://github.com/supabase/supabase/blob/master/examples/edge-functions/supabase/functions/stripe-webhooks/index.ts).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,22 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'examples-telegram-bot',
|
||||
title: 'Telegram Bot',
|
||||
description: 'Building a Telegram Bot with Edge Functions.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/AWfE3a9J_uo"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Handle Telegram Bot Webhooks with the [grammY framework](https://grammy.dev/). grammY is an open source Telegram Bot Framework which makes it easy to handle and respond to incoming messages. [View on GitHub](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/telegram-bot).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,62 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'functions-import-maps',
|
||||
title: 'Import Maps',
|
||||
description: 'Using Import Maps.',
|
||||
}
|
||||
|
||||
<div class="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/ILr3cneZuFk"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
Since [Supabase CLI](https://supabase.com/docs/guides/resources/supabase-cli) version `1.33.0` [import maps](https://deno.land/manual@v1.29.2/node/import_maps) can be used with Supabase Edge Functions.
|
||||
|
||||
[Import maps](https://github.com/WICG/import-maps#the-import-map) is a web-platform standard that allows you to use bare specifiers with Deno without having to install the Node.js package locally.
|
||||
|
||||
So if we want to do the following in our code:
|
||||
|
||||
```ts, ignore
|
||||
import lodash from "lodash";
|
||||
```
|
||||
|
||||
We can accomplish this using an import map, and we don't even have to install the `lodash` package locally. We would want to create a JSON file (for example **import_map.json**) with the following:
|
||||
|
||||
```json
|
||||
{
|
||||
"imports": {
|
||||
"lodash": "https://cdn.skypack.dev/lodash"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Import Map Placement
|
||||
|
||||
We recommend creating one `import_map.json` within the `/supabase/functions` folder (see [Organizing your Edge Functions](/docs/guides/functions/quickstart#organizing-your-edge-functions)), similar to a `package.json` file, to define imports that can be used across all of your project's functions.
|
||||
|
||||
Alternatively, you can create one `import_map.json` file in each function folder, which will take priority over a top-level file.
|
||||
|
||||
Lastly, you can override this default behaviour by providing the `--import-map <string>` flag to the `serve` and `deploy` commands.
|
||||
|
||||
## Visual Studio Code Configuration
|
||||
|
||||
In order for vscode to understand the imports correctly, you need to specify the `deno.importMap` flag in your `.vscode/settings.json` file:
|
||||
|
||||
```json settings.json
|
||||
{
|
||||
"deno.enable": true,
|
||||
"deno.unstable": true,
|
||||
"deno.importMap": "./supabase/functions/import_map.json"
|
||||
}
|
||||
```
|
||||
|
||||
For a full guide on developing with Deno in Visual Studio Code, see [this guide](https://deno.land/manual@v1.25.4/vscode_deno).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,60 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'functions-local-development',
|
||||
title: 'Local Development',
|
||||
description: 'Run your functions locally.',
|
||||
}
|
||||
|
||||
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve):
|
||||
|
||||
```bash
|
||||
supabase start # start the supabase stack
|
||||
supabase functions serve hello-world # start the Function watcher
|
||||
```
|
||||
|
||||
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
|
||||
|
||||
### Invoking functions locally
|
||||
|
||||
While serving your local Function, you can invoke it using curl:
|
||||
|
||||
```bash
|
||||
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "name":"Functions" }'
|
||||
```
|
||||
|
||||
or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke):
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Use the credentials outputted in your terminal when running `supabase start`
|
||||
const supabase = createClient(
|
||||
'http://localhost:54321',
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0'
|
||||
)
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('hello-world', {
|
||||
body: { name: 'Functions' },
|
||||
})
|
||||
```
|
||||
|
||||
You should see the response `{ "message":"Hello Functions!" }`.
|
||||
|
||||
If you execute Function with a different payload the response will change. <br />
|
||||
Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again!
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`).
|
||||
- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT.
|
||||
- The Function is proxied through the local API (`http://localhost:54321`)
|
||||
|
||||
</Admonition>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -3,7 +3,7 @@ import Layout from '~/layouts/DefaultGuideLayout'
|
||||
export const meta = {
|
||||
id: 'functions-quickstart',
|
||||
title: 'Edge Functions Quickstart',
|
||||
description: 'Globally distributed TypeScript functions.',
|
||||
description: 'Globally distributed TypeScript Functions.',
|
||||
sidebar_label: 'Quickstart',
|
||||
}
|
||||
|
||||
@@ -55,9 +55,9 @@ If you want to use Edge Functions to handle webhooks (e.g. [Stripe payment webho
|
||||
|
||||
</Admonition>
|
||||
|
||||
## Execute remote functions
|
||||
## Invoking remote functions
|
||||
|
||||
You can execute Edge Functions using curl. Copy the curl command from the Dashboard. It should look like this:
|
||||
You can invoke Edge Functions using curl:
|
||||
|
||||
```bash
|
||||
curl --request POST 'https://<project_ref>.functions.supabase.co/hello-world' \
|
||||
@@ -66,135 +66,61 @@ curl --request POST 'https://<project_ref>.functions.supabase.co/hello-world' \
|
||||
--data '{ "name":"Functions" }'
|
||||
```
|
||||
|
||||
<Admonition type="note">
|
||||
|
||||
If you receive an error `Invalid JWT`, find the `ANON_KEY` of your project in the Dashboard under `Settings > API`.
|
||||
|
||||
</Admonition>
|
||||
|
||||
or using one of the [client libraries](/docs#reference-documentation), e.g. using [supabase-js](/docs/reference/javascript/functions-invoke):
|
||||
|
||||
```js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
// Create a single supabase client for interacting with your database
|
||||
const supabase = createClient('https://xyzcompany.supabase.co', 'public-anon-key')
|
||||
|
||||
const { data, error } = await supabase.functions.invoke('hello-world', {
|
||||
body: { name: 'Functions' },
|
||||
})
|
||||
```
|
||||
|
||||
After invoking your Edge Function you should see the response `{ "message":"Hello Functions!" }`.
|
||||
|
||||
## Debug functions
|
||||
## Database Functions vs Edge Functions
|
||||
|
||||
You can debug your deployed Edge Functions using the "Functions" section of the Dashboard. There are two types debugging tools available:
|
||||
For data-intensive operations we recommend using [Database Functions](/docs/guides/database/functions), which are executed within your database
|
||||
and can be called remotely using the [REST and GraphQL API](/docs/guides/api).
|
||||
|
||||
- Invocations: shows the Request and Response for each execution.
|
||||
- Logs: shows any platform events, including deployments and errors.
|
||||
For use-cases which require low-latency we recommend [Edge Functions](/docs/guides/functions), which are globally-distributed and can be written in TypeScript.
|
||||
|
||||

|
||||
## Organizing your Edge Functions
|
||||
|
||||
## Develop locally
|
||||
|
||||
You can run your Edge Function locally using [`supabase functions serve`](/docs/reference/cli/usage#supabase-functions-serve):
|
||||
We recommend developing “fat functions”. This means that you should develop few large functions, rather than many small functions. One common pattern when developing Functions is that you need to share code between two or more Functions. To do this, you can store any shared code in a folder prefixed with an underscore (`_`). We recommend this folder structure:
|
||||
|
||||
```bash
|
||||
supabase start # start the supabase stack
|
||||
supabase functions serve hello-world # start the Function watcher
|
||||
└── supabase
|
||||
├── functions
|
||||
│ ├── import_map.json # A top-level import map to use across functions.
|
||||
│ ├── _shared
|
||||
│ │ ├── supabaseAdmin.ts # Supabase client with SERVICE_ROLE key.
|
||||
│ │ └── supabaseClient.ts # Supabase client with ANON key.
|
||||
│ │ └── cors.ts # Reusable CORS headers.
|
||||
│ ├── function-one # Use hyphens to name functions.
|
||||
│ │ └── index.ts
|
||||
│ └── function-two
|
||||
│ └── index.ts
|
||||
├── migrations
|
||||
└── config.toml
|
||||
```
|
||||
|
||||
The `functions serve` command has hot-reloading capabilities. It will watch for any changes to your files and restart the Deno server.
|
||||
## Naming Edge Functions
|
||||
|
||||
### Invoke functions locally
|
||||
We recommend using hyphens to name functions because hyphens are the most URL-friendly of all the naming conventions (snake_case, camelCase, PascalCase).
|
||||
|
||||
While serving your local Function, you can execute it using curl:
|
||||
## Using HTTP Methods
|
||||
|
||||
```bash
|
||||
curl --request POST 'http://localhost:54321/functions/v1/hello-world' \
|
||||
--header 'Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24ifQ.625_WdcF3KHqz5amU0x2X5WWHP-OEs_4qj0ssLNHzTs' \
|
||||
--header 'Content-Type: application/json' \
|
||||
--data '{ "name":"Functions" }'
|
||||
```
|
||||
|
||||
You should see the response `{ "message":"Hello Functions!" }`.
|
||||
|
||||
<details>
|
||||
<summary>Implementation details</summary>
|
||||
|
||||
- Edge Functions don't serve HTML content (`GET` requests that return `text/html` are rewritten to `text/plain`).
|
||||
- The `Authorization` header is required. You can use either the `ANON` key, the `SERVICE_ROLE` key, or a logged-in user's JWT.
|
||||
- The Function is proxied through the local API (`http://localhost:54321`)
|
||||
|
||||
</details>
|
||||
<br />
|
||||
|
||||
If you execute Function with a different payload the response will change. <br />
|
||||
Modify the `--data '{"name":"Functions"}'` line to `--data '{"name":"World"}'` and try invoking the command again!
|
||||
|
||||
## Secrets and Environment Variables
|
||||
|
||||
It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler
|
||||
|
||||
```js
|
||||
Deno.env.get(MY_SECRET_NAME)
|
||||
```
|
||||
|
||||
### Default secrets
|
||||
|
||||
By default, Edge Functions have access to these secrets:
|
||||
|
||||
- `SUPABASE_URL`: The API gateway for your Supabase project.
|
||||
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled.
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database.
|
||||
|
||||
### Local secrets
|
||||
|
||||
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
|
||||
|
||||
```jsx
|
||||
echo "MY_NAME=Yoda" >> ./supabase/.env.local
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env.local` for storing your local development secrets.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Never check your .env files into Git!
|
||||
|
||||
</Admonition>
|
||||
|
||||
Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line:
|
||||
|
||||
```jsx
|
||||
console.log(Deno.env.get('MY_NAME'))
|
||||
```
|
||||
|
||||
Now we can invoke our function locally, by serving it with our new `.env.local` file:
|
||||
|
||||
```bash
|
||||
supabase functions serve hello-world --env-file ./supabase/.env.local
|
||||
```
|
||||
|
||||
When the function starts you should see the name “Yoda” output to the terminal.
|
||||
|
||||
### Production secrets
|
||||
|
||||
Let's create a `.env` for production. In this case we'll just use the same as our local secrets:
|
||||
|
||||
```bash
|
||||
cp ./supabase/.env.local ./supabase/.env
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env` for storing your production secrets.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Never check your `.env` files into Git!
|
||||
|
||||
</Admonition>
|
||||
|
||||
Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set):
|
||||
|
||||
```bash
|
||||
supabase secrets set --env-file ./supabase/.env
|
||||
|
||||
# You can also set secrets individually using:
|
||||
supabase secrets set MY_NAME=Chewbacca
|
||||
```
|
||||
|
||||
You don't need to re-deploy after setting your secrets.
|
||||
|
||||
To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list):
|
||||
|
||||
```bash
|
||||
supabase secrets list
|
||||
```
|
||||
Edge Functions supports `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, and `OPTIONS`. A function can be designed to perform different actions based on a request's HTTP method. See the [example on building a RESTful service](https://github.com/supabase/supabase/tree/master/examples/edge-functions/supabase/functions/restful-tasks) to learn how to handle different HTTP methods in your function.
|
||||
|
||||
## Limitations
|
||||
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'schedule-functions',
|
||||
title: 'Schedule Functions',
|
||||
description: 'Schedule Functions with pg_cron.',
|
||||
}
|
||||
|
||||
The hosted Supabase Platform supports the [`pg_cron` extension](/docs/guides/database/extensions/pgcron), a simple cron-based job scheduler for PostgreSQL that runs inside the database.
|
||||
|
||||
In combination with the [`pg_net` extension](/docs/guides/database/extensions/pgnet), this allows us to invoke functions periodically on a set schedule.
|
||||
|
||||
## Examples
|
||||
|
||||
### Invoke Function every minute
|
||||
|
||||
Make a POST request to a Supabase Edge Function every minute:
|
||||
|
||||
```sql
|
||||
select
|
||||
cron.schedule(
|
||||
'invoke-function-every-minute',
|
||||
'* * * * *', -- every minute
|
||||
$$
|
||||
select
|
||||
net.http_post(
|
||||
url:='https://project-ref.functions.supabase.co/function-name',
|
||||
headers:='{"Content-Type": "application/json", "Authorization": "Bearer YOUR_ANON_KEY"}'::jsonb,
|
||||
body:=concat('{"time": "', now(), '"}')::jsonb
|
||||
) as request_id;
|
||||
$$
|
||||
);
|
||||
```
|
||||
|
||||
## Resources
|
||||
|
||||
- [`pg_net` extension](/docs/guides/database/extensions/pgnet)
|
||||
- [`pg_cron` extension](/docs/guides/database/extensions/pgcron)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,89 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'functions-secrets',
|
||||
title: 'Secrets and Environment Variables',
|
||||
description: 'Managing secrets and environment variables.',
|
||||
}
|
||||
|
||||
It's common that you will need to use sensitive information or environment-specific variables inside your Edge Functions. You can access these using Deno's built-in handler
|
||||
|
||||
```js
|
||||
Deno.env.get(MY_SECRET_NAME)
|
||||
```
|
||||
|
||||
### Default secrets
|
||||
|
||||
By default, Edge Functions have access to these secrets:
|
||||
|
||||
- `SUPABASE_URL`: The API gateway for your Supabase project.
|
||||
- `SUPABASE_ANON_KEY`: The `anon` key for your Supabase API. This is safe to use in a browser when you have [Row Level Security](/docs/guides/auth/row-level-security) enabled.
|
||||
- `SUPABASE_SERVICE_ROLE_KEY`: The `service_role` key for your Supabase API. This is safe to use in Edge Functions, but it should NEVER be used in a browser. This key will bypass [Row Level Security](/docs/guides/auth/row-level-security).
|
||||
- `SUPABASE_DB_URL`: The URL for your [PostgreSQL database](/docs/guides/database). You can use this to connect directly to your database.
|
||||
|
||||
### Local secrets
|
||||
|
||||
Let's create a local file for storing our secrets, and inside it we can store a secret `MY_NAME`:
|
||||
|
||||
```bash
|
||||
echo "MY_NAME=Yoda" >> ./supabase/.env.local
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env.local` for storing your local development secrets.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Never check your .env files into Git!
|
||||
|
||||
</Admonition>
|
||||
|
||||
Now let's access this environment variable `MY_NAME` inside our Function. Anywhere in your function, add this line:
|
||||
|
||||
```jsx
|
||||
console.log(Deno.env.get('MY_NAME'))
|
||||
```
|
||||
|
||||
Now we can invoke our function locally, by serving it with our new `.env.local` file:
|
||||
|
||||
```bash
|
||||
supabase functions serve hello-world --env-file ./supabase/.env.local
|
||||
```
|
||||
|
||||
When the function starts you should see the name “Yoda” output to the terminal.
|
||||
|
||||
### Production secrets
|
||||
|
||||
Let's create a `.env` for production. In this case we'll just use the same as our local secrets:
|
||||
|
||||
```bash
|
||||
cp ./supabase/.env.local ./supabase/.env
|
||||
```
|
||||
|
||||
This creates a new file `./supabase/.env` for storing your production secrets.
|
||||
|
||||
<Admonition type="caution">
|
||||
|
||||
Never check your `.env` files into Git!
|
||||
|
||||
</Admonition>
|
||||
|
||||
Let's push all the secrets from the `.env` file to our remote project using [`supabase secrets set`](/docs/reference/cli/usage#supabase-secrets-set):
|
||||
|
||||
```bash
|
||||
supabase secrets set --env-file ./supabase/.env
|
||||
|
||||
# You can also set secrets individually using:
|
||||
supabase secrets set MY_NAME=Chewbacca
|
||||
```
|
||||
|
||||
You don't need to re-deploy after setting your secrets.
|
||||
|
||||
To see all the secrets which you have set remotely, use [`supabase secrets list`](/docs/reference/cli/usage#supabase-secrets-list):
|
||||
|
||||
```bash
|
||||
supabase secrets list
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,140 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with SolidJS',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SolidJS app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a SolidJS app">
|
||||
|
||||
Create a SolidJS app using the `degit` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npx degit solidjs/templates/js my-app
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SolidJS app.
|
||||
|
||||
Navigate to the SolidJS app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
In `App.jsx`, create a Supabase client using your [Project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
Add a `getCountries` function to fetch the data and display the query result to the page.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```jsx src/App.jsx
|
||||
import { createClient } from "@supabase/supabase-js";
|
||||
import { createEffect, createSignal, For } from "solid-js";
|
||||
|
||||
const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>');
|
||||
|
||||
function App() {
|
||||
const [countries, setCountries] = createSignal();
|
||||
|
||||
createEffect(() => {
|
||||
getCountries();
|
||||
});
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
setCountries(data);
|
||||
}
|
||||
|
||||
return (
|
||||
<ul>
|
||||
<For each={countries()}>{(country) => <li>{country.name}</li>}</For>
|
||||
</ul>
|
||||
);
|
||||
}
|
||||
|
||||
export default App;
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:3000 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,166 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with SvelteKit',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a SvelteKit app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a SvelteKit app">
|
||||
|
||||
Create a SvelteKit app using the `npm create` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm create svelte@latest myapp
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a SvelteKit app.
|
||||
|
||||
Navigate to the SvelteKit app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Create the Supabase client">
|
||||
|
||||
Create a `/src/lib` directory in your SvelteKit app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js src/lib/supabaseClient.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Use `load` method to fetch the data server-side and display the query results as a simple list.
|
||||
|
||||
Create `+page.server.js` file in the `routes` directory with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```js src/routes/+page.server.js
|
||||
import { supabase } from "$lib/supabaseClient";
|
||||
|
||||
export async function load() {
|
||||
const { data } = await supabase.from("countries").select();
|
||||
return {
|
||||
countries: data ?? [],
|
||||
};
|
||||
}
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
<StepHikeCompact.Details title="">
|
||||
|
||||
Replace the existing content in your `+page.svelte` file in the `routes` directory with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```svelte src/routes/+page.svelte
|
||||
<script>
|
||||
export let data;
|
||||
let { countries } = data;
|
||||
$: ({ countries } = data);
|
||||
</script>
|
||||
|
||||
<ul>
|
||||
{#each countries as country}
|
||||
<li>{country.name}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -0,0 +1,153 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
import StepHikeCompact from '~/components/StepHikeCompact'
|
||||
|
||||
export const meta = {
|
||||
title: 'Use Supabase with Vue',
|
||||
subtitle:
|
||||
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Vue app.',
|
||||
breadcrumb: 'Framework Quickstarts',
|
||||
}
|
||||
|
||||
<StepHikeCompact>
|
||||
|
||||
<StepHikeCompact.Step step={1}>
|
||||
<StepHikeCompact.Details title="Set up a Supabase project with sample data">
|
||||
|
||||
[Create a new project](https://app.supabase.com) in the Supabase Dashboard.
|
||||
|
||||
After your project is ready, create a table in your Supabase database using the [SQL Editor](https://app.supabase.com/project/_/sql) in the Dashboard. Use the following SQL statement to create a `countries` table with some sample data.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sql SQL_EDITOR
|
||||
-- Create the table
|
||||
CREATE TABLE countries (
|
||||
id SERIAL PRIMARY KEY,
|
||||
name VARCHAR(255) NOT NULL
|
||||
);
|
||||
-- Insert some sample data into the table
|
||||
INSERT INTO countries (name) VALUES ('United States');
|
||||
INSERT INTO countries (name) VALUES ('Canada');
|
||||
INSERT INTO countries (name) VALUES ('Mexico');
|
||||
````
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={2}>
|
||||
|
||||
<StepHikeCompact.Details title="Create a Vue app">
|
||||
|
||||
Create a Vue app using the `npm init` command.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```sh Terminal
|
||||
npm init vue@latest my-app
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={3}>
|
||||
<StepHikeCompact.Details title="Install the Supabase client library">
|
||||
|
||||
The fastest way to get started is to use the `supabase-js` client library which provides a convenient interface for working with Supabase from a Vue app.
|
||||
|
||||
Navigate to the Vue app and install `supabase-js`.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
cd my-app && npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={4}>
|
||||
<StepHikeCompact.Details title="Create the Supabase client">
|
||||
|
||||
Create a `/src/lib` directory in your Vue app, create a file called `supabaseClient.js` and add the following code to initialize the Supabase client with your [project URL and public API (anon) key](https://app.supabase.com/project/_/settings/api).
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```js src/lib/supabaseClient.js
|
||||
import { createClient } from '@supabase/supabase-js'
|
||||
|
||||
export const supabase = createClient('https://<project>.supabase.co', '<your-anon-key>')
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={5}>
|
||||
<StepHikeCompact.Details title="Query data from the app">
|
||||
|
||||
Replace the existing content in your `App.vue` file with the following code.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
|
||||
```vue src/App.vue
|
||||
<script setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { supabase } from './lib/supabaseClient'
|
||||
|
||||
const countries = ref([])
|
||||
|
||||
async function getCountries() {
|
||||
const { data } = await supabase.from('countries').select()
|
||||
countries.value = data
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getCountries()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<ul>
|
||||
<li v-for="country in countries" :key="country.id">{{ country.name }}</li>
|
||||
</ul>
|
||||
</template>
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
|
||||
<StepHikeCompact.Step step={6}>
|
||||
<StepHikeCompact.Details title="Start the app">
|
||||
|
||||
Start the app and go to http://localhost:5173 in a browser and you should see the list of countries.
|
||||
|
||||
</StepHikeCompact.Details>
|
||||
|
||||
<StepHikeCompact.Code>
|
||||
|
||||
```bash Terminal
|
||||
npm run dev
|
||||
```
|
||||
|
||||
</StepHikeCompact.Code>
|
||||
|
||||
</StepHikeCompact.Step>
|
||||
</StepHikeCompact>
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
|
||||
|
||||
export default Page
|
||||
@@ -530,6 +530,10 @@ export class AccountComponent implements OnInit {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -500,6 +500,10 @@ Now you will need to run the prebuild command to get the application working on
|
||||
expo prebuild
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -793,6 +793,10 @@ class _AccountPageState extends State<AccountPage> {
|
||||
}
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
Congratulations, that is it! You have now built a fully functional user management app using Flutter and Supabase!
|
||||
|
||||
## See also
|
||||
|
||||
@@ -522,6 +522,10 @@ template: `
|
||||
`
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -495,6 +495,10 @@ return (
|
||||
<Avatar url={profile.avatar_url} onUpload={updateProfile}></Avatar>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -555,6 +555,10 @@ export default defineComponent({
|
||||
</script>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -718,6 +718,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -420,6 +420,10 @@ And then we can add the widget to the Account page:
|
||||
|
||||
That is it! You should now be able to upload a profile photo to Supabase Storage.
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -397,6 +397,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -771,6 +771,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -415,6 +415,10 @@ return (
|
||||
)
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -368,6 +368,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -434,6 +434,10 @@ And then we can add the widget to the Account page:
|
||||
</form>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -379,6 +379,10 @@ And then we can add the widget to the Account page:
|
||||
</template>
|
||||
```
|
||||
|
||||
### Storage management
|
||||
|
||||
<StorageManagement />
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
@@ -18,7 +18,7 @@ Once created, the pipeline backfills all your historical data from Firestore and
|
||||
|
||||
Before you begin, you'll need:
|
||||
|
||||
- An [Estuary account](https://go.estuary.dev/sign-up).
|
||||
- An Estuary account. [Head to the web app to start for free](https://dashboard.estuary.dev).
|
||||
|
||||
- For your Firestore database:
|
||||
|
||||
@@ -31,36 +31,35 @@ Before you begin, you'll need:
|
||||
|
||||
You'll start by creating a **capture**, a task in Flow that connects to your data source system: in this case, Firestore. This process will create one or more data **collections**, backed by a real-time data lake.
|
||||
|
||||
1. Go to the Flow web application at [dashboard.estuary.dev](http://dashboard.estuary.dev) and sign in using the credentials provided by your Estuary account manager.
|
||||
1. Go to the [**Captures** tab](https://dashboard.estuary.dev/captures) of the Flow web app and choose **New Capture**.
|
||||
|
||||
2. Click the **Captures** tab and choose **New Capture**.
|
||||
|
||||
3. Locate and select the **Google Firestore** card.
|
||||
2. Locate and select the **Google Firestore** card.
|
||||
|
||||
A form appears with the properties required for a Firestore capture.
|
||||
|
||||
4. Set a name for your capture.
|
||||
3. Set a name for your capture.
|
||||
|
||||
Click inside the **Name** field to generate a drop-down menu of available **prefixes** and select one (likely, this will be the name of your organization). Append a unique capture name after the `/` to create the full name, for example, `acmeCo/myFirestoreCapture`.
|
||||
|
||||
5. Fill out the required properties for Firestore.
|
||||
4. Fill out the required properties for Firestore.
|
||||
|
||||
- **Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
|
||||
- **Credentials**: The JSON service account key created per the prerequisites.
|
||||
**Database**: Flow can autodetect the database name, but you may optionally specify it here. This is helpful if the service account used has access to multiple Firebase projects. Your database name usually follows the format `projects/$PROJECTID/databases/(default)`.
|
||||
|
||||
**Credentials**: The JSON service account key created per the prerequisites.
|
||||
|
||||
6. Click **Discover Endpoint**.
|
||||
5. Click **Next**.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection with Firestore. It generates a capture specification and details of the collections that it will create, once published.
|
||||
Flow uses the provided configuration to initiate a connection with Firestore. It maps each collection in the Firestore database to a Flow collection.
|
||||
|
||||
7. Use the **Specification Editor** to view the [JSON schemas](https://docs.estuary.dev/concepts/schemas/) for each collection and make sure they are formatted correctly for your needs. If they're not, you can edit them.
|
||||
6. Optionally, use the **Collection Selector** to remove any collections you don't need to migrate to Supabase.
|
||||
|
||||
8. Click **Save and publish**.
|
||||
7. Click **Save and Publish**.
|
||||
|
||||
You'll see a notification when the capture publishes successfully.
|
||||
You'll see a notification when the capture publishes successfully.
|
||||
|
||||
The data currently in your Firestore database has been captured, and future updates to it will be captured continuously.
|
||||
The data currently in your Firestore database has been captured to Flow, and future updates to it will be captured continuously.
|
||||
|
||||
Click **Materialize Collections** to continue.
|
||||
Click **Materialize Collections** to continue.
|
||||
|
||||
## Step 2: Materialize your collections to Postgres
|
||||
|
||||
@@ -72,23 +71,31 @@ A form appears with the properties required for a Postgres materialization.
|
||||
|
||||
2. Choose a unique name for your materialization like you did when naming your capture; for example, `acmeCo/mySupabaseMaterialization`.
|
||||
|
||||
3. Fill out the required properties for PostgreSQL. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
|
||||
3. Fill out the required properties for Postgres. You can find most of these in Supabase by going to the **Settings** section and clicking **Database**.
|
||||
|
||||
- **Address**: Format at `<host>:<port>`.
|
||||
- **User**: Usually, this is `postgres`.
|
||||
- **Password**: The password you set when you created your Supabase project.
|
||||
**Address**: Format at `<host>:<port>`.
|
||||
|
||||
**User**: Usually, this is `postgres`.
|
||||
|
||||
**Password**: The password you set when you created your Supabase project.
|
||||
|
||||
4. Scroll down to view the **Collection Selector** and fill in the **Table** field for each collection.
|
||||
4. Click **Next**.
|
||||
|
||||
The collections you just created have already been selected, but you must provide names for the tables to which they'll be materialized.
|
||||
Flow initiates a connection with the database and the **Collection Selector** expands.
|
||||
It's populated with your collections from Firestore, each mapped to a Postgres table.
|
||||
|
||||
5. Click **Discover Endpoint**.
|
||||
5. For each collection, apply a stricter JSON schema.
|
||||
This ensure that the less-structured Firestore data will be written to a Postgres table in the correct shape.
|
||||
|
||||
Flow uses the provided configuration to initiate a connection to your Supabase Postgres database and generate a specification.
|
||||
In the Collection Selector, choose a collection and click its **Specification** tab.
|
||||
|
||||
Click **Schema Inference**. Flow scans the data in your collection and infers a new schema to use for materialization.
|
||||
|
||||
Review the new schema and click **Apply Inferred Schema**.
|
||||
|
||||
6. Click **Save and Publish**. You'll see a notification when the full materialization publishes successfully.
|
||||
6. Click **Save and Publish**. You'll see a notification when the materialization publishes successfully.
|
||||
|
||||
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
|
||||
Your Firestore collections are copied to tables in Supabase. As long as you leave the capture and materialation running, any changes to the Firestore data will be reflected in Supabase in milliseconds.
|
||||
|
||||
## Resources
|
||||
|
||||
|
||||
@@ -0,0 +1,107 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'illa',
|
||||
title: 'ILLA',
|
||||
description:
|
||||
'Get started with Supabase and ILLA, a low-code platform for developers that enables the rapid development and deployment of internal tools.',
|
||||
}
|
||||
|
||||
This tutorial outlines the process of creating an Admin Panel using ILLA Builder and Supabase in a few simple steps. ILLA is a low-code platform for developers that enables the rapid development and deployment of internal tools. It allows for creating pages by dragging and dropping UI components, connecting to any database or API, and writing JavaScript. To learn more about ILLA and give it a try, visit their website at [https://www.illacloud.com/](https://www.illacloud.com/). Let's begin!
|
||||
|
||||
### Step 1: Set up your Back end on Supabase
|
||||
|
||||
On the [Supabase dashboard](https://app.supabase.com/projects), click `New project` and set the name to adminPanel.
|
||||
|
||||

|
||||
|
||||
Create a new table by clicking on the `Create a new table` .
|
||||
|
||||
Supabase offers a variety of options for populating tables with data, including writing queries, creating schemas through a user interface, and uploading CSV files.
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
Fill out the info in the table. The database is now set up.
|
||||
|
||||
### Step 2: Build UI on ILLA Builder
|
||||
|
||||
On [ILLA Builder](https://fast-try.illacloud.com/), click Create New to create a new application.
|
||||
|
||||

|
||||
|
||||
Drag components from the `Insert` panel to the canvas.
|
||||
|
||||
Select the components on the canvas and configure the property on the `Inspect` panel.
|
||||
|
||||
As seen in the below screenshot, we have built a simple admin panel.
|
||||
|
||||

|
||||
|
||||
### Step 3: Connect to Supabase and config CRUD
|
||||
|
||||
Note down the database connection information under [Project Settings](https://app.supabase.com/project/hdcfnsxpwwgboqomdrhp/settings/database) in Supabase.
|
||||
|
||||

|
||||
|
||||
In the Action List, click `+ New` and select Supabase DB.
|
||||
|
||||

|
||||
|
||||
Fill out the form to connect to your Supabase instance. Test connection and save resource.
|
||||
|
||||

|
||||
|
||||
Click `Create Action` to create an action with the Supabase resource and config your CRUD.
|
||||
|
||||

|
||||
|
||||
Use `{{` to get the front-end input data. The following is an example of the User Management page in the Admin Panel.
|
||||
|
||||
Search for a user by the name inputted in input1
|
||||
|
||||
```
|
||||
SELECT *
|
||||
FROM user
|
||||
WHERE name = "{{input1.value}}"
|
||||
;
|
||||
```
|
||||
|
||||
Update user data. Update user information when id matches
|
||||
|
||||
```
|
||||
UPDATE user
|
||||
SET name = "{{input3.value}}"
|
||||
, email = "{{input4.value}}"
|
||||
WHERE id="{{input2.value}}"
|
||||
;
|
||||
```
|
||||
|
||||
Insert user data
|
||||
|
||||
```
|
||||
INSERT INTO user VALUES("{{input5.value}}","{{input6.value}}","{{input7.value}}");
|
||||
```
|
||||
|
||||
Delete a user by id
|
||||
|
||||
```
|
||||
DELETE FROM user WHERE id = "{{input2.value}}";
|
||||
```
|
||||
|
||||
### Step 4: Show data on components
|
||||
|
||||
Configure the properties of components with `{{` . For example:
|
||||
|
||||

|
||||
|
||||
## Resources
|
||||
|
||||
- [ILLA Cloud official website](https://www.illacloud.com/)
|
||||
- [ILLA Cloud GitHub](https://github.com/illacloud/illa-builder)
|
||||
- [ILLA Cloud documentation](https://www.illacloud.com/docs/about-illa)
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -23,7 +23,14 @@ Note your application key from the Keys and Credentials section - this will be u
|
||||
|
||||
# Web
|
||||
|
||||
[](http://www.youtube.com/watch?v=jrjrcpc2PFQ 'Supabase & Keyri | Web Integration Overview')
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/jrjrcpc2PFQ"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
For your web app, first download KeyriQR.html (available [here](https://raw.githubusercontent.com/Keyri-Co/library-keyri-connect/main/KeyriQR.html)) and save it to a public directory.
|
||||
|
||||
@@ -64,7 +71,15 @@ That's it!
|
||||
|
||||
# Mobile
|
||||
|
||||
[](http://www.youtube.com/watch?v=oGMsSKyh6tc 'Supabase & Keyri | Mobile Integration Overview')
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/oGMsSKyh6tc"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
|
||||
### Install Flutter
|
||||
|
||||
|
||||
@@ -14,7 +14,18 @@ export const meta = {
|
||||
- Emails
|
||||
- In-app notifications
|
||||
|
||||
This guide steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
|
||||
Here is William giving us the overview of how OneSignal can work with Supabase to send notifications to your users.
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/mw0DLwItue4"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
In this guide, we will build a similar app and steps you through how you can integrate OneSignal with Supabase to create a seamless cloud messaging experience for your users using Database webhooks and edge functions through a simple Next.js application.
|
||||
|
||||

|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@ By default, Prisma migrations will try to drop the `postgres` database, which ca
|
||||
Create a shadow database in your PostgreSQL server within the same Supabase project using the `psql` CLI and the `DATABASE_URL` from the previous steps (or use the local database).
|
||||
|
||||
```bash
|
||||
psql postgresql://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.red:5432
|
||||
psql postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432
|
||||
```
|
||||
|
||||
After you connect to your project's PostgreSQL instance, create another database (e.g., `postgres_shadow`):
|
||||
@@ -83,8 +83,8 @@ In the `.env` file, update `DATABASE_URL` and `SHADOW_DATABASE_URL` to the conne
|
||||
|
||||
```env
|
||||
# .env
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres"
|
||||
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow"
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
|
||||
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
|
||||
```
|
||||
|
||||
In the `schema.prisma` file, change the `provider` from "sqlite" to `"postgresql"` and add the `shadowDatabaseUrl` property.
|
||||
@@ -145,8 +145,8 @@ When migrating, you need to use the non-pooled connection URL (like the one used
|
||||
|
||||
```env
|
||||
# .env
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:6543/postgres?pgbouncer=true&connection_limit=1"
|
||||
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.vdbnhqozmlzdsaejdxwr.supabase.co:5432/postgres_shadow"
|
||||
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true&connection_limit=1"
|
||||
SHADOW_DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres_shadow"
|
||||
```
|
||||
|
||||
Prisma Migrate uses database transactions to check out the current state of the database and the migrations table. However, the Migration Engine is designed to use a single connection to the database, and does not support connection pooling with PgBouncer. If you attempt to run Prisma Migrate commands in any environment that uses PgBouncer for connection pooling, you might see the following error:
|
||||
|
||||
@@ -19,15 +19,11 @@ This value is also reported in the [database settings page](https://app.supabase
|
||||
|
||||
## Database storage management
|
||||
|
||||
Your project will be changed to read-only mode once it reaches 95% of your subscription quota, preventing further writes to your project.
|
||||
In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`.
|
||||
Supabase uses network-attached storage to balance performance with scalability. For Pro and Enterprise projects, database storage expands ~1.5x automatically (e.g., 8GB -> 12GB) when you reach 90% of the database storage quota. Automatic database storage expansion can only occur once every six hours. Pro projects can store up to 1024TB.
|
||||
|
||||
Regular operation (read-write mode) is automatically re-enabled once database storage usage falls under 95%.
|
||||
All projects enter read-only mode when you reach 95% of the database storage quota. In read-only mode, clients will encounter errors such as `cannot execute INSERT in a read-only transaction`. Regular operation (read-write mode) is automatically re-enabled once usage is below 95% of the database storage quota.
|
||||
|
||||
If you are on Pro or Enterprise, database storage expands automatically, however you may also encounter this error if fill the database faster than it can expand.
|
||||
|
||||
Supabase uses network-attached storage, which balances performance with scalability. Pro projects can store up to 1024TB.
|
||||
If you intend to grow beyond this, reach out to our team to learn more about our Enterprise tier.
|
||||
If you need more than 1024TB of database storage or require multiple storage expansions in a short period of time, [contact us](https://app.supabase.com/support/new) to learn more about the Enterprise plan.
|
||||
|
||||
### Increasing available database storage
|
||||
|
||||
|
||||
@@ -73,6 +73,11 @@ After developing your project and deciding it's Production Ready, you should run
|
||||
|
||||
- Supabase provides CAPTCHA protection on the signup, sign-in and password reset endpoints. Please refer to [our guide](/docs/guides/auth/auth-captcha) on how to protect against abuse using this method.
|
||||
|
||||
### Email Link Validity
|
||||
|
||||
- When working with enterprise systems, email scanners may scan and make a `GET` request to the reset password link or sign up link in your email. Since links in Supabase Auth are single use, a user who opens an email post-scan to click on a link will receive an error. To get around this problem,
|
||||
consider altering the email template to replace the original magic link with a link to a domain you control. The domain can present the user with a "Sign-in" button which redirect the user to the original magic link URL when clicked.
|
||||
|
||||
## Next steps
|
||||
|
||||
This checklist is always growing so be sure to check back frequently, and also feel free to suggest additions and amendments by making a PR on [GitHub](https://github.com/supabase/supabase).
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'http-status-codes',
|
||||
title: 'HTTP Status Codes',
|
||||
description: 'HTTP Status Codes used by the Supabase platform',
|
||||
}
|
||||
|
||||
The Supabase platform offers several HTTP APIs for each project. These APIs can use the status codes to indicate the state of the project, and the request being processed. The status codes returned for requests can be access via the [logs explorer](../platform/logs#logs-explorer).
|
||||
|
||||
## 2XX Success
|
||||
|
||||
2XX status codes indicate that the request was processed successfully.
|
||||
|
||||
## 3XX Redirects
|
||||
|
||||
3XX status codes indicate that the client must initiate another course of action to have the request processed successfully. The most popular usage of 3XX codes is to redirect the client to a different location.
|
||||
|
||||
## 4XX Client Errors
|
||||
|
||||
4XX status codes indicate an issue on the client's end with the request being made. These could include missing or invalid auth information, a malformed request, making too many requests in too short a time period ("rate limiting"), or a network issue on the client's end.
|
||||
|
||||
## 5XX Server or Project Errors
|
||||
|
||||
5XX status codes indicate that the project was unable to process the request successfully, but not because of an issue with the client's request.
|
||||
|
||||
5XX status codes can be the result of the project not having enough [compute](/docs/guides/platform/compute-add-ons) to process a complex request being made by a client or not being able to keep up with the volume of requests made against the project.
|
||||
|
||||
### 54X Project Errors
|
||||
|
||||
54X status codes are custom codes used by the Supabase platform to indicate the state of the project.
|
||||
|
||||
#### 540 Project Paused
|
||||
|
||||
The project the request was being made against has been paused. The project cannot process requests until it is un-paused by the owner.
|
||||
|
||||
Free-tier projects may be paused due to inactivity, on request by the owner, or in rare instances, due to abuse.
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -12,6 +12,8 @@ In some cases, access to new features require upgrading or migrating your Supaba
|
||||
|
||||
## Upgrade your project
|
||||
|
||||
<Admonition type="note">This is only available for projects on the Free plan.</Admonition>
|
||||
|
||||
When you pause and restore a project, the restored database includes the latest features. This method _does_ include downtime, so be aware that your project will be inaccessible for a short period of time.
|
||||
|
||||
1. On the [General Settings](https://app.supabase.com/project/_/settings/general) page in the Dashboard, click **Pause project**. You will be redirected to the home screen as your project is pausing. This process can take several minutes.
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
import Layout from '~/layouts/DefaultGuideLayout'
|
||||
|
||||
export const meta = {
|
||||
id: 'troubeshooting',
|
||||
title: 'Troubleshooting',
|
||||
description: 'Diagnosing and fixing issues with your hosted Supabase project.',
|
||||
}
|
||||
|
||||
Learn how to diagnose and fix common issues with your Supabase project.
|
||||
|
||||
## HTTP API Issues
|
||||
|
||||
Symptoms:
|
||||
|
||||
- HTTP timeouts
|
||||
- 5xx response codes
|
||||
- High response times
|
||||
|
||||
### Under-provisioned resources
|
||||
|
||||
The most common class of issues that causes HTTP timeouts and 5xx response codes is the under-provisioning of resources for your project. This can cause your project to be unable to service the traffic it is receiving.
|
||||
|
||||
Each Supabase project is provisioned with [segregated compute resources](../platform/compute-add-ons). This allows the project to serve unlimited requests, as long as they can be handled using the resources that have been provisioned. Complex queries, or queries that process larger amounts of data, will require higher amounts of resources. As such, the amount of resources that can handle a high volume of simple queries (or queries involving small amounts of data), will likely be unable to handle a similar volume of complex queries.
|
||||
|
||||
You can view the resource utilization of your Supabase Project using the [reports in the Dashboard](https://app.supabase.com/project/_/reports/database).
|
||||
|
||||
Some common solutions for this issue are:
|
||||
|
||||
- [Upgrading](https://app.supabase.com/project/_/settings/billing/subscription) to a [larger compute add-on](../platform/compute-add-ons) in order to serve higher volumes of traffic.
|
||||
- [Optimizing the queries](../platform/performance#examining-query-performance) being executed.
|
||||
- [Using fewer Postgres connections](../platform/performance#configuring-clients-to-use-fewer-connections) can reduce the amount of resources needed on the project.
|
||||
- [Restarting](https://app.supabase.com/project/_/settings/general) the database. This only temporarily solves the issue by terminating any ongoing workloads that might be tying up your compute resources.
|
||||
|
||||
If your [Daily Disk IO budget](../platform/compute-add-ons#disk-io-bandwidth) has been drained, you will need to either wait for it to be replenished the next day, or upgrade to a larger compute add-on to increase the budget available to your project.
|
||||
|
||||
## Unable to connect to your Supabase Project
|
||||
|
||||
Symptom: You're unable to connect to your Postgres database directly, but can open the Project in the [Supabase Dashboard](https://app.supabase.com/project/_/).
|
||||
|
||||
### Too many open connections
|
||||
|
||||
Errors about too many open connections can be _temporarily_ resolved by [restarting the database](https://app.supabase.com/project/_/settings/general). However, this won't solve the underlying issue for a permanent solution.
|
||||
|
||||
- If you're receiving a `No more connections allowed (max_client_conn)` error:
|
||||
- Configure your applications and services to [use fewer connections](../platform/performance#configuring-clients-to-use-fewer-connections).
|
||||
- [Upgrade](https://app.supabase.com/project/_/settings/billing/subscription) to a [larger compute add-on](../platform/compute-add-ons) to increase the number of available connections.
|
||||
- If you're receiving a `sorry, too many clients already` or `remaining connection slots are reserved for non-replication superuser connections` error message in addition to the above suggestions, switch to using the [connection pooler](/docs/guides/database/connecting-to-postgres#connection-pool) instead.
|
||||
|
||||
### Connection refused
|
||||
|
||||
If you receive a `connection refused` error after a few initial failed connection attempts, your client has likely been temporarily blocked in order to protect the database from brute-force attacks. You can wait 30 minutes before trying again with the correct password, or you can [contact support](https://app.supabase.com/support/new) with your client's IP address to manually unblock you.
|
||||
|
||||
If you're also unable to open the project using the [Supabase Dashboard](https://app.supabase.com/project/_/), review the solutions for [under-provisioned projects](#under-provisioned-resources).
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -121,7 +121,7 @@ channel.subscribe(async (status) => {
|
||||
})
|
||||
```
|
||||
|
||||
## Track and display which users are online
|
||||
## Track and Display Which Users Are Online
|
||||
|
||||
[Presence](/docs/guides/realtime#presence) stores and synchronize shared state across clients. The `sync` event is triggered whenever the shared state changes. The `join` event is triggered when new clients join the channel and `leave` event is triggered when clients leave.
|
||||
|
||||
|
||||
@@ -64,20 +64,6 @@ export const meta = {
|
||||
|
||||
</div>
|
||||
|
||||
<div className="grid gap-16 md:grid-cols-2">
|
||||
|
||||
<div>
|
||||
|
||||
### Supabase CLI
|
||||
|
||||
Develop your project locally, manage your Supabase projects, handle database migrations, CI/CD workflows, and generate types from your database schema, all from the comfort of your Terminal.
|
||||
|
||||
[Learn more](/docs/guides/resources/supabase-cli)
|
||||
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
{/* end of container */}
|
||||
|
||||
</div>
|
||||
|
||||
@@ -95,6 +95,10 @@ POSTGRES_PORT=5432
|
||||
|
||||
Once you have done this, you can safely comment out the `db` section of the `docker-compose` file, and remove any instances where the services `depends_on` the `db` image.
|
||||
|
||||
Supabase services require your external database to be initialized with a specific schema. Refer to our [postgres/migrations](https://github.com/supabase/postgres/tree/develop/migrations) repository for instructions on running these migrations.
|
||||
|
||||
Note that you need superuser permission on the postgres role to perform the initial schema migration. Once completed, the postgres role will be demoted to non-superuser to prevent abuse.
|
||||
|
||||
### Setting database's `log_min_messages`
|
||||
|
||||
By default, `docker compose` sets the database's `log_min_messages` configuration to `fatal` to prevent redundant logs generated by Realtime.
|
||||
|
||||
@@ -117,10 +117,8 @@ using ( bucket_id = 'public' );
|
||||
-- 1. Allow logged-in access to any files in the "restricted" bucket
|
||||
create policy "Restricted Access"
|
||||
on storage.objects for select
|
||||
using (
|
||||
bucket_id = 'restricted'
|
||||
and auth.role() = 'authenticated'
|
||||
);
|
||||
to authenticated
|
||||
using ( bucket_id = 'restricted' );
|
||||
```
|
||||
|
||||
### Allow individual access to a file
|
||||
|
||||
@@ -14,6 +14,15 @@ Supabase Storage offers the functionality to transform and resize images dynamic
|
||||
above](https://supabase.com/pricing).
|
||||
</Admonition>
|
||||
|
||||
<div className="video-container">
|
||||
<iframe
|
||||
src="https://www.youtube-nocookie.com/embed/dLqSmxX3r7I"
|
||||
frameBorder="1"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
|
||||
## Get a public URL for a transformed image
|
||||
|
||||
You can pass a `transform` option to the functions you are currently using to interact with your objects. This returns the public URL that serves the resized image.
|
||||
@@ -59,6 +68,32 @@ supabase.storage.from('bucket').download('image.jpg', {
|
||||
})
|
||||
```
|
||||
|
||||
## Automatic Image Optimisation (WebP)
|
||||
|
||||
When using the image transformation API we will automatically find the best format supported by the browser and return that to the client, without any code change.
|
||||
For instance, if you use Chrome when viewing a jpeg image and using transformation options, you'll see that the content-type returned is `webp`.
|
||||
|
||||
As a result, this will lower the bandwidth that you send to your users and your application will load much faster.
|
||||
|
||||
<Admonition type="note">
|
||||
We currently only support WebP. AVIF support will come in the near future
|
||||
</Admonition>
|
||||
|
||||
**Disabling automatic optimisation:**
|
||||
|
||||
In case you'd like to return the original format of the image and **opt-out** from the automatic image optimization detection, you can pass the `format=origin` parameter when requesting a transformed image,
|
||||
this is also supported in the JavaScript SDK starting from v2.2.0
|
||||
|
||||
```ts
|
||||
await storage.from('bucket').download('image.jpeg', {
|
||||
transform: {
|
||||
width: 200,
|
||||
height: 200,
|
||||
format: 'origin',
|
||||
},
|
||||
})
|
||||
```
|
||||
|
||||
## NextJS Loader
|
||||
|
||||
You can use Supabase Image Transformation to optimise your NextJS images using a custom [Loader](https://nextjs.org/docs/api-reference/next/image#loader-configuration).
|
||||
@@ -69,7 +104,7 @@ To get started, create a `supabase-image-loader.js` file in your NextJS project
|
||||
const projectId = '' // your supabase project id
|
||||
|
||||
export default function supabaseLoader({ src, width, height, quality }) {
|
||||
return `https://${projectId}.supabase.co/storage/v1/${src}?width=${width}&height=${
|
||||
return `https://${projectId}.supabase.co/storage/v1/render/image/public/${src}?width=${width}&height=${
|
||||
height || 0
|
||||
}&quality=${quality || 75}`
|
||||
}
|
||||
@@ -149,6 +184,34 @@ supabase.storage.from('bucket').download('image.jpg', {
|
||||
| BMP | `bmp` | ☑️ | ☑️ |
|
||||
| TIFF | `tiff` | ☑️ | ☑️ |
|
||||
|
||||
## Self Hosting
|
||||
|
||||
Our solution to image resizing and optimisation can be self-hosted as with any other Supabase product.
|
||||
Under the hood we use the awesome [Imgproxy](https://imgproxy.net/)
|
||||
|
||||
#### Imgproxy Configuration:
|
||||
|
||||
Simply deploy an imgproxy container with the following configuration:
|
||||
|
||||
```yaml
|
||||
imgproxy:
|
||||
image: darthsim/imgproxy
|
||||
environment:
|
||||
- IMGPROXY_ENABLE_WEBP_DETECTION=true
|
||||
- IMGPROXY_JPEG_PROGRESSIVE=true
|
||||
```
|
||||
|
||||
Note: make sure that this service can only be reachable within an internal network and not exposed to the public internet
|
||||
|
||||
#### Storage API Configuration:
|
||||
|
||||
Once [Imgproxy](https://imgproxy.net/) is deployed we need to configure a couple of environment variables in your self-hosted [storage-api](https://github.com/supabase/storage-api) service as follows:
|
||||
|
||||
```shell
|
||||
ENABLE_IMAGE_TRANSFORMATION=true
|
||||
IMGPROXY_URL=yourinternalimgproxyurl.internal.com
|
||||
```
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -1,159 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultLayout'
|
||||
import Link from 'next/link'
|
||||
import { GlassPanel } from 'ui'
|
||||
|
||||
export const meta = {
|
||||
title: 'Tutorials',
|
||||
}
|
||||
|
||||
### Webapps
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{webapps.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
### Mobile
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{mobile.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
### Low code
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{lowCode.map((item) => {
|
||||
return (
|
||||
<Link href={`/${item.href}`} key={item.title} passHref>
|
||||
<a className={'col-span-4'}>
|
||||
<GlassPanel title={item.title} span="col-span-6" background={false} icon={item.icon}>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
|
||||
export const webapps = [
|
||||
{
|
||||
title: 'NextJS',
|
||||
href: '/guides/tutorials/with-nextjs',
|
||||
description:
|
||||
'Learn how to build a user management app with NextJS and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/nextjs-icon',
|
||||
},
|
||||
{
|
||||
title: 'React',
|
||||
href: '/guides/tutorials/with-react',
|
||||
description:
|
||||
'Learn how to build a user management app with React and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/react-icon',
|
||||
},
|
||||
{
|
||||
title: 'Vue 3',
|
||||
href: '/guides/tutorials/with-vue-3',
|
||||
description:
|
||||
'Learn how to build a user management app with Vue 3 and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/vuejs-icon',
|
||||
},
|
||||
{
|
||||
title: 'Nuxt 3',
|
||||
href: '/guides/tutorials/with-nuxt-3',
|
||||
description:
|
||||
'Learn how to build a user management app with Nuxt 3 and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/nuxt-icon',
|
||||
},
|
||||
{
|
||||
title: 'Angular',
|
||||
href: '/guides/tutorials/with-angular',
|
||||
description:
|
||||
'Learn how to build a user management app with Angular and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/angular-icon',
|
||||
},
|
||||
{
|
||||
title: 'RedwoodJS',
|
||||
href: '/guides/tutorials/with-redwoodjs',
|
||||
description:
|
||||
'Learn how to build a user management app with RedwoodJS and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/redwood-icon',
|
||||
},
|
||||
{
|
||||
title: 'Svelte',
|
||||
href: '/guides/tutorials/with-svelte',
|
||||
description:
|
||||
'Learn how to build a user management app with Svelte and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/svelte-icon',
|
||||
},
|
||||
{
|
||||
title: 'SvelteKit',
|
||||
href: '/guides/tutorials/with-sveltekit',
|
||||
description:
|
||||
'Learn how to build a user management app with SvelteKit and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/svelte-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const mobile = [
|
||||
{
|
||||
title: 'Flutter',
|
||||
href: '/guides/tutorials/with-flutter',
|
||||
description:
|
||||
'Learn how to build a user management app with Flutter and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/flutter-icon',
|
||||
},
|
||||
{
|
||||
title: 'Expo',
|
||||
href: '/guides/tutorials/with-expo',
|
||||
description:
|
||||
'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/expo-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const lowCode = [
|
||||
{
|
||||
title: 'Ionic React',
|
||||
href: '/guides/tutorials/with-ionic-react',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic React and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
{
|
||||
title: 'Ionic Vue',
|
||||
href: '/guides/tutorials/with-ionic-vue',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic Vue and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
{
|
||||
title: 'Ionic Angular',
|
||||
href: '/guides/tutorials/with-ionic-angular',
|
||||
description:
|
||||
'Learn how to build a user management app with Ionic Angular and Supabase Database, Auth, and Storage functionality.',
|
||||
icon: '/docs/img/icons/ionic-icon',
|
||||
},
|
||||
]
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
@@ -69,13 +69,28 @@ export const meta = {
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/nextjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
</a>
|
||||
<IconPanel title="NextJS" icon="/docs/img/icons/nextjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/flutter`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
<IconPanel title="Flutter" icon="/docs/img/icons/flutter-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/sveltekit`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SvelteKit" icon="/docs/img/icons/svelte-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/solidjs`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="SolidJS" icon="/docs/img/icons/solidjs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
<Link href={`/guides/getting-started/quickstarts/vue`} passHref>
|
||||
<a className="no-underline">
|
||||
<IconPanel title="Vue" icon="/docs/img/icons/vuejs-icon" />
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -1,77 +0,0 @@
|
||||
import Layout from '~/layouts/DefaultLayout'
|
||||
import Link from 'next/link'
|
||||
// import { useTheme } from 'common/Providers'
|
||||
import { GlassPanel } from 'ui'
|
||||
|
||||
{/* // export const { isDarkMode } = useTheme() */}
|
||||
|
||||
export const meta = {
|
||||
title: 'Auth',
|
||||
}
|
||||
|
||||
## Add and manage email and password, passwordless, OAuth, and mobile logins to your project through a suite of identity providers and APIs.
|
||||
|
||||
### Popular Guides
|
||||
|
||||
<div className="grid grid-cols-12 gap-6 not-prose">
|
||||
{topArticles.map((item) => {
|
||||
return (
|
||||
<Link
|
||||
href={`/${item.href}`}
|
||||
key={item.title}
|
||||
passHref
|
||||
>
|
||||
<a className={item.span ?? 'col-span-4'}>
|
||||
<GlassPanel
|
||||
title={item.title}
|
||||
header="/docs/img/cards/sample-card-header-4.svg"
|
||||
span="col-span-6"
|
||||
background={false}
|
||||
//* isDarkMode={isDarkMode}
|
||||
>
|
||||
{item.description}
|
||||
</GlassPanel>
|
||||
</a>
|
||||
</Link>
|
||||
)})}
|
||||
|
||||
</div>
|
||||
|
||||
export const topArticles = [
|
||||
{
|
||||
title: 'Enable Captcha Protection',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-captcha',
|
||||
description: 'Keep your platform safe from bots and malicious scripts by implementing hCaptcha.',
|
||||
span: 'col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Auth Helpers',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-helpers',
|
||||
description: 'A collection of framework-specific utilities for implementing Supabase Auth.',
|
||||
span: 'col-span-6',
|
||||
},
|
||||
{
|
||||
title: 'Email Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/auth-email',
|
||||
description: 'Learn how to add an email and password login flow to your platform.',
|
||||
},
|
||||
{
|
||||
title: 'Phone Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/phone-login',
|
||||
description: 'Learn how to add a login flow so your users can access your platform through one-time passwords via SMS.',
|
||||
},
|
||||
{
|
||||
title: 'Social Login',
|
||||
header: '/docs/img/cards/sample-card-header-4.svg',
|
||||
href: '/guides/auth/social-login',
|
||||
description: 'Learn how to add a login flow so your users can access your platform through social providers like Apple, Facebook, or Google.',
|
||||
},
|
||||
]
|
||||
|
||||
export const Page = ({ children }) => <Layout meta={meta} children={children} />
|
||||
|
||||
export default Page
|
||||
Whitespace-only changes.
@@ -2,16 +2,31 @@
|
||||
import specFile from '~/../../spec/cli_v1_config.yaml' assert { type: 'yml' }
|
||||
import { Parameter } from '~/lib/refGenerator/refTypes'
|
||||
import ReactMarkdown from 'react-markdown'
|
||||
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
|
||||
import { Heading } from '~/components/CustomHTMLElements'
|
||||
|
||||
// Parameters are grouped on the page by tag
|
||||
const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local']
|
||||
const TAGS = ['general', 'auth', 'api', 'database', 'dashboard', 'local', 'edge-functions']
|
||||
|
||||
const tocList = TAGS.map((tag) =>
|
||||
specFile.parameters
|
||||
.filter((param: Parameter) => param.tags[0] === tag)
|
||||
.map((parameter) => {
|
||||
console.log('the param', parameter)
|
||||
const text = parameter.id
|
||||
const link = `#${parameter.id}`
|
||||
const level = '2'
|
||||
return { text, link, level }
|
||||
})
|
||||
).flat()
|
||||
|
||||
export default function Config() {
|
||||
return (
|
||||
<div>
|
||||
<div className="flex my-16">
|
||||
<div className="grid grid-cols-12 relative gap-4">
|
||||
<div className="relative col-span-12 md:col-span-9 transition-all ease-out duration-100">
|
||||
<div className="w-full prose">
|
||||
<h1 className="text-4xl mb-16">{specFile.info.title}</h1>
|
||||
<h1 className="">CLI configuration</h1>
|
||||
<div className="max-w-xs w-32 h-[1px] bg-gradient-to-r from-brand-800 to-brand-900 my-8"></div>
|
||||
<ReactMarkdown>{specFile.info.description}</ReactMarkdown>
|
||||
<div>
|
||||
{TAGS.map((tag) =>
|
||||
@@ -22,11 +37,11 @@ export default function Config() {
|
||||
{index === 0 && <h2 className="text-xl capitalize">{tag}</h2>}
|
||||
<div className="mt-8">
|
||||
<div>
|
||||
<h2 className="text-xl font-medium text-scale-1200 font-mono">
|
||||
<Heading tag="h2" parseAnchors={false} customAnchor={parameter.id}>
|
||||
<span className="mr-2">$</span>
|
||||
{parameter.title}
|
||||
</h2>
|
||||
<div className="grid" id={parameter.id}>
|
||||
</Heading>
|
||||
<div className="grid">
|
||||
<div className="border-b pb-8" key={parameter.id}>
|
||||
<div className=" mb-16">
|
||||
<p className="mb-4 scroll-mt-16 mt-0 text-scale-1100 text-base">
|
||||
@@ -63,6 +78,14 @@ export default function Config() {
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div className="md:col-span-3">
|
||||
<div className="sticky top-20 border-l">
|
||||
<span className="block font-mono text-xs uppercase text-scale-1200 pl-5 mb-4">
|
||||
On this page
|
||||
</span>
|
||||
<GuidesTableOfContents list={tocList} />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
|
||||
import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json'
|
||||
// @ts-expect-error
|
||||
import spec from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
|
||||
import RefSectionHandler from '~/components/reference/RefSectionHandler'
|
||||
import { flattenSections } from '~/lib/helpers'
|
||||
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
|
||||
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
|
||||
|
||||
const sections = flattenSections(clientLibsCommonSections)
|
||||
|
||||
export default function PyReference(props) {
|
||||
return (
|
||||
<RefSectionHandler
|
||||
sections={sections}
|
||||
spec={spec}
|
||||
typeSpec={typeSpec}
|
||||
pageProps={props}
|
||||
type="client-lib"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return handleRefStaticProps(sections, '/python')
|
||||
}
|
||||
|
||||
export function getStaticPaths() {
|
||||
return handleRefGetStaticPaths()
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import clientLibsCommonSections from '~/../../spec/common-client-libs-sections.json'
|
||||
import typeSpec from '~/../../spec/enrichments/tsdoc_v2/combined.json'
|
||||
// @ts-expect-error
|
||||
import spec from '~/../../spec/supabase_py_v2.yml' assert { type: 'yml' }
|
||||
import RefSectionHandler from '~/components/reference/RefSectionHandler'
|
||||
import { flattenSections } from '~/lib/helpers'
|
||||
import handleRefGetStaticPaths from '~/lib/mdx/handleRefStaticPaths'
|
||||
import handleRefStaticProps from '~/lib/mdx/handleRefStaticProps'
|
||||
import { useRouter } from 'next/router'
|
||||
import RefSEO from '~/components/reference/RefSEO'
|
||||
|
||||
const sections = flattenSections(clientLibsCommonSections)
|
||||
|
||||
export default function PyReference(props) {
|
||||
const router = useRouter()
|
||||
const slug = router.query.slug[0]
|
||||
const filteredSection = sections.filter((section) => section.id === slug)
|
||||
|
||||
const pageTitle = filteredSection[0]?.title
|
||||
? `${filteredSection[0]?.title} | Supabase`
|
||||
: 'Supabase'
|
||||
|
||||
return (
|
||||
<>
|
||||
<RefSEO title={pageTitle} />
|
||||
|
||||
<RefSectionHandler
|
||||
sections={filteredSection}
|
||||
spec={spec}
|
||||
typeSpec={typeSpec}
|
||||
pageProps={props}
|
||||
type="client-lib"
|
||||
/>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export async function getStaticProps() {
|
||||
return handleRefStaticProps(sections, '/python')
|
||||
}
|
||||
|
||||
export function getStaticPaths() {
|
||||
return handleRefGetStaticPaths()
|
||||
}
|
||||
@@ -1,724 +0,0 @@
|
||||
import Layout from '~/layouts/tutorials/TutorialLayout'
|
||||
|
||||
export const meta = {
|
||||
title: 'Creating a user management dashboard with NextJS and Supabase',
|
||||
author: 'Rich Haines',
|
||||
video: 'https://www.youtube-nocookie.com/embed/0Fs96oZ4se0',
|
||||
}
|
||||
|
||||
In this 25-minute guide, we’ll be building a collaborative user managment app using NextJS and Supabase.
|
||||
As users add and move rectangles in a canvas, changes will be automatically synced and persisted, allowing for a canvas that updates in real-time across clients.
|
||||
Users will also be able to see other users selections, and undo and redo actions.
|
||||
|
||||

|
||||
|
||||
### GitHub
|
||||
|
||||
Should you get stuck while working through the guide, refer to [this repo](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management).
|
||||
|
||||
## Building the App
|
||||
|
||||
Let's start building the Next.js app from scratch.
|
||||
|
||||
### Initialize a Next.js app
|
||||
|
||||
We can use [`create-next-app`](https://nextjs.org/docs/getting-started) to initialize
|
||||
an app called `supabase-nextjs`:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
```bash
|
||||
npx create-next-app@latest --ts --use-npm supabase-nextjs
|
||||
cd supabase-nextjs
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
Then install the Supabase client library: [supabase-js](https://github.com/supabase/supabase-js)
|
||||
|
||||
```bash
|
||||
npm install @supabase/supabase-js
|
||||
```
|
||||
|
||||
And finally we want to save the environment variables in a `.env.local`.
|
||||
All we need are the API URL and the `anon` key that you copied [earlier](#get-the-api-keys).
|
||||
|
||||
```bash title=.env.local
|
||||
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
|
||||
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
|
||||
```
|
||||
|
||||
And one optional step is to update the CSS file `styles/globals.css` to make the app look nice.
|
||||
You can find the full contents of this file [here](https://raw.githubusercontent.com/supabase/supabase/master/examples/user-management/nextjs-ts-user-management/styles/globals.css).
|
||||
|
||||
### Set up a Login component
|
||||
|
||||
#### Supabase Auth Helpers
|
||||
|
||||
Next.js is a highly versatile framework offering pre-rendering at build time (SSG), server-side rendering at request time (SSR), API routes, and middleware edge-functions.
|
||||
|
||||
It can be challenging to authenticate your users in all these different environments, that's why we've created the [Supabase Auth Helpers](https://supabase.com/docs/guides/auth/auth-helpers/nextjs) to make user management and data fetching within Next.js as easy as possible.
|
||||
|
||||
Install the auth helpers for React and Next.js
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-helpers-react @supabase/auth-helpers-nextjs
|
||||
```
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
Wrap your `pages/_app.js` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx title=pages/_app.js
|
||||
import '../styles/globals.css'
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({ Component, pageProps }) {
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
Wrap your `pages/_app.tsx` component with the `SessionContextProvider` component:
|
||||
|
||||
```jsx lines=2,8 title=pages/_app.tsx
|
||||
import { createBrowserSupabaseClient } from '@supabase/auth-helpers-nextjs'
|
||||
import { SessionContextProvider, Session } from '@supabase/auth-helpers-react'
|
||||
|
||||
function MyApp({
|
||||
Component,
|
||||
pageProps,
|
||||
}: AppProps<{
|
||||
initialSession: Session,
|
||||
}>) {
|
||||
const [supabaseClient] = useState(() => createBrowserSupabaseClient())
|
||||
|
||||
return (
|
||||
<SessionContextProvider
|
||||
supabaseClient={supabaseClient}
|
||||
initialSession={pageProps.initialSession}
|
||||
>
|
||||
<Component {...pageProps} />
|
||||
</SessionContextProvider>
|
||||
)
|
||||
}
|
||||
export default MyApp
|
||||
```
|
||||
|
||||
See the [Auth Helpers docs](/docs/guides/auth/auth-helpers/nextjs#usage-with-typescript) for more details on usage with TypeScript.
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
#### Supabase Auth UI
|
||||
|
||||
We can use the [Supabase Auth UI](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui) a pre-built React component for authenticating users via OAuth, email, and magic links.
|
||||
|
||||
Install the Supabase Auth UI for React
|
||||
|
||||
```bash
|
||||
npm install @supabase/auth-ui-react
|
||||
```
|
||||
|
||||
Add the `Auth` component to your home page
|
||||
|
||||
```jsx title=pages/index.js
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
const Home = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
|
||||
) : (
|
||||
<p>Account page will go here.</p>
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
### Account page
|
||||
|
||||
After a user is signed in we can allow them to edit their profile details and manage their account.
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
Let's create a new component for that called `Account.js` within a `components` folder.
|
||||
|
||||
```tsx title=components/Account.js
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useUser, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Account({ session }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState(null)
|
||||
const [website, setWebsite] = useState(null)
|
||||
const [avatar_url, setAvatarUrl] = useState(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({ username, website, avatar_url }) {
|
||||
try {
|
||||
setLoading(true)
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="website">Website</label>
|
||||
<input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button primary block"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
Let's create a new component for that called `Account.tsx` within a `components` folder.
|
||||
|
||||
```tsx title=components/Account.tsx
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useUser, useSupabaseClient, Session } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Account({ session }: { session: Session }) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const user = useUser()
|
||||
const [loading, setLoading] = useState(true)
|
||||
const [username, setUsername] = useState<Profiles['username']>(null)
|
||||
const [website, setWebsite] = useState<Profiles['website']>(null)
|
||||
const [avatar_url, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
|
||||
useEffect(() => {
|
||||
getProfile()
|
||||
}, [session])
|
||||
|
||||
async function getProfile() {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
let { data, error, status } = await supabase
|
||||
.from('profiles')
|
||||
.select(`username, website, avatar_url`)
|
||||
.eq('id', user.id)
|
||||
.single()
|
||||
|
||||
if (error && status !== 406) {
|
||||
throw error
|
||||
}
|
||||
|
||||
if (data) {
|
||||
setUsername(data.username)
|
||||
setWebsite(data.website)
|
||||
setAvatarUrl(data.avatar_url)
|
||||
}
|
||||
} catch (error) {
|
||||
alert('Error loading user data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
async function updateProfile({
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
}: {
|
||||
username: Profiles['username']
|
||||
website: Profiles['website']
|
||||
avatar_url: Profiles['avatar_url']
|
||||
}) {
|
||||
try {
|
||||
setLoading(true)
|
||||
if (!user) throw new Error('No user')
|
||||
|
||||
const updates = {
|
||||
id: user.id,
|
||||
username,
|
||||
website,
|
||||
avatar_url,
|
||||
updated_at: new Date().toISOString(),
|
||||
}
|
||||
|
||||
let { error } = await supabase.from('profiles').upsert(updates)
|
||||
if (error) throw error
|
||||
alert('Profile updated!')
|
||||
} catch (error) {
|
||||
alert('Error updating the data!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
<div>
|
||||
<label htmlFor="email">Email</label>
|
||||
<input id="email" type="text" value={session.user.email} disabled />
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="username">Username</label>
|
||||
<input
|
||||
id="username"
|
||||
type="text"
|
||||
value={username || ''}
|
||||
onChange={(e) => setUsername(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
<div>
|
||||
<label htmlFor="website">Website</label>
|
||||
<input
|
||||
id="website"
|
||||
type="website"
|
||||
value={website || ''}
|
||||
onChange={(e) => setWebsite(e.target.value)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button
|
||||
className="button primary block"
|
||||
onClick={() => updateProfile({ username, website, avatar_url })}
|
||||
disabled={loading}
|
||||
>
|
||||
{loading ? 'Loading ...' : 'Update'}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<button className="button block" onClick={() => supabase.auth.signOut()}>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
### Launch!
|
||||
|
||||
Now that we have all the components in place, let's update `pages/index.js`:
|
||||
|
||||
```jsx lines=3,14 title=pages/index.js
|
||||
import { Auth, ThemeSupa } from '@supabase/auth-ui-react'
|
||||
import { useSession, useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import Account from '../components/Account'
|
||||
|
||||
const Home = () => {
|
||||
const session = useSession()
|
||||
const supabase = useSupabaseClient()
|
||||
|
||||
return (
|
||||
<div className="container" style={{ padding: '50px 0 100px 0' }}>
|
||||
{!session ? (
|
||||
<Auth supabaseClient={supabase} appearance={{ theme: ThemeSupa }} theme="dark" />
|
||||
) : (
|
||||
<Account session={session} />
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export default Home
|
||||
```
|
||||
|
||||
Once that's done, run this in a terminal window:
|
||||
|
||||
```bash
|
||||
npm run dev
|
||||
```
|
||||
|
||||
And then open the browser to [localhost:3000](http://localhost:3000) and you should see the completed app.
|
||||
|
||||
## Bonus: Profile photos
|
||||
|
||||
Every Supabase project is configured with [Storage](/docs/guides/storage) for managing large files like
|
||||
photos and videos.
|
||||
|
||||
### Create an upload widget
|
||||
|
||||
Let's create an avatar widget for the user so that they can upload a profile photo. We can start by creating a new component:
|
||||
|
||||
<Tabs
|
||||
scrollable
|
||||
size="small"
|
||||
type="underlined"
|
||||
defaultActiveId="js"
|
||||
>
|
||||
<Tabs.Panel id="js" label="JavaScript">
|
||||
|
||||
```jsx title=components/Avatar.js
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
|
||||
export default function Avatar({ uid, url, size, onUpload }) {
|
||||
const supabase = useSupabaseClient()
|
||||
const [avatarUrl, setAvatarUrl] = useState(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar = async (event) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
) : (
|
||||
<div className="avatar no-image" style={{ height: size, width: size }} />
|
||||
)}
|
||||
<div style={{ width: size }}>
|
||||
<label className="button primary block" htmlFor="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
<Tabs.Panel id="ts" label="TypeScript">
|
||||
|
||||
```tsx title=components/Avatar.tsx
|
||||
import React, { useEffect, useState } from 'react'
|
||||
import { useSupabaseClient } from '@supabase/auth-helpers-react'
|
||||
import { Database } from '../utils/database.types'
|
||||
type Profiles = Database['public']['Tables']['profiles']['Row']
|
||||
|
||||
export default function Avatar({
|
||||
uid,
|
||||
url,
|
||||
size,
|
||||
onUpload,
|
||||
}: {
|
||||
uid: string
|
||||
url: Profiles['avatar_url']
|
||||
size: number
|
||||
onUpload: (url: string) => void
|
||||
}) {
|
||||
const supabase = useSupabaseClient<Database>()
|
||||
const [avatarUrl, setAvatarUrl] = useState<Profiles['avatar_url']>(null)
|
||||
const [uploading, setUploading] = useState(false)
|
||||
|
||||
useEffect(() => {
|
||||
if (url) downloadImage(url)
|
||||
}, [url])
|
||||
|
||||
async function downloadImage(path: string) {
|
||||
try {
|
||||
const { data, error } = await supabase.storage.from('avatars').download(path)
|
||||
if (error) {
|
||||
throw error
|
||||
}
|
||||
const url = URL.createObjectURL(data)
|
||||
setAvatarUrl(url)
|
||||
} catch (error) {
|
||||
console.log('Error downloading image: ', error)
|
||||
}
|
||||
}
|
||||
|
||||
const uploadAvatar: React.ChangeEventHandler<HTMLInputElement> = async (event) => {
|
||||
try {
|
||||
setUploading(true)
|
||||
|
||||
if (!event.target.files || event.target.files.length === 0) {
|
||||
throw new Error('You must select an image to upload.')
|
||||
}
|
||||
|
||||
const file = event.target.files[0]
|
||||
const fileExt = file.name.split('.').pop()
|
||||
const fileName = `${uid}.${fileExt}`
|
||||
const filePath = `${fileName}`
|
||||
|
||||
let { error: uploadError } = await supabase.storage
|
||||
.from('avatars')
|
||||
.upload(filePath, file, { upsert: true })
|
||||
|
||||
if (uploadError) {
|
||||
throw uploadError
|
||||
}
|
||||
|
||||
onUpload(filePath)
|
||||
} catch (error) {
|
||||
alert('Error uploading avatar!')
|
||||
console.log(error)
|
||||
} finally {
|
||||
setUploading(false)
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<div>
|
||||
{avatarUrl ? (
|
||||
<img
|
||||
src={avatarUrl}
|
||||
alt="Avatar"
|
||||
className="avatar image"
|
||||
style={{ height: size, width: size }}
|
||||
/>
|
||||
) : (
|
||||
<div className="avatar no-image" style={{ height: size, width: size }} />
|
||||
)}
|
||||
<div style={{ width: size }}>
|
||||
<label className="button primary block" htmlFor="single">
|
||||
{uploading ? 'Uploading ...' : 'Upload'}
|
||||
</label>
|
||||
<input
|
||||
style={{
|
||||
visibility: 'hidden',
|
||||
position: 'absolute',
|
||||
}}
|
||||
type="file"
|
||||
id="single"
|
||||
accept="image/*"
|
||||
onChange={uploadAvatar}
|
||||
disabled={uploading}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
```
|
||||
|
||||
</Tabs.Panel>
|
||||
</Tabs>
|
||||
|
||||
### Add the new widget
|
||||
|
||||
And then we can add the widget to the Account page:
|
||||
|
||||
```jsx title=components/Account.js
|
||||
// Import the new component
|
||||
import Avatar from './Avatar'
|
||||
|
||||
// ...
|
||||
|
||||
return (
|
||||
<div className="form-widget">
|
||||
{/* Add to the body */}
|
||||
<Avatar
|
||||
uid={user.id}
|
||||
url={avatar_url}
|
||||
size={150}
|
||||
onUpload={(url) => {
|
||||
setAvatarUrl(url)
|
||||
updateProfile({ username, website, avatar_url: url })
|
||||
}}
|
||||
/>
|
||||
{/* ... */}
|
||||
</div>
|
||||
)
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
At this stage you have a fully functional application!
|
||||
|
||||
- See the complete [example on GitHub](https://github.com/supabase/supabase/tree/master/examples/user-management/nextjs-ts-user-management) and deploy it to Vercel.
|
||||
- Explore the [pre-built Auth UI for React](https://supabase.com/docs/guides/auth/auth-helpers/auth-ui).
|
||||
- Explore the [Auth Helpers for Next.js](https://supabase.com/docs/guides/auth/auth-helpers/nextjs).
|
||||
- Explore the [Supabase Cache Helpers](https://github.com/psteinroe/supabase-cache-helpers).
|
||||
- See the [Next.js Subscription Payments Starter](https://github.com/vercel/nextjs-subscription-payments) template on GitHub.
|
||||
- Got a question? [Ask here](https://github.com/supabase/supabase/discussions).
|
||||
- Sign in: [app.supabase.com](https://app.supabase.com)
|
||||
|
||||
export default ({ children }) => <Layout meta={meta} children={children} />
|
||||
@@ -15,11 +15,11 @@ Chase Granberry
|
||||
Chris Copplestone
|
||||
Danny Ng
|
||||
Dave Wilson
|
||||
David Thyresson
|
||||
Div Arora
|
||||
Egor Romanov
|
||||
Fabrizio Fenoglio
|
||||
Francisco Mazzoni
|
||||
Haydn Maley
|
||||
Hieu Pham
|
||||
Inian P
|
||||
Isaiah Hamilton
|
||||
@@ -30,6 +30,7 @@ Jonny Summers-Muir
|
||||
Joshen Lim
|
||||
Kang Ming Tay
|
||||
Karlo Ison
|
||||
Kevin Brolly
|
||||
Kevin Grüneberg
|
||||
Lakshmipathi G
|
||||
Laura C
|
||||
@@ -47,7 +48,7 @@ Paul Copplestone
|
||||
Pavel Borisov
|
||||
Qiao Han
|
||||
Ramiro Nuñez Dosio
|
||||
Rodrigo Mansueli Nunes
|
||||
Rodrigo Martins Mansueli
|
||||
Rory Wilding
|
||||
Stanislav M
|
||||
Steve Chavez
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 132 KiB |
Loaded 100 of 322 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user