Merge branch 'master' into chore/update-telemetry-behaviour-consent

This commit is contained in:
Francesco Sansalvadore authored and GitHub committed 2023-07-27 13:59:53 +02:00
commit 4ee56ab302
553 files changed
+14247 -9532

No files matched your search

+20
View File
@@ -132,6 +132,26 @@ Create a new entry in the [`redirects.js`](https://github.com/supabase/supabase/
---
### Federated docs
We support "federating" docs, meaning doc content can come directly from external repos other than [`supabase/supabase`](https://github.com/supabase/supabase).
- It's great for things like client libs who have their own set of docs that we don't want to duplicate on the official Supabase docs (eg. [`supabase/vecs`](https://github.com/supabase/vecs)).
- No duplication or manual steps required - fetches and generates automatically as part of the docs build pipeline
- It's flexible - you can "embed" external docs nearly anywhere at any level in Supabase docs, but they will feel native
- If you are maintaining a repo containing docs that you think could also live in Supabase docs, feel free to create an issue and we can work together to integrate
Federated docs work using Next.js's build pipeline. We use `getStaticProps()` to fetch remote documentation (ie. markdown) at build time which is processed and passed to the respective page within the docs.
See the [Vecs Python source code](https://github.com/supabase/supabase/blob/master/apps/docs/pages/guides/ai/python/%5Bslug%5D.tsx) to see how we do this for [`supabase/vecs`](https://github.com/supabase/vecs). Use this as a starting point for federating other docs.
Some things to consider:
- Links will often need to be transformed. For example if you are bringing in external markdown content, they may contain relative links that may not translate 1-to-1 after rendering in the Supabase docs. Use the [Link Transform](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/rehypeLinkTransform.ts) rehype plugin to transform links.
- External markdown may contain syntax extensions that Supabase docs don't understand by default (eg. [mkdocs-material extensions](https://squidfunk.github.io/mkdocs-material/setup/extensions/python-markdown)). We've built a few remark plugins to support these extensions (eg. [MkDocs Admonition](https://github.com/supabase/supabase/blob/master/apps/docs/lib/mdx/plugins/remarkAdmonition.ts)). If there is a markdown extension that you need that isn't built yet, feel free to open an issue and we can work together to create it.
---
## Community channels
If you are stuck somewhere or have any questions, join our [Discord Community Server](https://discord.supabase.com/) or the [Github Discussions](https://github.com/supabase/supabase/discussions). We are here to help!
@@ -1,6 +1,5 @@
import { useState } from 'react'
import { Input, Button } from 'ui'
import Admonition from '~/components/Admonition'
import { Admonition, Button, Input } from 'ui'
function base64URL(value: string) {
return globalThis.btoa(value).replace(/[=]/g, '').replace(/[+]/g, '-').replace(/[\/]/g, '_')
+8
View File
@@ -37,6 +37,14 @@ const Frameworks = () => {
},
href: '/reference/javascript/installing#javascript',
},
{
name: 'Kotlin',
logo: {
light: '/docs/img/icons/kotlin-icon.svg',
dark: '/docs/img/icons/kotlin-icon.svg',
},
href: '/guides/with-kotlin',
},
{
name: 'Next.js',
logo: {
@@ -1,31 +0,0 @@
### Fetch requests to API endpoints aren't showing the session
You must pass along the cookie header with the fetch request in order for your API endpoint to get access to the cookie from this request.
```ts
const res = await fetch('http://localhost/contact', {
headers: {
cookie: headers().get('cookie') as string,
},
})
```
### Performing administration tasks on the server side with the `service_role` `secret`
By default, the auth-helpers do not permit the use of the `service_role` `secret`. This restriction is in place to prevent the accidental exposure of your `service_role` `secret` to the public. Since the auth-helpers function on both the server and client side, it becomes challenging to separate the key specifically for client-side usage.
However, there is a solution. You can create a separate Supabase client using the `createClient` method from `@supabase/supabase-js` and provide it with the `service_role` `secret`. In a server environment, you will also need to disable certain properties to ensure proper functionality.
By implementing this approach, you can safely utilize the `service_role` `secret` without compromising security or exposing sensitive information to the public.
```ts
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(supabaseUrl, serviceRoleSecret, {
auth: {
persistSession: false,
autoRefreshToken: false,
detectSessionInUrl: false,
},
})
```
@@ -0,0 +1,53 @@
import ProductManagementSQLTemplate from './product_management_sql_template.mdx'
import { Tabs } from 'ui'
export const TabPanel = Tabs.Panel
## Project setup
Before we start building we're going to set up our Database and API. This is as simple as starting a new Project in Supabase and then creating a "schema" inside the database.
### Create a project
1. [Create a new project](https://app.supabase.com) in the Supabase Dashboard.
1. Enter your project details.
1. Wait for the new database to launch.
### Set up the database schema
Now we are going to set up the database schema. You can just copy/paste the SQL from below and run it yourself.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="sql"
>
{/* <TabPanel id="dashboard" label="Dashboard">
1. Go to the [SQL Editor](https://app.supabase.com/project/_/sql) page in the Dashboard.
2. Click **Product Management**.
3. Click **Run**.
</TabPanel> */}
<TabPanel id="sql" label="SQL">
<ProductManagementSQLTemplate />
</TabPanel>
</Tabs>
### Get the API Keys
Now that you've created some database tables, you are ready to insert data using the auto-generated API.
We just need to get the Project URL and `anon` key from the API settings.
1. Go to the [API Settings](https://app.supabase.com/project/_/settings/api) page in the Dashboard.
2. Find your Project `URL`, `anon`, and `service_role` keys on this page.
### Set up Google Authentication
From the [Google Console](https://console.developers.google.com/apis/library), create a new project and add OAuth2 credentials.
![Create Google OAuth credentials](/docs/img/guides/kotlin/google-cloud-oauth-credentials-create.png)
In your [Supabase Auth settings](https://app.supabase.com/project/_/auth/providers) enable Google as a provider and set the required credentials as outlined in the [auth docs](/docs/guides/auth/social-login/auth-google).
@@ -0,0 +1,35 @@
```sql
-- Create a table for public profiles
create table
public.products (
id uuid not null default gen_random_uuid (),
name text not null,
price real not null,
image text null,
constraint products_pkey primary key (id)
) tablespace pg_default;
-- Set up Storage!
insert into storage.buckets (id, name)
values ('Product Image', 'Product Image');
-- Set up access controls for storage.
-- See https://supabase.com/docs/guides/storage#policy-examples for more details.
CREATE POLICY "Enable read access for all users" ON "storage"."objects"
AS PERMISSIVE FOR SELECT
TO public
USING (true)
CREATE POLICY "Enable insert for all users" ON "storage"."objects"
AS PERMISSIVE FOR INSERT
TO authenticated, anon
WITH CHECK (true)
CREATE POLICY "Enable update for all users" ON "storage"."objects"
AS PERMISSIVE FOR UPDATE
TO public
USING (true)
WITH CHECK (true)
```
@@ -60,6 +60,12 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
},
],
[
{
label: 'Local Dev / CLI',
icon: 'reference-cli',
href: '/guides/cli',
level: 'reference_javascript',
},
{
label: 'Platform',
icon: 'platform',
@@ -78,13 +84,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
href: '/guides/self-hosting',
level: 'self_hosting',
},
{
label: 'Integrations',
icon: 'integrations',
hasLightIcon: true,
href: '/guides/integrations',
level: 'integrations',
},
],
[
{
@@ -131,7 +130,14 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
community: true,
},
{
label: 'Tools reference',
label: 'Tools',
},
{
label: 'Integrations',
icon: 'integrations',
hasLightIcon: true,
href: 'https://supabase.com/partners/integrations',
level: 'integrations',
},
{
label: 'Management API',
@@ -139,12 +145,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
href: '/reference/api/introduction',
level: 'reference_javascript',
},
{
label: 'Supabase CLI',
icon: 'reference-cli',
href: '/guides/cli',
level: 'reference_javascript',
},
],
[
{
@@ -206,7 +206,6 @@ export const gettingstarted: NavMenuConstant = {
items: [
{ name: 'Features', url: '/guides/getting-started/features' },
{ name: 'Architecture', url: '/guides/getting-started/architecture' },
{ name: 'Local Development', url: '/guides/getting-started/local-development' },
{
name: 'Framework Quickstarts',
items: [
@@ -215,6 +214,7 @@ export const gettingstarted: NavMenuConstant = {
{ name: 'NuxtJS', url: '/guides/getting-started/quickstarts/nuxtjs' },
{ name: 'RedwoodJS', url: '/guides/getting-started/quickstarts/redwoodjs' },
{ name: 'Flutter', url: '/guides/getting-started/quickstarts/flutter' },
{ name: 'Android Kotlin', url: '/guides/getting-started/quickstarts/kotlin' },
{ name: 'SvelteKit', url: '/guides/getting-started/quickstarts/sveltekit' },
{ name: 'SolidJS', url: '/guides/getting-started/quickstarts/solidjs' },
{ name: 'Vue', url: '/guides/getting-started/quickstarts/vue' },
@@ -277,7 +277,10 @@ export const gettingstarted: NavMenuConstant = {
name: 'Expo',
url: '/guides/getting-started/tutorials/with-expo',
},
{
name: 'Android Kotlin',
url: '/guides/getting-started/tutorials/with-kotlin',
},
{
name: 'Ionic React',
url: '/guides/getting-started/tutorials/with-ionic-react',
@@ -474,6 +477,7 @@ export const auth = {
url: undefined,
items: [
{ name: 'Enable Captcha Protection', url: '/guides/auth/auth-captcha' },
{ name: 'Configuring Custom SMTP', url: '/guides/auth/auth-smtp' },
{ name: 'Managing User Data', url: '/guides/auth/managing-user-data' },
{ name: 'Multi-Factor Authentication', url: '/guides/auth/auth-mfa' },
{ name: 'Row Level Security', url: '/guides/auth/row-level-security' },
@@ -911,6 +915,7 @@ export const ai: NavMenuConstant = {
{ name: 'Vector columns', url: '/guides/ai/vector-columns' },
{ name: 'Engineering for scale', url: '/guides/ai/engineering-for-scale' },
{ name: 'Choosing Compute Add-on', url: '/guides/ai/choosing-compute-addon' },
{ name: 'Going to Production', url: '/guides/ai/going-to-prod' },
],
},
{
@@ -970,14 +975,29 @@ export const ai: NavMenuConstant = {
export const supabase_cli: NavMenuConstant = {
icon: 'reference-cli',
title: 'Supabase CLI',
title: 'Local Dev / CLI',
url: '/guides/cli',
items: [
{ name: 'Overview', url: '/guides/cli' },
{ name: 'Managing Environments', url: '/guides/cli/managing-environments' },
{ name: 'Getting started', url: '/guides/cli' },
{ name: 'Local Development', url: '/guides/cli/local-development' },
{ name: 'Managing environments', url: '/guides/cli/managing-environments' },
{
name: 'Using environment variables in config.toml',
url: '/guides/cli/using-environment-variables-in-config',
name: 'Managing config and secrets',
url: '/guides/cli/managing-config',
},
{
name: 'Testing emails locally',
url: '/guides/cli/testing-emails',
},
{
name: 'GitHub Action',
url: undefined,
items: [
{
name: 'Generate types from your database',
url: '/guides/cli/github-action/generating-types',
},
],
},
{
name: 'Reference',
@@ -1060,6 +1080,24 @@ export const platform: NavMenuConstant = {
{ name: 'Production Checklist', url: '/guides/platform/going-into-prod' },
],
},
{
name: 'Integrations',
url: undefined,
items: [
{
name: 'Integrations Marketplace',
url: '/guides/platform/marketplace',
},
{
name: 'Publish an OAuth App',
url: '/guides/platform/oauth-apps/publish-an-oauth-app',
},
{
name: 'Sign in with Supabase',
url: '/guides/platform/oauth-apps/authorize-an-oauth-app',
},
],
},
{
name: 'Troubleshooting',
url: undefined,
@@ -1183,87 +1221,6 @@ export const migrate = {
],
}
export const integrations: NavMenuConstant = {
icon: 'integrations',
title: 'Integrations',
url: '/guides/integrations',
items: [
{ name: 'Overview', url: '/guides/integrations/integrations' },
{
name: 'OAuth Apps (Beta)',
url: undefined,
items: [
{
name: 'Publish an OAuth App',
url: '/guides/integrations/oauth-apps/publish-an-oauth-app',
},
{
name: 'Authorize an OAuth App',
url: '/guides/integrations/oauth-apps/authorize-an-oauth-app',
},
],
},
{
name: 'Auth',
url: undefined,
items: [
{
name: 'Auth0',
url: '/guides/integrations/auth0',
},
{ name: 'Authsignal', url: '/guides/integrations/authsignal' },
{ name: 'Clerk', url: '/guides/integrations/clerk' },
{ name: 'keyri', url: '/guides/integrations/keyri' },
{ name: 'Passage', url: '/guides/integrations/passage' },
{ name: 'Stytch', url: '/guides/integrations/stytch' },
{ name: 'SuperTokens', url: '/guides/integrations/supertokens' },
],
},
{
name: 'Caching / Offline-first',
url: undefined,
items: [{ name: 'Polyscale', url: '/guides/integrations/polyscale' }],
},
{
name: 'Developer Tools',
url: undefined,
items: [
{ name: 'Cloudflare Workers', url: '/guides/integrations/cloudflare-workers' },
{ name: 'Estuary', url: '/guides/integrations/estuary' },
{ name: 'OpenAI', url: '/guides/ai/examples/openai' },
{ name: 'pgMustard', url: '/guides/integrations/pgmustard' },
{ name: 'Prisma', url: '/guides/integrations/prisma' },
{ name: 'Sequin', url: '/guides/integrations/sequin' },
{ name: 'Snaplet', url: '/guides/integrations/snaplet' },
{ name: 'Vercel', url: '/guides/integrations/vercel' },
{ name: 'Upstash Redis', url: '/guides/functions/examples/upstash-redis' },
{ name: 'WeWeb', url: '/guides/integrations/weweb' },
{ name: 'Zuplo', url: '/guides/integrations/zuplo' },
],
},
{
name: 'Low-code',
url: undefined,
items: [
{ name: 'Appsmith', url: '/guides/integrations/appsmith' },
{ name: 'Bracket', url: '/guides/integrations/bracket' },
{ name: 'DhiWise', url: '/guides/integrations/dhiwise' },
{ name: 'Directus', url: '/guides/integrations/directus' },
{ name: 'Draftbit', url: '/guides/integrations/draftbit' },
{ name: 'FlutterFlow', url: '/guides/integrations/flutterflow' },
{ name: 'Forest Admin', url: '/guides/integrations/forestadmin' },
{ name: 'Plasmic', url: '/guides/integrations/plasmic' },
{ name: 'ILLA', url: '/guides/integrations/illa' },
],
},
{
name: 'Messaging',
url: undefined,
items: [{ name: 'OneSignal', url: '/guides/integrations/onesignal' }],
},
],
}
export const reference = {
title: 'API Reference',
icon: 'reference',
+3 -3
View File
@@ -2,7 +2,6 @@ import Link from 'next/link'
import { Alert, Button, CodeBlock, GlassPanel, markdownComponents, Tabs } from 'ui'
import StepHikeCompact from '~/components/StepHikeCompact'
// Common components
import Admonition from './Admonition'
import ButtonCard from './ButtonCard'
import JwtGenerator from './JwtGenerator'
@@ -22,7 +21,7 @@ import QuickstartIntro from './MDX/quickstart_intro.mdx'
import SocialProviderSettingsSupabase from './MDX/social_provider_settings_supabase.mdx'
import SocialProviderSetup from './MDX/social_provider_setup.mdx'
import StorageManagement from './MDX/storage_management.mdx'
import AuthHelpersFAQ from './MDX/auth_helpers_faq.mdx'
import KotlinProjectSetup from './MDX/kotlin_project_setup.mdx'
import { CH } from '@code-hike/mdx/components'
import RefHeaderSection from './reference/RefHeaderSection'
@@ -32,6 +31,7 @@ import CliGlobalFlagsHandler from '~/components/reference/enrichments/cli/CliGlo
import Options from '~/components/Options'
import Param from '~/components/Params'
import { Admonition } from 'ui'
import {
IconMenuJavascript,
IconMenuHome,
@@ -71,11 +71,11 @@ const components = {
QuickstartIntro,
DatabaseSetup,
ProjectSetup,
KotlinProjectSetup,
SocialProviderSetup,
SocialProviderSettingsSupabase,
StepHikeCompact,
StorageManagement,
AuthHelpersFAQ,
Mermaid,
Extensions,
Alert: (props: any) => (
@@ -1,7 +1,7 @@
import Link from 'next/link'
import { useRouter } from 'next/router'
import { Admonition } from 'ui'
import { useMenuActiveRefId } from '~/hooks/useMenuState'
import Admonition from '../Admonition'
import { ICommonSection } from './Reference.types'
export interface OldVersionAlertProps {
+11 -9
View File
@@ -18,15 +18,17 @@ This reference documents every object and method available in Supabase's Kotlin
Supported Kotlin targets:
| **Module** | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
| -------------------------------------- | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **JS** _(Browser)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
| | **GoTrue** | **Realtime** | **Postgrest** | **Storage** | **Functions** | **Apollo-GraphQL** |
| ------------------------------------------------------------------ | ---------- | ------------ | ------------- | ----------- | ------------- | ------------------ |
| **JVM** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Android** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **JS** _(Browser, NodeJS)_ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **IOS** | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **tvOS** _(tvosArm64, tvosX64, tvosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **watchOS** _(watchosArm64, watchosX64, watchosSimulatorArm64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **MacOS** _(macosX64 & macosArm64)_ 🚧 | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ |
| **Windows** _(mingwX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
| **Linux** _(linuxX64)_ 🚧 | ☑️ | ✅ | ✅ | ✅ | ✅ | ❌ |
✅ = full support
+4
View File
@@ -8,6 +8,7 @@ import Head from 'next/head'
import { PropsWithChildren, memo } from 'react'
import Footer from '~/components/Navigation/Footer'
import { menuState, useMenuLevelId, useMenuMobileOpen } from '~/hooks/useMenuState'
import { Announcement, LW8CountdownBanner } from 'ui'
const levelsData = {
home: {
@@ -326,6 +327,9 @@ const SiteLayout = ({ children }: PropsWithChildren<{}>) => {
<title>Supabase Docs</title>
</Head>
<main>
<Announcement>
<LW8CountdownBanner />
</Announcement>
<div className="flex flex-row h-screen">
<NavContainer />
<Container>
+1 -3
View File
@@ -1,17 +1,15 @@
import { MDXProvider } from '@mdx-js/react'
import { NextSeo } from 'next-seo'
import Head from 'next/head'
import Link from 'next/link'
import { useRouter } from 'next/router'
import { FC, useEffect, useRef, useState } from 'react'
import { IconExternalLink } from 'ui'
import { ExpandableVideo, IconExternalLink } from 'ui'
import components from '~/components'
import { highlightSelectedTocItem } from '~/components/CustomHTMLElements/CustomHTMLElements.utils'
import { FooterHelpCalloutType } from '~/components/FooterHelpCallout'
import GuidesTableOfContents from '~/components/GuidesTableOfContents'
import useHash from '~/hooks/useHash'
import { LayoutMainContent } from '../DefaultLayout'
import ExpandableVideo from 'ui/src/components/ExpandableVideo/ExpandableVideo'
interface Props {
meta: {
@@ -1,8 +1,8 @@
import { Content, Paragraph, Parent } from 'mdast'
import { MdxJsxFlowElement } from 'mdast-util-mdx'
import { AdmonitionProps } from 'ui'
import { Node } from 'unist'
import { visit } from 'unist-util-visit'
import { AdmonitionProps } from '~/components/Admonition'
/**
* Transforms an `mkdocs-material` Admonition to a Supabase Admonition.
@@ -153,7 +153,7 @@ And after ChatGPT receives a response from the plugin it will answer your questi
## Resources
- ChatGPT Retrieval Plugin: [github.com/openai/chatgpt-retrieval-plugin](https://github.com/openai/chatgpt-retrieval-plugin)
- ChatGTP Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
- ChatGPT Plugins: [official documentation](https://platform.openai.com/docs/plugins/introduction)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -5,7 +5,7 @@ export const meta = {
title: 'Generating OpenAI GPT3 completions',
description: 'Generate GPT text completions using OpenAI and Supabase Edge Functions.',
subtitle: 'Generate GPT text completions using OpenAI and Supabase Edge Functions.',
video: 'https://www.youtube.com/v/29p8kIqyU_Y',
video: 'https://www.youtube-nocookie.com/v/29p8kIqyU_Y',
tocVideo: '29p8kIqyU_Y',
}
+100
View File
@@ -0,0 +1,100 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'ai-going-to-prod',
title: 'Going to Production',
description: 'Checklist for going to production with your AI application.',
subtitle: 'Going to production checklist for AI applications.',
sidebar_label: 'Going to Production',
}
This guide will help you to prepare your application for production. We'll provide actionable steps to help you scale your application, ensure that it is reliable, can handle the load, and provide optimal precision for your use case.
See our [Engineering for Scale](/docs/guides/ai/engineering-for-scale) guide for more information about engineering at scale.
## Do you need indexes?
Sequential scans will result in significantly higher latencies and lower throughput, guaranteeing 100% precision and not being RAM bound.
There are a couple of cases where you might not need indexes:
- You have a small dataset and don't need to scale it.
- You are not expecting high amounts of vector search queries per second.
- You need to guarantee 100% precision.
You don't have to create indexes in these cases and can use sequential scans instead. This type of workload will not be RAM bound and will not require any additional resources but will result in higher latencies and lower throughput. Extra CPU cores may help to improve queries per second, but it will not help to improve latency.
On the other hand, if you need to scale your application, you will need to create indexes. This will result in lower latencies and higher throughput, but will require additional RAM to make use of Postgres Caching. Also, using indexes will result in lower precision, since you are replacing exact (KNN) search with approximate (ANN) search.
## Understanding `probes` and `lists`
Indexes used for approximate vector similarity search in pgvector divides a dataset into partitions. The number of these partitions is defined by the `lists` constant. The `probes` controls how many lists are going to be searched during a query.
The values of lists and probes directly affect precision and requests per second (RPS).
- Higher `lists` means an index will be built slower, but you can achieve better RPS and precision.
- Higher `probes` means that select queries will be slower, but you can achieve better precision.
- `lists` and `probes` are not independent. Higher `lists` means that you will have to use higher `probes` to achieve the same precision.
You can find more examples of how `lists` and `probes` constants affect precision and RPS in [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blogpost.
<div>
<img
alt="multi database"
className="dark:hidden"
src="/docs/img/ai/going-prod/lists-count--light.png"
/>
<img
alt="multi database"
className="hidden dark:block"
src="/docs/img/ai/going-prod/lists-count--dark.png"
/>
</div>
## Performance Tips when using indexes
First, a few generic tips which you can pick and choose from:
1. The Supabase managed platform will automatically optimize Postgres configs for you based on your compute addon. But if you self-host, consider **adjusting your Postgres config** based on RAM & CPU cores. See [example optimizations](https://gist.github.com/egor-romanov/323e2847851bbd758081511785573c08) for more details.
2. Prefer `inner-product` to `L2` or `Cosine` distances if your vectors are normalized (like `text-embedding-ada-002`). If embeddings are not normalized, `Cosine` distance should give the best results with an index.
3. **Pre-warm your database.** Implement the warm-up technique before transitioning to production or running benchmarks.
- Execute 10,000 to 50,000 "warm-up" queries before each benchmark, matching the number of `probes` you are going to use in production. Additionally, you can execute about 1,000 queries with probes ranging from three to ten times the prod's probes. Both of these help to increase RAM utilization.
4. **Establish your workload.** Increasing the lists constant for the pgvector index can accelerate your queries (at the expense of a slower build). For instance, for benchmarks with 1,000,000 embeddings, we employed a `lists` constant of 2000 (`number of vectors / 500`) as opposed to the suggested 1000 (`number of vectors / 1000`).
5. **Benchmark your own specific workloads.** Doing this during cache warm-up helps gauge the best value for the `probes` constant, balancing precision with requests per second (RPS).
## Going into production
1. Decide if you are going to use indexes or not. You can skip the rest of this guide if you do not use indexes.
2. Over-provision RAM during preparation. You can scale down in step `5`, but it's better to start with a larger size to get the best results for RAM requirements. (We'd recommend at least 8XL if you're using Supabase.)
3. Upload your data to the database. If you use the [`vecs`](/docs/guides/ai/python/api) library, it will automatically generate an index with default parameters.
4. Run a benchmark using randomly generated queries and observe the results. Again, you can use the `vecs` library with the `ann-benchmarks` tool. Do it with probes set to 10 (default) and then with probes set to 100 or more, so RPS will be lower than 10.
5. Monitor the RAM usage, and save it as a note for yourself. You would likely want to use a compute add-on in the future that has the same amount of RAM that was used at the moment (both actual RAM usage and RAM used for cache and buffers).
6. Scale down your compute add-on to the one that would have the same amount of RAM used at the moment.
7. Repeat step 3 to load the data into RAM. You should see RPS increase on subsequent runs, and stop when it no longer increases. Then repeat the benchmark with probes set to a higher value if you haven't already performed it for that compute add-on size.
8. Run a benchmark using real queries and observe the results. You can use the `vecs` library for that as well with `ann-benchmarks` tool. Set probes to 10 (default) and then gradually increase/decrease probes until you see that both precision and RPS match your requirements.
9. If you want higher RPS and you don't expect to have frequent inserts and reindexing, you can increase `lists` constantly. You have to rebuild the index with a higher lists value and repeat steps 6-7 to find the best combination of `lists` and `probes` constants to achieve the best RPS and precision values. Higher `lists` mean that index will build slower, but you can achieve better RPS and precision. Higher probes mean that select queries will be slower, but you can achieve better precision.
## Useful links
Don't forget to check out the general [Production Checklist](/docs/guides/platform/going-into-prod) to ensure your project is secure, performant, and will remain available for your users.
You can look at our [Choosing Compute Add-on](/docs/guides/ai/choosing-compute-addon) guide to get a basic understanding of how much compute you might need for your workload.
Or take a look at our [pgvector 0.4.0 performance](https://supabase.com/blog/pgvector-performance) blog post to see what pgvector is capable of and how the above technique can be used to achieve the best results.
<div>
<img
alt="multi database"
className="dark:hidden"
src="/docs/img/ai/going-prod/size-to-rps--light.png"
/>
<img
alt="multi database"
className="hidden dark:block"
src="/docs/img/ai/going-prod/size-to-rps--dark.png"
/>
</div>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -1
View File
@@ -51,7 +51,7 @@ In the Settings page, look for the **Sitekey** section and copy the key.
## Enable Captcha protection for your Supabase project
Navigate to the **[Authentication](https://supabase.com/dashboard/project/_/settings/auth)** page in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.
Navigate to the **[Auth](https://supabase.com/dashboard/project/_/settings/auth)** section of your Project Settings in the Supabase Dashboard and find the **Enable Captcha protection** toggle under the **Security and Protection** section.
![supabase_auth_general_settings.png](/docs/img/guides/auth-captcha/supabase_auth_general_settings.png)
@@ -45,6 +45,32 @@ To guard against this:
The user should be brought to a page on your site where they can confirm the action by clicking a button.
The button should contain the actual confirmation link which can be obtained from parsing the `confirmation_url={{ .ConfirmationURL }}` query parameter in the URL.
### Email Tracking
If you are using an external email provider that enables "email tracking", the links inside the Supabase email templates will be overwritten and won't perform as expected. We recommend disabling email tracking to ensure email links are not overwritten.
### Redirecting the user to a server-side endpoint
If you intend to use [Server-side rendering](/docs/guides/auth/server-side-rendering), you might want the email link to redirect the user to a server-side endpoint to check if they are authenticated before returning the page. However, the default email link will redirect the user after verification to the redirect URL with the session in the query fragments. Since the session is returned in the query fragments by default, you won't be able to access it on the server-side.
You can customize the email link in the email template to redirect the user to a server-side endpoint successfully. For example:
```html
<a href="https://api.example.com/v1/authenticate?token_hash={{ .TokenHash }}&type=invite"
>Accept the invite
</a>
```
When the user clicks on the link, the request will hit `https://api.example.com/v1/authenticate` and you can grab the `token_hash` and `type` query parameters from the URL. Then, you can call the [`verifyOtp`](/docs/reference/javascript/auth-verifyotp) method to get back an authenticated session before redirecting the user back to the client. Since the `verifyOtp` method makes a `POST` request to Supabase Auth to verify the user, the session will be returned in the response body, which can be read by the server. For example:
```js
const { token_hash, type } = Object.fromEntries(new URLSearchParams(window.location.search))
const { data: { session }, error } = supabase.auth.verifyOtp({ token_hash, type })
// subsequently redirect the user back to the client
// ...
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -92,7 +92,7 @@ Options are available via `queryParams`:
prompt: 'consent',
hd: 'domain.com',
}}
onlyThirdPartyProviders={true}
onlyThirdPartyProviders
/>
```
@@ -854,24 +854,6 @@ See [refreshing session example](/docs/guides/auth/auth-helpers/nextjs#managing-
- [Protected Routes](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/[id]/page.tsx)
- [Conditional Rendering in Client Components with SSR](https://github.com/supabase/supabase/tree/master/examples/auth/nextjs/app/login-form.tsx)
## Frequently asked questions
<AuthHelpersFAQ />
### OAuth sign in isn't redirecting on the server side
The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
```ts
import { NextResponse } from "next/server";
...
const { data } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
return NextResponse.redirect(data.url)
```
## Migration Guide
### Migrating to v0.7.X
@@ -745,24 +745,6 @@ export default function Index() {
> Ensure you have [enabled replication](https://supabase.com/dashboard/project/_/database/replication) on the table you are subscribing to.
## Frequently asked questions
<AuthHelpersFAQ />
### OAuth sign in isn't redirecting on the server side
The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
```ts
import { redirect } from "@remix-run/node"; // or cloudflare/deno
...
const { data } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
return redirect(data.url)
```
## Migration Guide
### Migrating to v0.2.0
@@ -749,24 +749,6 @@ export const actions = {
}
```
## Frequently asked questions
<AuthHelpersFAQ />
### OAuth sign in isn't redirecting on the server side
The reason behind this limitation is that the auth helpers library lacks a direct mechanism for performing server-side redirects, as each framework handles redirects differently. However, the library does offer a URL through the data property it returns, which should be utilized for the purpose of redirection.
```ts
import { redirect } from '@sveltejs/kit';
...
const { data } = await supabase.auth.signInWithOAuth({
provider: 'github',
})
throw redirect(303, data.url)
```
## Migration Guide [#migration]
### Migrate to 0.10
+11 -4
View File
@@ -687,17 +687,24 @@ json_query_path(auth.jwt(), '$.amr[0]')
authentication method in the JWT.
Once you have extracted the most recent entry in the array, you can compare the
`method` and `timestamp` to enforce stricter rules.
`method` and `timestamp` to enforce stricter rules. For instance, you can mandate that access will be only be granted on a table to users who have recently signed in with a password.
Currently recognized methods are:
Currently recognized authentication methods are:
- `oauth` - any OAuth based sign in (social login).
- `password` - any password based sign in.
- `otp` - any one-time password based sign in (email code, SMS code, magic
link).
- `oauth` - any OAuth based sign in (social login).
- `totp` - a TOTP additional factor.
- `sso/saml` - any Single Sign On (SAML) method.
This list will expand in the future.
The following additional claims are available when using PKCE flow:
- `invite` - any sign in via an invitation.
- `magiclink` - any sign in via magic link. Excludes logins resulting from invocation of `signUp`.
- `email/signup` - any login resulting from an email signup.
- `email_change` - any login resulting from a change in email.
More authentication methods will be added over time as we increase the number of authentication methods supported by Supabase.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -14,12 +14,7 @@ export const meta = {
## Single Page Application (SPA)
### Sending password reset email
Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail)
to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to
the update password page. This URL must be saved in your
allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at
[Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't
redirect the user.
Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user.
```ts
await supabase.auth.resetPasswordForEmail('hello@example.com', {
@@ -28,9 +23,7 @@ await supabase.auth.resetPasswordForEmail('hello@example.com', {
```
### Email link
The email link you receive will work like a magic link. This way when you click the link you will be logged into
the website. Since we passed a redirect URL to the [`.resetPasswordForEmail`](https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail)
method the user should be sent to the update password page.
The email link you receive will work like a magic link. This way when you click the link you will be logged into the website. Since we passed a redirect URL to the [`.resetPasswordForEmail`](https://supabase.com/docs/reference/javascript/auth-resetpasswordforemail) method the user should be sent to the update password page.
### Update Password
To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and pass along the new password to this method.
@@ -42,12 +35,7 @@ await supabase.auth.updateUser({ password: new_password })
## Server-Side Rendering (SSR)
### Sending password reset email
Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail)
to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to
the callback page along with a query parameter to the update password page. This URL must be saved in your
allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at
[Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't
redirect the user.
Supabase provides a convenient method [`.resetPasswordForEmail`](/docs/reference/javascript/auth-resetpasswordforemail) to reset a user password. This method takes a parameter of `redirectTo` which we will use to pass an absolute URL to the callback page along with a query parameter to the update password page. This URL must be saved in your allowed [Redirect URLs](https://supabase.com/dashboard/project/_/auth/url-configuration) list found at [Authentication > Redirect Configuration](https://supabase.com/dashboard/project/_/auth/url-configuration) or it won't redirect the user.
```ts
await supabase.auth.resetPasswordForEmail('hello@example.com', {
@@ -66,6 +54,7 @@ The email link you receive will behave like a magic link. When the link is click
### Exchange authorization code
After redirecting to the server page, we need to retrieve the code from the query parameter called `code` and pass it to the `.exchangeCodeForSession` function.
```ts
// api/auth/callback.ts
@@ -84,6 +73,7 @@ The query parameter is always `code` for the authorization code returned from th
</Admonition>
We will also need to check for the `next` query parameter to redirect the user to the update password page.
```ts
// api/auth/callback.ts
@@ -96,8 +86,7 @@ res.redirect(next)
```
### Update Password
To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and
pass along the new password to this method.
To update the password we call the [`.updateUser`](/docs/reference/javascript/auth-updateuser) method and pass along the new password to this method.
```ts
await supabase.auth.updateUser({ password: new_password })
+39
View File
@@ -0,0 +1,39 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth-smtp',
title: 'Configure a Custom SMTP',
description: 'Moving towards production: Configuring a custom SMTP provider',
}
# Auth SMTP
At present, you can trial the Supabase platform by sending up to **4** emails per hour via the built-in service. The default email service as a whole is offered on a best effort basis: we will do our best to maintain it and will review usage of the service on a regular basis to see if the email service should be continued.
As you progress toward production, you may find yourself wanting for a custom SMTP service in order to increase your limits. A custom SMTP server will allow you to set your own cap on the number of emails sent per hour.
Beyond rate limits, an SMTP server might also help with:
- Deliverability and Reputation Management
- Scalability
- Analytics and Tracking
- Compliance and Anti Spam measures
## How to Set up SMTP
Head over to the [Auth Settings Page](https://supabase.com/dashboard/project/_/settings/auth) and hit "Enable Custom SMTP" under the SMTP Provider section.
Fill in fields below with the relevant details obtained from your custom SMTP provider:
![SMTP settings](/docs/img/guides/auth-smtp/smtp.png)
### SMTP Providers
You can use Supabase Auth with any major SMTP provider of your choosing. Some SMTP providers you could consider using are:
- [Twilio SendGrid](https://docs.sendgrid.com/for-developers/sending-email/integrating-with-the-smtp-api)
- [AWS SES](https://docs.aws.amazon.com/ses/latest/dg/send-email-smtp.html)
- [Resend](https://resend.com/docs/dashboard/emails/introduction)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -7,16 +7,42 @@ export const meta = {
video: 'https://www.youtube.com/v/akScoPO01bc',
}
## Overview
In this guide we'll show you how to authenticate your users with SMS based One-Time Password (OTP) tokens.
There are two reasons to use Supabase SMS OTP tokens:
- You want users to log in with mobile number + password, and the mobile number should be verified via SMS
- You want users to log in with mobile number ONLY (i.e. passwordless login)
- You want users to log in with a phone number and password, and verify the phone number on signup with SMS
- You want users to log in with a phone number ONLY (i.e. passwordless login)
We'll cover:
What you'll need:
- Twilio account ([sign up](https://www.twilio.com/try-twilio))
- Supabase project (create one [here](https://supabase.com/dashboard))
- Mobile phone capable of receiving SMS
SMS Authentication can be done with either Twilio Verify or Twilio Programmable Messaging. [Twilio Verify](https://www.twilio.com/en-us/trusted-activation/verify) is a specialized OTP solution and is recommended for most developers that need over-the-phone authentication. Alternatively you can use [Twilio Programmable Messaging](https://www.twilio.com/docs/messaging) which offers generic SMS sending support.
## Twilio Verify
To set up Twilio Verify, you will need to:
1. Create a new [verification service](https://support.twilio.com/hc/en-us/articles/360033309133-Getting-Started-with-Twilio-Verify-V2) in the Twilio dashboard.
2. [Switch Phone Provider to Twilio Verify](https://supabase.com/dashboard/project/_/auth/providers)
3. Configure the Twilio Verify Service ID field using the Verification Service ID obtained in 1.
When using Twilio Verify, OTPs are generated by Twilio. This means that:
- Unlike other providers, the OTP expiry duration and message content fields are not configurable via the Supabase dashboard. Please head to Twilio Verify to configure these settings.
- The token remains the same during its validity period until the verification is successful. This means if your user make another request within that period, they will receive the same token.
- Twilio Verify has a separate set of rate limits that apply. Visit Twilio's [Rate Limit and Timeouts page](https://www.twilio.com/docs/verify/api/rate-limits-and-timeouts) to find out more.
<Admonition type="caution">
At this time, Twilio Verify is only supported on the `whatsapp` and `sms` channels.
</Admonition>
## Twilio (Programmable Messaging)
In this section we'll cover:
- [Finding your Twilio credentials](#finding-your-twilio-credentials)
- [Using OTP with password based logins](#using-otp-with-password-based-logins)
@@ -349,6 +375,8 @@ let { data, error } = await supabase.auth.verifyOtp({
## Resources
- [Reasons to use Twilio Verify](https://www.twilio.com/blog/9-reasons-to-use-the-verify-api)
- [Twilio Verify Documentation](https://www.twilio.com/docs/verify)
- [Twilio Signup](https://www.twilio.com/try-twilio)
- [Supabase Dashboard](https://supabase.com/dashboard)
- [Supabase Row Level Security](/docs/guides/auth#row-level-security)
@@ -33,7 +33,7 @@ Setting up Facebook logins for your application consists of 3 parts:
<SocialProviderSetup provider="Facebook" />
## Set up FaceBook Login for your Facebook App
## Set up Facebook Login for your Facebook App
From the `Add Products to your App` screen:
+35 -6
View File
@@ -4,12 +4,15 @@ export const meta = {
title: 'Supabase CLI',
description:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
subtitle:
'The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.',
}
The Supabase CLI provides tools to develop your project locally and deploy to the Supabase Platform.
You can also use the CLI to manage your Supabase projects, handle database migrations and CI/CD workflows, and generate types directly from your database schema.
You can use the Supabase CLI to run the entire Supabase stack locally on your machine, simply by running `supabase init` (to create a new local project) and then `supabase start`.
## Installation
The Supabase CLI provides tools to develop your project locally, deploy to the Supabase Platform, handle database migrations, and generate types directly from your database schema.
## Installing the Supabase CLI
<Tabs
scrollable
@@ -74,7 +77,7 @@ and run one of the following:
</TabPanel>
</Tabs>
## Updates
## Updating the Supabase CLI
When a new [version](https://github.com/supabase/cli/releases) is released, you can update the CLI using the same methods.
@@ -121,12 +124,38 @@ npx supabase stop --no-backup
npx supabase start
```
## Running Supabase locally
Inside the folder where you want to create your project, run:
```bash
supabase init
```
This will create a new `supabase` folder. It's safe to commit this folder to your version control system.
Now, to start the Supabase stack, run:
```bash
supabase start
```
This takes time on your first run because the CLI needs to download the local Docker images. The CLI includes the entire Supabase toolset, and a few additional images that are useful for local development (like a local SMTP server and a database diff tool).
The local development environment includes Supabase Studio, a graphical interface for working with your database, running by default on [localhost:54323](http://localhost:54323).
![Local Studio](/docs/img/guides/cli/local-studio.png)
When you are finished working on your Supabase project, you can stop the stack with:
```bash
supabase stop
```
## See also
- [Supabase CLI Reference](/docs/reference/cli/introduction)
- [Supabase CLI Configuration](/docs/reference/cli/config)
- [Local Development](/docs/guides/getting-started/local-development)
- [Managing Environments](/docs/guides/cli/managing-environments)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
@@ -0,0 +1,138 @@
import { CodeHikeConfig, remarkCodeHike } from '@code-hike/mdx'
import { GetStaticPaths, GetStaticProps } from 'next'
import { MDXRemote, MDXRemoteSerializeResult } from 'next-mdx-remote'
import { serialize } from 'next-mdx-remote/serialize'
import { relative } from 'path'
import rehypeSlug from 'rehype-slug'
import remarkGfm from 'remark-gfm'
import codeHikeTheme from 'config/code-hike.theme.json' assert { type: 'json' }
import components from '~/components'
import Layout from '~/layouts/DefaultGuideLayout'
import { UrlTransformFunction, linkTransform } from '~/lib/mdx/plugins/rehypeLinkTransform'
import remarkMkDocsAdmonition from '~/lib/mdx/plugins/remarkAdmonition'
import { removeTitle } from '~/lib/mdx/plugins/remarkRemoveTitle'
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'setup-cli'
const branch = 'gh-pages'
const docsDir = 'docs'
const externalSite = 'https://supabase.github.io/setup-cli'
// Each external docs page is mapped to a local page
const pageMap = [
{
slug: 'generating-types',
meta: {
title: 'Generate types from your database',
description: 'End-to-end type safety across client, server, and database.',
subtitle: 'End-to-end type safety across client, server, and database.',
tocVideo: 'VSNgAIObBdw',
},
remoteFile: 'generating-types.md',
},
]
interface ActionDocsProps {
source: MDXRemoteSerializeResult
meta: {
title: string
description?: string
}
}
export default function ActionDocs({ source, meta }: ActionDocsProps) {
return (
<Layout meta={meta}>
<MDXRemote {...source} components={components} />
</Layout>
)
}
/**
* Fetch markdown from external repo and transform links
*/
export const getStaticProps: GetStaticProps<ActionDocsProps> = async ({ params }) => {
const page = pageMap.find(({ slug }) => slug === params.slug)
if (!page) {
throw new Error(`No page mapping found for slug '${params.slug}'`)
}
const { remoteFile, meta } = page
const response = await fetch(
`https://raw.githubusercontent.com/${org}/${repo}/${branch}/${docsDir}/${remoteFile}`
)
const source = await response.text()
const urlTransform: UrlTransformFunction = (url) => {
try {
const externalSiteUrl = new URL(externalSite)
const placeholderHostname = 'placeholder'
const { hostname, pathname, hash } = new URL(url, `http://${placeholderHostname}`)
// Don't modify a url with a FQDN or a url that's only a hash
if (hostname !== placeholderHostname || pathname === '/') {
return url
}
const relativePage = (
pathname.endsWith('.md')
? pathname.replace(/\.md$/, '')
: relative(externalSiteUrl.pathname, pathname)
).replace(/^\//, '')
const page = pageMap.find(({ remoteFile }) => `${relativePage}.md` === remoteFile)
// If we have a mapping for this page, use the mapped path
if (page) {
return page.slug + hash
}
// If we don't have this page in our docs, link to original docs
return `${externalSite}/${relativePage}${hash}`
} catch (err) {
console.error('Error transforming markdown URL', err)
return url
}
}
const codeHikeOptions: CodeHikeConfig = {
theme: codeHikeTheme,
lineNumbers: true,
showCopyButton: true,
skipLanguages: [],
autoImport: false,
}
const mdxSource = await serialize(source, {
scope: {
chCodeConfig: codeHikeOptions,
},
mdxOptions: {
remarkPlugins: [
remarkGfm,
remarkMkDocsAdmonition,
[removeTitle, meta.title],
[remarkCodeHike, codeHikeOptions],
],
rehypePlugins: [[linkTransform, urlTransform], rehypeSlug],
},
})
return { props: { source: mdxSource, meta } }
}
export const getStaticPaths: GetStaticPaths = async () => {
return {
paths: pageMap.map(({ slug }) => ({
params: {
slug,
},
})),
fallback: false,
}
}
@@ -4,7 +4,9 @@ export const meta = {
id: 'local-development',
title: 'Local Development',
description: 'How to use Supabase on your local development machine.',
video: 'https://www.youtube.com/v/vyHyYpvjaks',
subtitle: 'How to use Supabase on your local development machine.',
video: 'https://www.youtube-nocookie.com/v/vyHyYpvjaks',
tocVideo: 'vyHyYpvjaks',
}
Supabase is a flexible platform that lets you decide how you want to build your projects. You can use the Dashboard directly to get up and running quickly, or use a proper local setup. We suggest you work locally and deploy your changes to a linked project on the [Supabase Platform](https://app.supabase.io/).
@@ -25,23 +27,6 @@ The Dashboard provides a wide range of features for setting up your project: cre
5. **Work offline**: Need to work from a train? A plane? An automobile? No problem. Developing your project locally allows you to work offline.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/vyHyYpvjaks"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Prerequisites
Make sure you have these installed on your local machine:
- [Docker Desktop](https://docs.docker.com/desktop/)
- [Supabase CLI](/docs/guides/cli)
- [Git](https://github.com/git-guides/install-git)
## Log in to the Supabase CLI
```bash
@@ -415,24 +400,24 @@ supabase functions deploy <function_name>
To use Auth locally, update your project's `supabase/config.toml` file that gets created after running `supabase init`. Add any providers you want, and set enabled to `true`.
```bash config.toml
```bash supabase/config.toml
[auth.external.github]
enabled = true
client_id = "env($SUPABASE_AUTH_GITHUB_CLIENT_ID)"
secret = "env($SUPABASE_AUTH_GITHUB_SECRET)"
client_id = "env(SUPABASE_AUTH_GITHUB_CLIENT_ID)"
secret = "env(SUPABASE_AUTH_GITHUB_SECRET)"
redirect_uri = "http://localhost:54321/auth/v1/callback"
```
As a best practice, any secret values should be loaded from environment variables. You can add them to `supabase/.env` for the CLI to automatically substitute them.
As a best practice, any secret values should be loaded from environment variables. You can add them to `.env` file in your project's root directory for the CLI to automatically substitute them.
```bash supabase/.env
```bash .env
SUPABASE_AUTH_GITHUB_CLIENT_ID="redacted"
SUPABASE_AUTH_GITHUB_SECRET="redacted"
```
For these changes to take effect, you need to run `supabase stop` and `supabase start` again.
If you have additional RLS policies defined on your `auth` schema, you can pull them as a migration file locally.
If you have additional triggers or RLS policies defined on your `auth` schema, you can pull them as a migration file locally.
```bash
supabase db remote commit --schema auth
@@ -474,7 +459,7 @@ This will switch the logging drivers and will direct logs to the Analytics serve
## Limitations and considerations
The local development environment is not as feature-complete as the Supabase Platform. We're working towards parity between the hosted platform and the local environment. Here are some of the differences:
The local development environment is not as feature-complete as the Supabase Platform. Here are some of the differences:
- You cannot update your project settings in the Dashboard. This must be done using the local config file.
- The CLI version determines the local version of Studio used, so make sure you keep your local [Supabase CLI up to date](https://github.com/supabase/cli#getting-started). We're constantly adding new features and bug fixes.
@@ -0,0 +1,50 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'managing-config',
title: 'Managing config and secrets',
description: 'Managing local configuration using config.toml.',
}
The Supabase CLI uses a `config.toml` file to manage local configuration. This file is located in the `supabase` directory of your project.
## Config reference
The `config.toml` file is automatically created when you run `supabase start`.
There are a wide variety of options available, which can be found in the [CLI Config Reference](/docs/reference/cli/config).
For example, to enable the "Apple" OAuth provider for local development, you can append the following information to `config.toml`:
```toml
[auth.external.apple]
enabled = false
client_id = ""
secret = ""
redirect_uri = "" # Overrides the default auth redirectUrl.
```
## Using secrets inside config.toml
You can reference environment variables within the `config.toml` file using the `env()` function. This will detect any values stored in an `.env` file at the root of your project directory. This is particularly useful for storing sensitive information like API keys, and any other values that you don't want to check into version control.
For example, if your `.env` contained the following values:
```bash
GITHUB_CLIENT_ID=""
GITHUB_SECRET=""
```
Then you would reference them inside of our `config.toml` like this:
```toml
[auth.external.github]
enabled = true
client_id = "env(GITHUB_CLIENT_ID)"
secret = "env(GITHUB_SECRET)"
redirect_uri = "" # Overrides the default auth redirectUrl.
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -3,37 +3,15 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'managing-environments',
title: 'Managing Environments',
description: 'How to deploy Supabase schema changes with a CI / CD pipeline.',
video: 'https://www.youtube.com/v/rOLyOsBR1Uc',
description: 'Manage multiple environments using Database Migrations and GitHub Actions.',
subtitle: 'Manage multiple environments using Database Migrations and GitHub Actions.',
video: 'https://www.youtube-nocookie.com/v/rOLyOsBR1Uc',
tocVideo: 'rOLyOsBR1Uc',
}
## Overview
This guide shows you how to set up your local Supabase development environment that integrates with GitHub Actions to automatically test and release schema changes to staging and production Supabase projects.
The Supabase CLI provides the tools you need to manage multiple environments.
This guide shows you how to set up your local Supabase development environment that integrates with GitHub Actions to automatically
test and release schema changes to staging and production Supabase projects.
## Prerequisites
Make sure you have these installed on your local machine:
- [Docker Desktop](https://docs.docker.com/desktop/)
- [Supabase CLI](/docs/guides/cli)
- [Git](https://github.com/git-guides/install-git)
To get started:
- Create a [Supabase project](https://supabase.com/dashboard) or use an existing one
- Initialize a local Git repository
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/rOLyOsBR1Uc"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
![Deploy migration](/docs/img/guides/cli/cicd-github.png)
## Set up a local environment
@@ -0,0 +1,24 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'testing-emails-locally',
title: 'Testing emails locally',
description: 'Testing emails for Supabase Auth on your local machine.',
subtitle: 'Testing emails for Supabase Auth on your local machine.',
}
The Supabase CLI uses [Inbucket](https://github.com/inbucket/inbucket) to capture emails sent from your local machine. This is useful for testing emails sent from Supabase Auth.
## Accessing Inbucket
By default, Inbucket is available at [localhost:54324](http://localhost:54324) when you run `supabase start`. Simply open this URL in your browser to view the emails.
## Going into Production
The "default" email provided by Supabase is only for development purposes. It is [heavily restricted](/docs/guides/platform/going-into-prod#auth-rate-limits) to ensure that it is not used for spam.
Before you go into production, you must configure your own email provider. This is as simple as enabling a new SMTP credentials in your [project settings](https://supabase.com/dashboard/project/_/settings/auth).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,35 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'using-environment-variables-in-config',
title: 'Using environment variables in config.toml',
description: 'How to use environment variables in config.toml with the Supabase CLI.',
}
The Supabase CLI is capable of utilizing environment variables stored in our project's root directory's `.env` file within the `config.toml` file.
We can reference the environment variable by using the `env()` function.
Inside of our `.env` file we add the environment variable as we normally would
```bash
GITHUB_CLIENT_ID=""
GITHUB_SECRET=""
```
And then reference them inside of our `config.toml`
```toml
[auth.external.github]
enabled = true
client_id = "env(GITHUB_CLIENT_ID)"
secret = "env(GITHUB_SECRET)"
# Overrides the default auth redirectUrl.
redirect_uri = ""
```
These same environment variables will be referenced by the `supabase start` command from the Supabase CLI.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -38,7 +38,7 @@ Every Supabase project provides a full Postgres database. You can connect to the
## Connection Pooler
Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/docs/guides/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard:
Every Supabase project comes with PgBouncer for connection pooling. A connection pooler is useful for managing a large number of _temporary_ connections. For example, if you are using [Prisma](/partners/integrations/prisma), Drizzle, Kysely, or anything deployed to a Serverless environment (AWS Lambdas or Edge Functions). You can find the connection pool config in the [Database settings](https://supabase.com/dashboard/project/_/settings/database) inside the dashboard:
1. Go to the `Settings` section.
2. Click `Database`.
@@ -267,7 +267,7 @@ psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.
<StepHikeCompact.Details title="Install">
Install Drizzle and releated dependencies.
Install Postgres.js and releated dependencies.
</StepHikeCompact.Details>
@@ -275,7 +275,6 @@ psql "sslmode=verify-full sslrootcert=$HOME/Downloads/prod-supabase.cer host=db.
```shell
npm i postgres
npm i -D drizzle-kit
```
</StepHikeCompact.Code>
+1 -1
View File
@@ -425,7 +425,7 @@ Views provide the several benefits:
#### Simplicity
As a query becomes complex it becomes a hassle to call it. Especially when we run it at regularly. In the example above, instead of repeatedly running:
As a query becomes more complex, it can be a hassle to call it over and over - especially when we run it regularly. In the example above, instead of repeatedly running:
```sql
select
@@ -34,6 +34,31 @@ There are two debugging tools available: Invocations and Logs. Invocations shows
- Search the [Edge Runtime](https://github.com/supabase/edge-runtime) and [CLI](https://github.com/supabase/cli) repos for the error message, to see if it has been reported before.
- If the output from the commands above does not help you to resolve the issue, please open a support ticket via the Supabase Dashboard (by clicking the "Help" button at the top right) and include all output and details about your commands.
# Advanced Techniques
## Checking Function Boot Time
Check the logs for the function. In the logs, look for a "Booted" event and note the reported boot time. If available, click on the event to access more details, including the regions from where the function was served. Investigate if the boot time is excessively high (`higher than 1 second`) and note any patterns or regions where it occurs.
## Finding Bundle Size
To find the bundle size of a function, run the following command locally:
```bash
deno info /path/to/function/index.ts
```
Look for the "size" field in the output which represents an approximated the bundle size of the function.You can find the accurate bundle size when you deploy your function via Supabase CLI. If the function is part of a larger application, consider examining the bundle size of the specific function independently.
## Analyze Dependencies
Review the dependencies listed in the output of the `deno info` command. Pay attention to any significantly large dependencies, as they can contribute to increased bundle size and potential boot time issues.
Examine if there are any unnecessary or redundant dependencies that can be removed. Check for outdated dependencies and recommend updating to the latest versions if applicable. When running deno info make sure to provide the correct path of the import map if you use one.
```bash
deno info --import-map=/path/to/import_map.json /path/to/function/index.ts
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -157,6 +157,13 @@ export const quickstarts = [
'Learn how to create a Supabase project, add some sample data to your database, and query the data from a Flutter app.',
icon: '/docs/img/icons/flutter-icon',
},
{
title: 'Android Kotlin',
href: '/guides/getting-started/quickstarts/kotlin',
description:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.',
icon: '/docs/img/icons/kotlin-icon',
},
{
title: 'SvelteKit',
href: '/guides/getting-started/quickstarts/sveltekit',
@@ -268,6 +275,13 @@ export const mobile = [
'Learn how to build a user management app with Expo and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/expo-icon',
},
{
title: 'Android Kotlin',
href: '/guides/getting-started/tutorials/with-kotlin',
description:
'Learn how to build a product management app with Android and Supabase Database, Auth, and Storage functionality.',
icon: '/docs/img/icons/kotlin-icon',
},
{
title: 'Ionic React',
href: '/guides/getting-started/tutorials/with-ionic-react',
@@ -153,36 +153,36 @@ Manage your projects programmatically. [Docs](/docs/reference/api).
Both Postgres and the Supabase Platform are production-ready. Some tools we offer on top of Postgres are still under development.
| Product | Feature | Stage | Available on self-hosted |
| -------------------------- | ---------------------- | ------- | ------------------------------------------- |
| Database | Postgres | `GA` | ✅ |
| Database | Triggers | `GA` | ✅ |
| Database | Functions | `GA` | ✅ |
| Database | Extensions | `GA` | ✅ |
| Database | Full Text Search | `GA` | ✅ |
| Database | Webhooks | `GA` | ✅ |
| Database | Point-in-Time Recovery | `alpha` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
| Database | Vault | `alpha` | ✅ |
| Studio | | `GA` | ✅ |
| Realtime | Postgres Changes | `GA` | ✅ |
| Realtime | Broadcast | `beta` | ✅ |
| Realtime | Presence | `beta` | ✅ |
| Storage | | `GA` | ✅ |
| Storage | S3 Backend | `GA` | ✅ |
| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Image Transformations | `GA` | ✅ |
| Storage | Resumable Uploads | `beta` | ✅ |
| Edge Functions | | `beta` | 🚧 [Deno Deploy](https://deno.com/deploy) |
| Auth | OAuth Providers | `beta` | ✅ |
| Auth | Passwordless | `beta` | ✅ |
| Auth | Next.js Auth Helpers | `alpha` | ✅ |
| Auth | SvelteKit Auth Helpers | `alpha` | ✅ |
| Auth | Remix Auth Helpers | `alpha` | ✅ |
| Management API | | `beta` | N/A |
| CLI | | `beta` | N/A |
| Client Library: JavaScript | | `GA` | N/A |
| Client Library: Dart | | `beta` | N/A |
| Product | Feature | Stage | Available on self-hosted |
| -------------------------- | ---------------------- | ------ | ------------------------------------------- |
| Database | Postgres | `GA` | ✅ |
| Database | Triggers | `GA` | ✅ |
| Database | Functions | `GA` | ✅ |
| Database | Extensions | `GA` | ✅ |
| Database | Full Text Search | `GA` | ✅ |
| Database | Webhooks | `GA` | ✅ |
| Database | Point-in-Time Recovery | `GA` | 🚧 [wal-g](https://github.com/wal-g/wal-g) |
| Database | Vault | `beta` | ✅ |
| Studio | | `GA` | ✅ |
| Realtime | Postgres Changes | `GA` | ✅ |
| Realtime | Broadcast | `beta` | ✅ |
| Realtime | Presence | `beta` | ✅ |
| Storage | | `GA` | ✅ |
| Storage | S3 Backend | `GA` | ✅ |
| Storage | CDN | `GA` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Smart CDN | `beta` | 🚧 [Cloudflare](https://www.cloudflare.com) |
| Storage | Image Transformations | `GA` | ✅ |
| Storage | Resumable Uploads | `beta` | ✅ |
| Edge Functions | | `beta` | ✅ |
| Auth | OAuth Providers | `GA` | ✅ |
| Auth | Passwordless | `GA` | ✅ |
| Auth | Next.js Auth Helpers | `beta` | ✅ |
| Auth | SvelteKit Auth Helpers | `beta` | ✅ |
| Auth | Remix Auth Helpers | `beta` | ✅ |
| CLI | | `beta` | ✅ Works with self-hosted |
| Management API | | `beta` | N/A |
| Client Library: JavaScript | | `GA` | N/A |
| Client Library: Dart | | `beta` | N/A |
- ✅ = Fully Available
- 🚧 = Available, but requires external tools or configuration
@@ -0,0 +1,305 @@
import Layout from '~/layouts/DefaultGuideLayout'
import StepHikeCompact from '~/components/StepHikeCompact'
export const meta = {
title: 'Use Supabase with Android Kotlin',
subtitle:
'Learn how to create a Supabase project, add some sample data to your database, and query the data from an Android Kotlin app.',
breadcrumb: 'Framework Quickstarts',
}
<StepHikeCompact>
<StepHikeCompact.Step step={1}>
<StepHikeCompact.Details title="Set up a Supabase project">
[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 all tables.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```sql SQL_EDITOR
-- Create products table
create table
public.products (
_id bigint generated by default as identity not null,
productid text not null,
name text null,
description text null,
price real null,
image text null,
category text null,
nutrition text null,
constraint products_pkey primary key (productid),
) tablespace pg_default;
````
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={2}>
<StepHikeCompact.Details title="Create an Android app with Android Studio">
Open Android Studio > New > New Android Project.
</StepHikeCompact.Details>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={3}>
<StepHikeCompact.Details title="Install the Supabase client library">
Import Supabase and all required dependencies. Replace the version placeholders `$supabase_version` and `$ktor_version` with the respective latest versions.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
implementation "io.github.jan-tennert.supabase:postgrest-kt:$supabase_version"
implementation "io.ktor:ktor-client-android:$ktor_version"
implementation "io.ktor:ktor-client-core:$ktor_version"
implementation "io.ktor:ktor-utils:$ktor_version"
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={4}>
<StepHikeCompact.Details title="Install the serializable plugin">
Open the `build.gradle` (app), add the serialization plugin to use annotation for data parsing. Please note that the plugin version should be the same as the Kotlin version in your app.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
plugins {
...
id 'org.jetbrains.kotlin.plugin.serialization' version '$kotlin_version'
...
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={5}>
<StepHikeCompact.Details title="Initialize the Supabase client">
You can create a Supabase client whenever you need to perform an API call. That being said, it is recommended to use a dependency injection library like [Hilt](https://developer.android.com/training/dependency-injection/hilt-android#kts).
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
val client = createSupabaseClient(
supabaseUrl = "https://xyzcompany.supabase.co",
supabaseKey = "public-anon-key"
) {
install(GoTrue)
install(Postgrest)
install(Storage)
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={6}>
<StepHikeCompact.Details title="Create a data transfer object">
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
@Serializable
data class ProductDto(
@SerialName("productid")
val productId: String,
@SerialName("name")
val name: String,
@SerialName("description")
val description: String,
@SerialName("price")
val price: Double,
@SerialName("image")
val image: String,
@SerialName("category")
val category: String,
@SerialName("nutrition")
val nutrition: String,
@SerialName("_id")
val _id: Int,
)
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={7}>
<StepHikeCompact.Details title="Create a domain object">
This kind of object will be consumed by the view.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
data class Product(
val productId: String,
val name: String,
val description: String,
val price: Double,
val image: String,
val category: String,
val nutrition: String,
val _id: Int,
)
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={8}>
<StepHikeCompact.Details title="Query data from the app">
Create a repository to interact with the data source.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
interface ProductRepository {
fun getProducts(): List<ProductDto>
}
class ProductRepositoryImpl @Inject constructor(
private val postgrest: Postgrest,
) : ProductRepository {
override suspend fun getProducts(): List<ProductDto> {
val result = client.postgrest["products"]
.select().decodeList<ProductDto>()
// Handle result data for next step
return result
}
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={9}>
<StepHikeCompact.Details title="Create a module to provide repository">
Use [Hilt](https://developer.android.com/training/dependency-injection/hilt-android) for dependency injection.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
InstallIn(SingletonComponent::class)
@Module
abstract class RepositoryModule {
@Binds
abstract fun bindProductRepository(impl: ProductRepositoryImpl): ProductRepository
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={10}>
<StepHikeCompact.Details title="Get data from ViewModel inside a coroutine scope">
Add the `@Inject` annotation to use the repository in a ViewModel.
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
class ProductListViewModel @Inject constructor(
private val productRepository: ProductRepository
) : ViewModel() {
private val _productList = MutableStateFlow<List<Product>?>(listOf())
val productList: Flow<List<Product>?> = _productList
init {
getProducts()
}
fun getProducts() {
viewModelScope.launch {
val products = productRepository.getProducts()
_productList.emit(products?.map { it -> it.asDomainModel() })
}
}
private fun ProductDto.asDomainModel(): Product {
return Product(
productId = this.productId,
name = this.name,
price = this.price,
image = this.image,
description = this.description,
category = this.category,
nutrition = this.nutrition,
_id = this._id
)
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={11}>
<StepHikeCompact.Details title="Observe data in a Composable">
</StepHikeCompact.Details>
<StepHikeCompact.Code>
```kotlin
@Composable
fun ProductListScreen(
modifier: Modifier = Modifier,
navController: NavController,
viewModel: ProductListViewModel = hiltViewModel(),
) {
val productList = viewModel.productList.collectAsState(initial = listOf()).value
if (!productList.isNullOrEmpty()) {
LazyColumn(
modifier = modifier.padding(24.dp),
contentPadding = PaddingValues(5.dp)
) {
items(productList) { item ->
ProductListItem(
product = item,
modifier = modifier,
onClick = {
navController.navigate(
ProductDetailsDestination.createRouteWithParam(
item.id
)
)
},
)
}
}
}
}
```
</StepHikeCompact.Code>
</StepHikeCompact.Step>
<StepHikeCompact.Step step={12}>
<StepHikeCompact.Details title="Start the app">
</StepHikeCompact.Details>
</StepHikeCompact.Step>
</StepHikeCompact>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
File diff suppressed because it is too large. Load diff
-45
View File
@@ -1,45 +0,0 @@
import Layout from '~/layouts/DefaultLayout'
import Link from 'next/link'
import Image from 'next/image'
import { GlassPanel } from 'ui'
import { integrations } from '~/components/Navigation/NavigationMenu/NavigationMenu.constants'
export const meta = {
title: 'Integrations',
}
Explore a variety of integrations from Supabase partners. Need a different integration? Find a [Supabase expert](https://supabase.com/partners/experts) to help build your next idea.
<div>
{integrations.items.map((item) => {
return ['Overview', 'OAuth Apps (Beta)'].includes(item.name) ? null :
(
<div key={item.name}>
<h2>{item.name}</h2>
<div className="grid grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 gap-6 not-prose">
{item.items?.map((integration) => (
<Link href={`${integration.url}`} key={integration.name} >
<a>
<GlassPanel
title={integration.name}
background={false}
icon={<Image height={40} width={40} className="rounded" alt="" src={`/docs/img/integrations/logos/${integration.name.toLowerCase().replace(/ /g,"_")}_logo.png`}/>}
>
{integration.description}
</GlassPanel>
</a>
</Link>
))}
</div>
</div>
)})}
</div>
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,173 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'appsmith',
title: 'Appsmith',
description:
'Get started with Supabase and Appsmith, an open-source framework for building internal tools.',
}
This guide explains how to quickly build a Support Dashboard by connecting a Supabase back-end to an Appsmith front-end.
[Appsmith](https://www.appsmith.com/) is an open-source framework for building internal tools. It lets you drag-and-drop UI components to build pages, connect to any API, database or GraphQL source and write logic with JavaScript objects.
If you don’t have an Appsmith account, create one [here](https://app.appsmith.com/user/signup).
Let’s get started!
## Step 1: Set up your Backend on Supabase
- On the [Supabase dashboard](https://supabase.com/dashboard), click `New project` and set the name to **Support Dashboard**
![create-project-supabase-01](/docs/img/guides/integrations/appsmith/create-project-supabase-01.png)
- Create a new table by clicking on the Create Table option on the side navigation.
- Supabase provides many ways to add data to the tables, from writing queries to creating schemas using UI to simply uploading CSV files. For our support dashboard, we will be creating the **tickets** table by uploading the [CSV file](https://raw.githubusercontent.com/vihar/datasets/master/tickets.csv) on Supabase.
![create-table-supabase-02](/docs/img/guides/integrations/appsmith/create-table-supabase-02.png)
The database is now set up.
## Step 2: Connect the database to Appsmith
- Note down the database connection information under Project Settings in Supabase.
![project-settings-supabase-03](/docs/img/guides/integrations/appsmith/project-settings-supabase-03.png)
- On Appsmith, create a new application under the dashboard under your preferred organization.
- Click on the `+` icon next to Datasources on the left navigation bar under Page1
- Next, click on Create New tab and choose PostgreSQL datasource, you’ll see the following screenshot:
![create-datasource-appsmith-04](/docs/img/guides/integrations/appsmith/create-datasource-appsmith-04.png)
- Fill out the form to connect to your Supabase instance. Click **Test** to test connection and then **Save** to save the datasource
![connect-supabase-datasource-05](/docs/img/guides/integrations/appsmith/connect-supabase-datasource-05.png)
## Step 3: Build UI on Appsmith
- Click on the + icon next to widgets and drag and drop a Tab widget. We can configure using the property pane by clicking on the cog icon on the top-right corner.
- As seen in the below screenshot, we have added four tabs to support the dashboard.
![property-pane-appsmith-06](/docs/img/guides/integrations/appsmith/property-pane-appsmith-06.png)
- Add widgets to the **Home** tab to create the dashboard as shown in the screenshot below. For eg: **Critical Open Issues** is a **Text** widget and below it is an **Input** widget which we will bind later to display the number of open tickets.
- Set up the **New** button to open a modal which will have a form to raise a new ticket.
![bind-query-appsmith-07](/docs/img/guides/integrations/appsmith/bind-query-appsmith-07.png)
- In the modal widget, add a few widgets to accept input when creating a new ticket. Please refer to the screenshot below.
![modal-appsmith-08](/docs/img/guides/integrations/appsmith/modal-appsmith-08.png)
## Step 4: Writing Queries in Appsmith and binding data to widgets
- Click on the + icon next to Datasources on the navigation bar and click New Query next to the Supabase connection here to create a new query.
![create-query-appsmith-09](/docs/img/guides/integrations/appsmith/create-query-appsmith-09.png)
- Rename the query to create_new_ticket under the query pane; here we can write SQL that can collect the data from the widgets using mustache templates.
```jsx
INSERT INTO PUBLIC."tickets"("id","createdAt","user","updatedAt","description",
"status","priority","category","assignedTo")
VALUES('{{appsmith.store.ticket.id}}','{{moment().format('yyyy-mm-ddHH:MM:ss')}}','{{c_user.text}}',
'{{moment().format('yyyy-mm-ddHH:MM:ss')}}','{{c_description.text}}','{{c_status.selectedOptionValue}}',
'{{c_property.selectedOptionValue}}',
'{{c_category.selectedOptionValue}}','{{c_assignee.selectedOptionValue}}');
```
- Click the **Confirm** button on the modal and under **Events**, set the **onClick** property to execute the create_new_ticket query.
- Create a second query named **get_tickets** that will list all the tickets.
***
```jsx
SELECT * FROM public."tickets";
```
- Drag and drop a table widget under the **Assigned To Me** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
```jsx
{
{
get_tickets.data.filter(
(t) => t.assignedTo === 'confidence@appsmith.com' && t.status !== 'closed'
)
}
}
```
- Drag and drop a table widget under the **Resolved** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
```jsx
{
{
get_tickets.data.filter((t) => t.status === 'open')
}
}
```
- Drag and drop a table widget under the **Closed** tab. Open the property pane and add the following snippet under **Table Data** to bind the query results.
```jsx
{
{
get_tickets.data.filter((t) => t.status === 'closed')
}
}
```
## Step 5: Creating Charts in Appsmith
- On the **Home** tab, click on the first Chart widget. Add Title **Open Issues By Category**. Change the **Chart Type** property to **Column Chart**.
- Update the x-axis and y-axis Labels under **Axis** on the property pane.
- Add the following code snippet under the **Series Data** property to bind the data to be displayed on the x and y axes.
```jsx
[
{
"x": "Hardware",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='hardware').length}}
},
{
"x": "Software",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='software').length}}
},
{
"x": "Other",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.category==='other').length}}
}
]
```
- The second chart will be a pie chart. Add the title, axes labels as mentioned above,
- Add the following code snippet under the **Series Data** property of the pie chart.
```jsx
[
{
"x": "High",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='high').length}}
},
{
"x": "Medium",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='medium').length}}
},
{
"x": "Low",
"y": {{get_tickets.data.filter(t => t.status==='open' && t.priority==='low').length}}
}
]
```
## Resources
- [Appsmith](https://www.appsmith.com/) official website.
- [Appsmith GitHub](https://github.com/appsmithorg).
- [Appsmith](https://docs.appsmith.com/) documentation.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,470 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'auth0',
title: 'Auth0',
description:
'Swap out Supabase authentication with Auth0. Let Auth0 handle tokens and signing users in and out, while Supabase enforces authorization policies with Row Level Security (RLS).',
}
This guide steps through building a Next.js application with Auth0 and Supabase. We configure Auth0 to handle authenticating users and managing tokens, while writing our authorization logic in Supabase - using Row Level Security policies.
> Note: This guide is heavily inspired by the [Using Next.js and Auth0 with Supabase](https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/) article on [Auth0's blog](https://auth0.com/blog/). Check it out for a practical step-by-step guide on integrating Auth0 and Supabase.
The full code example for this guide can be found [here](https://github.com/dijonmusters/supabase-auth0-example).
[Auth0](https://auth0.com/) is an authentication and authorization platform, offering numerous strategies to authenticate and manage users. It provides fine-grain control over how users sign in to your application, the token that is generated, and what data is stored about your users.
[Next.js](https://nextjs.org/) is a web application framework built on top of React. We will be using it for this example, as it allows us to write server-side logic within our application. Auth0 have also written a [very well integrated authentication library](https://www.npmjs.com/package/@auth0/nextjs-auth0) specifically for Next.js.
> Note: API routes (serverless functions) in Next.js closely resemble the structure of Node server frameworks - such as Express, Koa and Fastify. The server-side logic in this guide could easily be refactored in one of these frameworks and managed as a separate application to the front-end.
If you don’t have an Auth0 account, create one [here](https://auth0.com/signup).
You will also need a Supabase account, which can be created by signing in [here](https://supabase.com/dashboard/).
## Step 1: Creating an Auth0 tenant
From the Auth0 dashboard, click the menu to the right of the Auth0 logo, and select `Create tenant`.
![Create tenant from Auth0 dashboard](/docs/img/guides/integrations/auth0/IYzHxeW.png)
Enter a `Domain` for your tenant - this will need to be unique.
Select a `Region` - this should be geographically close to the majority of your users.
Select `Development` for `Environment Tag` - this should be production when you're ready to go live.
![Auth0 tenant settings](/docs/img/guides/integrations/auth0/iSA3E0J.png)
## Step 2: Setting up an Auth0 application
From the sidebar menu, select `Applications` > `Applications` and click `Create Application`.
Give your application a name, select the `Regular Web Applications` option and click `Create`.
![Auth0 application settings](/docs/img/guides/integrations/auth0/ANU4Wez.png)
Select `Settings` and navigate to the `Application URIs` section, and update the following:
`Allowed Callback URLs`: `http://localhost:3000/api/auth/callback`
`Allowed Logout URLs`: `http://localhost:3000`
Scroll to the bottom of the `Settings` section and reveal the `Advanced Settings`.
Select `OAuth` and set `JSON Web Token Signature` to `RS256`.
Confirm `OIDC Conformant` is `Enabled`.
Click `Save` to update the settings.
## Step 3: Creating a Supabase project
From your [Supabase dashboard](https://supabase.com/dashboard/), click `New project`.
Enter a `Name` for your Supabase project.
Enter a secure `Database Password`.
Select the same `Region` you selected for your Auth0 tenant.
Click `Create new project`.
![New Supabase project settings](/docs/img/guides/integrations/auth0/qnmJEU7.png)
## Step 4: Creating data in Supabase
From the sidebar menu in the [Supabase dashboard](https://supabase.com/dashboard/), click `Table editor`, then `New table`.
Enter `todo` as the `Name` field.
Select `Enable Row Level Security (RLS)`.
Create two new columns:
- `title` as `text`
- `user_id` as `text`
- `is_complete` as `bool` with the default value `false`
Click `Save` to create the new table.
![Todo table](/docs/img/guides/integrations/auth0/33kqP4K.png)
From the `Table editor` view, select the `todo` table and click `Insert row`.
Fill out the `title` field and click `Save`.
![New row settings](/docs/img/guides/integrations/auth0/mEhHAWC.png)
Click `Insert row` and add a couple of extra todos.
![List of todos](/docs/img/guides/integrations/auth0/dLOvhdq.png)
## Step 5: Building a Next.js app
Create a new Next.js project:
```bash
npx create-next-app <name-of-project>
```
Create a `.env.local` file and enter the following values:
```
AUTH0_SECRET=any-secure-value
AUTH0_BASE_URL=http://localhost:3000
AUTH0_ISSUER_BASE_URL=https://<name-of-your-tenant>.<region-you-selected>.auth0.com
AUTH0_CLIENT_ID=get-from-auth0-dashboard
AUTH0_CLIENT_SECRET=get-from-auth0-dashboard
NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard
NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard
SUPABASE_JWT_SECRET=get-from-supabase-dashboard
```
> Note: Auth0 values can be found under `Settings > Basic Information` for your application.
![Auth0 settings](/docs/img/guides/integrations/auth0/o07FaoV.png)
> Note: Supabase values can be found under `Settings > API` for your project.
![Supabase settings](/docs/img/guides/integrations/auth0/r1GAfLo.png)
Restart your Next.js development server to read in the new values from `.env.local`.
```bash
npm run dev
```
## Step 6: Install Auth0 Next.js library
Install the `@auth0/nextjs-auth0` library.
```bash
npm i @auth0/nextjs-auth0
```
Create a new file `pages/api/auth/[...auth0].js` and add:
```jsx
// pages/api/auth/[...auth0].js
import { handleAuth } from '@auth0/nextjs-auth0'
export default handleAuth()
```
> Note: This will create a few API routes for us. The main ones we will use are `/api/auth/login` and `/api/auth/logout` to handle signing users in and out.
Open `pages/_app.js` and wrap our `Component` with the `UserProvider` from Auth0:
```jsx
// pages/_app.js
import React from 'react'
import { UserProvider } from '@auth0/nextjs-auth0/client'
const App = ({ Component, pageProps }) => {
return (
<UserProvider>
<Component {...pageProps} />
</UserProvider>
)
}
export default App
```
Update `pages/index.js` to ensure the user is logged in to view the landing page.
```jsx
// pages/index.js
import styles from '../styles/Home.module.css'
import { withPageAuthRequired } from '@auth0/nextjs-auth0'
import Link from 'next/link'
const Index = ({ user }) => {
return (
<div className={styles.container}>
<p>
Welcome {user.name}!{' '}
<Link href="/api/auth/logout">
<a>Logout</a>
</Link>
</p>
</div>
)
}
export const getServerSideProps = withPageAuthRequired()
export default Index
```
> Note: `withPageAuthRequired` will automatically redirect the user to `/api/auth/login` if they are not currently logged in.
Test this is working by navigating to `http://localhost:3000` which should redirect you to an Auth0 sign in screen.
![Auth0 sign in screen](/docs/img/guides/integrations/auth0/xLRL7S7.png)
Either `Sign up` for a new account, or click `Continue with Google` to sign in.
You should now be able to view the landing page.
![Landing page](/docs/img/guides/integrations/auth0/YdBKRy6.png)
## Step 7: Sign Auth0 token for Supabase
Currently, neither Supabase or Auth0 allow for a custom signing secret to be set for their JWT. They also use different [signing algorithms](https://auth0.com/docs/configure/applications/signing-algorithms).
Therefore, we need to extract the bits we need from Auth0's JWT, and sign our own to send to Supabase.
We can do that using Auth0's `afterCallback` function, which gets called anytime the user authenticates.
Install the `jsonwebtoken` library.
```bash
npm i jsonwebtoken
```
Update `pages/api/auth/[...auth0].js` with the following:
```jsx
// pages/api/auth/[...auth0].js
import { handleAuth, handleCallback } from '@auth0/nextjs-auth0'
import jwt from 'jsonwebtoken'
const afterCallback = async (req, res, session) => {
const payload = {
userId: session.user.sub,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
session.user.accessToken = jwt.sign(payload, process.env.SUPABASE_JWT_SECRET)
return session
}
export default handleAuth({
async callback(req, res) {
try {
await handleCallback(req, res, { afterCallback })
} catch (error) {
res.status(error.status || 500).end(error.message)
}
},
})
```
Our `payload` for the JWT will contain our user's unique identifier from Auth0 - `session.user.sub` and an expiry of 1 hour.
We are signing this JWT using Supabase's signing secret, so Supabase will be able to validate it is authentic and hasn't been tampered with in transit.
> Note: We need to sign the user out and back in again to run the `afterCallback` function, and create our new token.
Now we just need to send the token along with the request to Supabase.
## Step 8: Requesting data from Supabase
Create a new file called `utils/supabase.js` and add the following:
```jsx
// utils/supabase.js
import { createClient } from '@supabase/supabase-js'
const getSupabase = (access_token) => {
const options = {}
if (access_token) {
options.global = {
headers: {
Authorization: `Bearer ${access_token}`,
},
}
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
options
)
return supabase
}
export { getSupabase }
```
This will be our client for talking to Supabase. We can pass it an `access_token` and it will be attached to our request.
Let's load our `todos` from Supabase in our landing page!
```jsx
// pages/index.js
import styles from '../styles/Home.module.css'
import { withPageAuthRequired } from '@auth0/nextjs-auth0'
import { getSupabase } from '../utils/supabase'
import Link from 'next/link'
import { useEffect } from 'react'
const Index = ({ user }) => {
const [todos, setTodos] = useState([])
const supabase = getSupabase(user.accessToken)
useEffect(() => {
const fetchTodos = async () => {
const { data } = await supabase.from('todo').select('*')
setTodos(data)
}
fetchTodos()
}, [])
return (
<div className={styles.container}>
<p>
Welcome {user.name}!{' '}
<Link href="/api/auth/logout">
<a>Logout</a>
</Link>
</p>
{todos?.length > 0 ? (
todos.map((todo) => <p key={todo.id}>{todo.content}</p>)
) : (
<p>You have completed all todos!</p>
)}
</div>
)
}
export const getServerSideProps = withPageAuthRequired()
export default Index
```
Alternatively, we could fetch todos on the server using the `getServerSideProps` function.
```jsx
// pages/index.js
import styles from '../styles/Home.module.css'
import { withPageAuthRequired, getSession } from '@auth0/nextjs-auth0'
import { getSupabase } from '../utils/supabase'
import Link from 'next/link'
const Index = ({ user, todos }) => {
return (
<div className={styles.container}>
<p>
Welcome {user.name}!{' '}
<Link href="/api/auth/logout">
<a>Logout</a>
</Link>
</p>
{todos?.length > 0 ? (
todos.map((todo) => <p key={todo.id}>{todo.content}</p>)
) : (
<p>You have completed all todos!</p>
)}
</div>
)
}
export const getServerSideProps = withPageAuthRequired({
async getServerSideProps({ req, res }) {
const {
user: { accessToken },
} = await getSession(req, res)
const supabase = getSupabase(accessToken)
const { data: todos } = await supabase.from('todo').select('*')
return {
props: { todos },
}
},
})
export default Index
```
Either way, when we reload our application, we are still getting the empty state for todos.
![Empty todo list](/docs/img/guides/integrations/auth0/XgEMwnN.png)
This is because we enabled Row Level Security, which blocks all requests by default. To enable our user to select their `todos` we need to write a policy.
## Step 9: Write a policy to allow select
Our policy will need to know who our currently logged in user is to determine whether or not they should have access. Let's create a PostgreSQL function to extract the current user from our new JWT.
Navigate back to the Supabase dashboard, select `SQL` from the sidebar menu, and click `New query`. This will create a new query called `new sql snippet`, which will allow us to run any SQL against our Postgres database.
Write the following and click `Run`.
```sql
create or replace function auth.user_id() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
```
This will create a function called `auth.user_id()`, which will inspect the `userId` field of our JWT payload.
> Note: To learn more about PostgreSQL functions, check out [our deep dive video](https://www.youtube.com/watch?v=MJZCCpCYEqk).
Let's create a policy that checks whether this user is the owner of the todo.
Select `Authentication` from the Supabase sidebar menu, click `Policies`, and then `New Policy` on the `todo` table.
![Create new policy](/docs/img/guides/integrations/auth0/M7XyhHe.png)
From the modal, select `Create a policy from scratch` and add the following.
![Policy settings for SELECT](/docs/img/guides/integrations/auth0/wuWz3am.png)
This policy is calling the function we just created to get the currently logged in user's ID `auth.user_id()` and checking whether this matches the `user_id` column for the current `todo`. If it does, then it will allow the user to select it, otherwise it will continue to deny.
Click `Review` and then `Save policy`.
> Note: To learn more about RLS and policies, check out [our deep dive video](https://www.youtube.com/watch?v=Ow_Uzedfohk).
The last thing we need to do is update the `user_id` columns for our existing `todos`.
Head back to the Supabase dashboard, and select `Table editor` from the sidebar.
![User ID null in Supabase Table Editor](/docs/img/guides/integrations/auth0/dLOvhdq.png)
Each of our `user_id` columns are set to `NULL`!
To get the ID for our Auth0 user, head over to the Auth0 dashboard, select `User Management` from the sidebar, click `Users` and select your test user.
![List of users in Auth0 dashboard](/docs/img/guides/integrations/auth0/GdXS013.png)
Copy their `user_id`.
![User ID in Auth0 dashboard](/docs/img/guides/integrations/auth0/tbvd0Uj.png)
Update each row in Supabase.
![User ID set to Auth0 user](/docs/img/guides/integrations/auth0/tPu4Tt8.png)
Now when we refresh our application, we should finally see our list of `todos`!
> Note: Check out [the repo](https://github.com/dijonmusters/supabase-auth0-example/blob/main/pages/index.js) for an example of writing new `todos` to Supabase.
## Resources
- [Auth0](https://auth0.com/) official website.
- [Auth0 blog](https://auth0.com/blog/).
- [Using Next.js and Auth0 with Supabase article](https://auth0.com/blog/using-nextjs-and-auth0-with-supabase/).
- [Auth0 community](https://community.auth0.com/).
- [Auth0 documentation](https://auth0.com/docs/).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,557 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'authsignal',
title: 'Authsignal',
description:
'Add an MFA step after sign-in. Use Supabase Auth to sign in with email and password and Authsignal to initiate an Authenticator App challenge.',
}
This guide shows how to integrate [Authsignal](https://www.authsignal.com/) with [Next.js](https://nextjs.org/) and [Supabase](https://supabase.com/) in order to add an MFA step after sign-in.
The user flow is as follows:
1. The user enters their email and password to sign in
2. If the user has set up MFA, they're prompted to complete an MFA challenge (via Authenticator App) in order to complete sign-in
3. If the user has not set up MFA, they're signed in immediately and will see a button to set up MFA
The approach uses a temporary encrypted cookie to ensure that the Supabase auth cookies (`access_token` and `refresh_token`) are only set if the MFA challenge was successful. Session data is encrypted using [@hapi/iron](https://hapi.dev/family/iron).
The full code version of this example can be found [here](https://github.com/authsignal/supabase-example).
A live demo can be found [here](https://authsignal-supabase-example.vercel.app).
## How it works
1. A sign-in form posts email and password to the Next.js API route `/api/sign-in`
2. The `signIn` API route calls the Supabase client's `signInWithEmail` method and gets back a session object
3. The `signIn` API route then calls the Authsignal client's `track` method to determine if an MFA challenge is required
4. If a challenge is required, the `signIn` API route saves the session object in a temporary encrypted cookie and redirects to Authsignal
5. Once the challenge is completed, Authsignal redirects back to `/api/callback` which retrieves the session and sets the Supabase auth cookies
6. The `callback` API route then redirects to the index page which is protected with Supabase's `withPageAuth` wrapper around `getServerSideProps`
## Step 1: Configuring an Authsignal tenant
Go to the [Authsignal Portal](https://portal.authsignal.com) and create a new project and tenant.
You will also need to [enable at least one authenticator for your tenant](https://portal.authsignal.com/organisations/tenants/authenticators) - for example Authenticator Apps.
Finally, to configure the sign-in action to always challenge, go [here](https://portal.authsignal.com/actions/signIn/rules) and set the default action outcome to `CHALLENGE` and click save.
![Authsignal settings](https://raw.githubusercontent.com/authsignal/supabase-example/main/authsignal-settings.png)
## Step 2: Creating a Supabase project
From your [Supabase dashboard](https://supabase.com/dashboard/), click `New project`.
Enter a `Name` for your Supabase project and enter or generate a secure `Database Password`, then click `Create new project`.
Once your project is created go to `Authentication -> Settings -> Auth Providers` and ensure `Enable Email provider` is checked and that `Confirm Email` is unchecked.
![Supabase settings](https://raw.githubusercontent.com/authsignal/supabase-example/main/supabase-settings.png)
## Step 3: Building a Next.js app
Create a new Next.js project:
```bash
npx create-next-app --typescript supabase-authsignal-example
cd supabase-authsignal-example
```
Create a `.env.local` file and enter the following values:
```sh
NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard
NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard
AUTHSIGNAL_SECRET=get-from-authsignal-dashboard
TEMP_TOKEN_SECRET=this-is-a-secret-value-with-at-least-32-characters
```
Supabase values can be found under `Settings > API` for your project.
Authsignal values can be found under `Settings > API Keys` for your tenant.
`TEMP_TOKEN_SECRET` is used to encrypt the temporary cookie. Set it to a random 32 character length string.
Restart your Next.js development server to read in the new values from `.env.local`.
```bash
npm run dev
```
## Step 4: Installing dependencies
Install the Supabase client and Auth helpers for Next.js:
```bash
npm install @supabase/supabase-js @supabase/auth-helpers-nextjs
```
Install the Authsignal Node.js client:
```bash
npm install @authsignal/node
```
Finally install 2 packages to help encrypt and serialize session data in cookies:
```bash
npm install @hapi/iron cookie
npm install --save-dev @types/cookie
```
## Step 5: Initializing the Authsignal client
Add the following code to `/lib/authsignal.ts`:
```ts
import { Authsignal } from '@authsignal/node'
const secret = process.env.AUTHSIGNAL_SECRET
if (!secret) {
throw new Error('AUTHSIGNAL_SECRET is undefined')
}
const redirectUrl = 'http://localhost:3000/api/callback'
export const authsignal = new Authsignal({ secret, redirectUrl })
```
The `redirectUrl` here is a Next.js API route which Authsignal will redirect back to after an MFA challenge. We'll implement this below.
## Step 6: Managing session data in cookies
Next we will add some helper functions for managing cookies:
- `setTempCookie` encrypts and serializes the Supabase session data and sets it in a temporary cookie
- `getSessionFromTempCookie` decrypts and parses this session data back from the cookie
- `setAuthCookie` sets the Supabase auth cookies (`access_token` and `refresh_token`) and clears the temporary cookie
Add the following code to `/lib/cookies.ts`:
```ts
import Iron from '@hapi/iron'
import { Session } from '@supabase/supabase-js'
import { parse, serialize } from 'cookie'
import { NextApiRequest, NextApiResponse } from 'next'
export async function setTempCookie(session: Session, res: NextApiResponse) {
const token = await Iron.seal(session, TEMP_TOKEN_SECRET, Iron.defaults)
const cookie = serialize(TEMP_COOKIE, token, {
maxAge: session.expires_in,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
})
res.setHeader('Set-Cookie', cookie)
}
export async function getSessionFromTempCookie(req: NextApiRequest): Promise<Session | undefined> {
const cookie = req.headers.cookie as string
const cookies = parse(cookie ?? '')
const tempCookie = cookies[TEMP_COOKIE]
if (!tempCookie) {
return undefined
}
const session = await Iron.unseal(tempCookie, TEMP_TOKEN_SECRET, Iron.defaults)
return session
}
export function setAuthCookie(session: Session, res: NextApiResponse) {
const { access_token, refresh_token, expires_in } = session
const authCookies = [
{ name: ACCESS_TOKEN_COOKIE, value: access_token },
refresh_token ? { name: REFRESH_TOKEN_COOKIE, value: refresh_token } : undefined,
]
.filter(isDefined)
.map(({ name, value }) =>
serialize(name, value, {
maxAge: expires_in,
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
path: '/',
sameSite: 'lax',
})
)
// Also clear the temp cookie
const updatedCookies = [...authCookies, serialize(TEMP_COOKIE, '', { maxAge: -1, path: '/' })]
res.setHeader('Set-Cookie', updatedCookies)
}
const isDefined = <T>(value: T | undefined): value is T => !!value
const TEMP_TOKEN_SECRET = process.env.TEMP_TOKEN_SECRET!
const TEMP_COOKIE = 'as-mfa-cookie'
const ACCESS_TOKEN_COOKIE = 'sb-access-token'
const REFRESH_TOKEN_COOKIE = 'sb-refresh-token'
```
## Step 7: Building the UI
We will add some form components for signing in and signing up as well as a basic home page.
Add the following code to `/pages/sign-up.tsx`:
```ts
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function SignUpPage() {
const router = useRouter()
return (
<main>
<form
onSubmit={async (e) => {
e.preventDefault()
const target = e.target as typeof e.target & {
email: { value: string }
password: { value: string }
}
const email = target.email.value
const password = target.password.value
await fetch('/api/sign-up', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
}).then((res) => res.json())
router.push('/')
}}
>
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" required />
<label htmlFor="password">Password</label>
<input id="password" type="password" name="password" required />
<button type="submit">Sign up</button>
</form>
<div>
{'Already have an account? '}
<Link href="sign-in">
<a>Sign in</a>
</Link>
</div>
</main>
)
}
```
Then add the following code to `/pages/sign-in.tsx`:
```ts
import Link from 'next/link'
import { useRouter } from 'next/router'
export default function SignInPage() {
const router = useRouter()
return (
<main>
<form
onSubmit={async (e) => {
e.preventDefault()
const target = e.target as typeof e.target & {
email: { value: string }
password: { value: string }
}
const email = target.email.value
const password = target.password.value
const { state, mfaUrl } = await fetch('/api/sign-in', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ email, password }),
}).then((res) => res.json())
if (state === 'CHALLENGE_REQUIRED') {
window.location.href = mfaUrl
} else {
router.push('/')
}
}}
>
<label htmlFor="email">Email</label>
<input id="email" type="email" name="email" required />
<label htmlFor="password">Password</label>
<input id="password" type="password" name="password" required />
<button type="submit">Sign in</button>
</form>
<div>
{"Don't have an account? "}
<Link href="sign-up">
<a>Sign up</a>
</Link>
</div>
</main>
)
}
```
Now we will use Supabase's `withPageAuth` wrapper around `getServerSideProps` to make the home page require authentication via SSR. Replace the existing code in `/pages/index.tsx` with the following:
```ts
import { getUser, User, withPageAuth } from '@supabase/auth-helpers-nextjs'
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { authsignal } from '../lib/authsignal'
interface Props {
user: User
isEnrolled: boolean
}
export const getServerSideProps: GetServerSideProps<Props> = withPageAuth({
redirectTo: '/sign-in',
async getServerSideProps(ctx) {
const { user } = await getUser(ctx)
const { isEnrolled } = await authsignal.getUser({ userId: user.id })
return {
props: { user, isEnrolled },
}
},
})
export default function HomePage({ user, isEnrolled }: Props) {
const router = useRouter()
return (
<main>
<section>
<div> Signed in as: {user?.email}</div>
<button
onClick={async (e) => {
e.preventDefault()
const { mfaUrl } = await fetch('/api/mfa', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ isEnrolled }),
}).then((res) => res.json())
window.location.href = mfaUrl
}}
>
{isEnrolled ? 'Manage MFA settings' : 'Set up MFA'}
</button>
<button onClick={() => router.push('/api/sign-out')}>Sign out</button>
</section>
</main>
)
}
```
Optional: To make things look a bit nicer, you can add the following to `/styles/globals.css`:
```css
main {
min-height: 100vh;
display: flex;
flex: 1;
flex-direction: column;
justify-content: center;
align-items: center;
}
section,
form {
display: flex;
flex-direction: column;
min-width: 300px;
}
button {
cursor: pointer;
font-weight: 500;
line-height: 1;
border-radius: 6px;
border: none;
background-color: #24b47e;
color: #fff;
padding: 0 15px;
height: 40px;
margin: 10px 0;
transition: background-color 0.15s, color 0.15s;
}
input {
outline: none;
font-family: inherit;
font-weight: 400;
background-color: #fff;
border-radius: 6px;
color: #1d1d1d;
border: 1px solid #e8e8e8;
padding: 0 15px;
margin: 5px 0;
height: 40px;
}
a {
color: #24b47e;
cursor: pointer;
}
```
## Step 8: Adding the API routes
Now we'll replace the existing api routes in `/pages/api/` with 5 new routes:
- `/sign-in.ts`: handles signing in with Supabase and initiating the MFA challenge with Authsignal
- `/sign-up.ts`: handles signing up with Supabase
- `/sign-out.ts`: clears the Supabase auth cookies and signs the user out
- `/mfa.ts`: handles the user's attempt to set up MFA or to manage their existing MFA settings
- `/callback.ts`: handles completing the MFA challenge with Authsignal
Add the following code to `/pages/api/sign-in.ts`:
```ts
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextApiRequest, NextApiResponse } from 'next'
import { authsignal } from '../../lib/authsignal'
import { setAuthCookie, setTempCookie } from '../../lib/cookies'
export default async function signIn(req: NextApiRequest, res: NextApiResponse) {
const { email, password } = req.body
const { data, error } = await supabaseClient.auth.api.signInWithEmail(email, password)
if (error || !data?.user) {
return res.send({ error })
}
const { state, url: mfaUrl } = await authsignal.track({
action: 'signIn',
userId: data.user.id,
})
if (state === 'CHALLENGE_REQUIRED') {
await setTempCookie(data, res)
} else {
setAuthCookie(data, res)
}
res.send({ state, mfaUrl })
}
```
Then to handle new sign-ups add the following to `/pages/api/sign-up.ts`:
```ts
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
import { Session } from '@supabase/supabase-js'
import { NextApiRequest, NextApiResponse } from 'next'
import { setAuthCookie } from '../../lib/cookies'
export default async function signUp(req: NextApiRequest, res: NextApiResponse) {
const { email, password } = req.body
const { data, error } = await supabaseClient.auth.api.signUpWithEmail(email, password)
if (error || !isSession(data)) {
res.send({ error })
} else {
setAuthCookie(data, res)
res.send({ data })
}
}
const isSession = (data: any): data is Session => !!data?.access_token
```
To clear the auth cookies on sign-out add the following to `/pages/api/sign-out.ts`:
```ts
import { supabaseClient } from '@supabase/auth-helpers-nextjs'
import { NextApiRequest, NextApiResponse } from 'next'
export default async function signOut(req: NextApiRequest, res: NextApiResponse) {
supabaseClient.auth.api.deleteAuthCookie(req, res, { redirectTo: '/sign-in' })
}
```
To handle the user's actions to set up MFA or manage their existing MFA settings, add the following to `/pages/api/mfa.ts`:
```ts
import { getUser, withApiAuth } from '@supabase/auth-helpers-nextjs'
import { NextApiRequest, NextApiResponse } from 'next'
import { authsignal } from '../../lib/authsignal'
export default withApiAuth(async function mfa(req: NextApiRequest, res: NextApiResponse) {
if (req.method !== 'POST') {
return res.status(405).send({ message: 'Only POST requests allowed' })
}
const { user } = await getUser({ req, res })
const { isEnrolled } = req.body
const { url: mfaUrl } = await authsignal.track({
action: isEnrolled ? 'manageSettings' : 'enroll',
userId: user.id,
redirectToSettings: isEnrolled,
})
res.send({ mfaUrl })
})
```
Because the user should be authenticated with Supabase to set up or manage MFA, we can use Supabase's `withApiAuth` wrapper to protect this route.
The `redirectToSettings` param specifies whether the user should be redirected to the MFA page settings panel after a challenge, rather than redirecting them immediately back to the application.
Finally we need a route to handle the redirect back from Authsignal after an MFA challenge. Add the following to `/pages/api/callback.ts`:
```ts
import { NextApiRequest, NextApiResponse } from 'next'
import { authsignal } from '../../lib/authsignal'
import { getSessionFromTempCookie, setAuthCookie } from '../../lib/cookies'
export default async function callback(req: NextApiRequest, res: NextApiResponse) {
const token = req.query.token as string
const { success } = await authsignal.validateChallenge({ token })
if (success) {
const session = await getSessionFromTempCookie(req)
if (session) {
setAuthCookie(session, res)
}
}
res.redirect('/')
}
```
That's it! You should now be able to sign up a new user and set up MFA.
Then if you sign out, you'll be prompted to complete an MFA challenge when signing back in again.
## Resources
- To learn more about Authsignal take a look at the [API Documentation](https://docs.authsignal.com/).
- You can customize the look and feel of the Authsignal Prebuilt MFA page [here](https://portal.authsignal.com/organisations/tenants/customizations).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,86 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'bracket',
title: 'Bracket',
description:
'Set up two-way syncs between Supabase and familiar tools like Airtable, Google Sheets, and Notion.',
}
This guide explains how to set up a sync between your Supabase database and tools like Salesforce, Hubspot, Google Sheets, Airtable, or Notion using [Bracket](https://usebracket.com) — a two-way data syncing tool.
With customizable two-way and one-way syncs, Bracket enables business teams to read from & write to their Supabase database without ever leaving a spreadsheet or CRM. If you don’t have a Bracket account, you can [create one here](https://app.usebracket.com).
This guide assumes you have a Supabase account and database. You do not need existing tables in Supabase if you are replicating data from a SaaS tool into Supabase. Otherwise, an existing table in Supabase is required.
We’ll go through an example using Supabase and Airtable.
## Step 1: Connect Bracket to Supabase
To connect Supabase to Bracket, you'll first need to pull the credentials for your Supabase database:
1. In the Supabase dashboard, go to the settings page using the gear icon on the bottom left, and open up your **Database** settings. In the **Connection info** section, you'll find the credentials required to connect to Bracket.
![Supabase dashboard](/docs/img/guides/integrations/bracket/001_supabase_dashboard.png)
2. On the Bracket web app, decide whether you want Supabase as your primary or secondary source. You should choose Supabase as your primary source if 1) you’re syncing one-way from Supabase, or 2) you’re syncing two ways, but want edits made in Supabase to win any merge conflicts with the secondary source. For the rest of this example, we’ll use the Supabase database as the primary source.
![Choose new source](/docs/img/guides/integrations/bracket/002_bracket_choose_new_source.png)
3. Choose Postgres as your source in the dropdown
![Connect Postgres](/docs/img/guides/integrations/bracket/003_bracket_connect_postgres.png)
4. Use the credentials you found in the Supabase dashboard under Database settings to fill out this section in the Bracket web app
![Select Postgres data](/docs/img/guides/integrations/bracket/004_select_postgres_data.png)
5. Either select a full table or write a SQL query. Note that regardless of which option you choose, you must have a primary key with unique constraints.
## Step 2: Connect Bracket to your Secondary Source
1. Select the Secondary Source and use the OAuth flow to grant limited and secure syncing access to your Airtable base, Notion database, or Google Sheet. For this example, we’ll assume an Airtable base.
2. Select an existing Airtable table to sync, or generate a table from scratch using an existing Postgres table.
## Step 3: Map fields and configure the sync
1. Choose the direction you want data to be syncing (one-way vs. two-way)
2. Map each Supabase field to each Airtable field.
1. Each field can only be mapped to one field in the other source
2. You don’t need to sync all fields over for a sync to work
![Map fields](/docs/img/guides/integrations/bracket/005_map_fields.png)
3. Name your sync and set the sync frequency. After that, you’re ready to start syncing!
## Step 4: Get syncing
1. Click _Test Run_ to ensure that Supabase and Airtable are connected to each other via Bracket. Note: this doesn’t sync data, it only tests that they can connect to each other properly
2. Click _Run Once_ to get data synced over for the first time. If there are errors, you can view them in the *Run History* section by clicking the errors link
3. If there are no errors, go ahead and turn on the sync by clicking the *Active/Inactive* toggle. Once the toggle is on, the _Run history_, _Field mapping_, and _Advanced_ sections cannot be clicked or edited. In order to interact or make changes to these sections, first turn the sync toggle off
![Sync overview](/docs/img/guides/integrations/bracket/006_sync_overview.png)
## Build-a-table
1. If you start with data in Supabase, you can auto-generate an Airtable table with the same fields and easily sync data between the two. You can also do the reverse if Airtable is your primary source!
2. If you generate a Supabase table from an existing Airtable, you can find it in the Table Editor on the Supabase dashboard.
## Roles
If you want to limit Bracket’s permissions to a specific role within Supabase, the roles must meet the following minimum RLS permissions:
1. If syncing one way from Supabase: `SELECT` permissions on all tables synced with Bracket
2. If syncing one way to Supabase or syncing two ways _with deletes disabled_: `SELECT`, `INSERT`, and `UPDATE` permissions on all tables synced with Bracket
3. If syncing one way to Supabase or syncing two ways _with deletes enabled_: `ALL` permissions on all tables synced with Bracket
## Resources
- [Bracket official website](https://www.usebracket.com/)
- [Bracket console](https://app.usebracket.com/)
- [Bracket docs](https://docs.usebracket.com/introduction)
- [Bracket: Postgres + Airtable sync tutorial](https://www.youtube.com/watch?v=Fv4QD7JMYqY&lc=Ugy-IbAKaPMjRnISLZJ4AaABAg)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,191 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'clerk',
title: 'Clerk',
description:
'This guide explains how to connect your Supabase database with Clerk, a powerful authentication provider built for the modern web.',
}
This guide explains how to connect your Supabase database with [Clerk](https://clerk.com), an authentication provider built for the modern web.
Clerk authenticates users, manages session tokens, and provides user management functionality that can be used in combination with the authorization logic available in Supabase through PostgreSQL Row Level Security (RLS) policies.
This guide assumes you have a Supabase account and database project already set up.
If you don't have a Clerk account, you can [create one now](https://dashboard.clerk.com/sign-up).
## Step 1: Create JWT template
The first step is to create a new Clerk application from your Clerk Dashboard if you haven't done so already. You can choose whichever authentication strategy and social login providers you prefer. For more information, check out Clerk's [guide](https://clerk.com/docs/authentication/set-up-your-application).
After your Clerk application has been created, use the lefthand menu to navigate to the **JWT Templates** page.
Click on the button to create a new template based on Supabase.
![Create Supabase JWT template from Clerk dashboard](/docs/img/guides/integrations/clerk/01_supabase-template.png)
This will pre-populate the default claims required by Supabase. You can include additional claims or modify them as necessary. [Shortcodes](https://clerk.com/docs/request-authentication/jwt-templates#shortcodes) are also available for adding dynamic values.
ℹ️ Note the name of the JWT template (which you can change) because this will be needed later.
![JWT template claims](/docs/img/guides/integrations/clerk/02_jwt-claims.png)
## Step 2: Sign JWT with Supabase secret
Supabase requires JWTs be signed with the HS256 signing algorithm and use their signing key. Find the JWT secret key in your Supabase project under **Settings** > **API** in the **Config** section.
![Sign with Supabase secret](/docs/img/guides/integrations/clerk/03_jwt-secret.png)
Click to reveal the JWT secret, copy it, and then paste it in the Signing key field in the Clerk JWT template.
![Paste signing key](/docs/img/guides/integrations/clerk/04_signing-key.png)
After the key is added, click the **Apply Changes** button to save your template.
## Step 3: Configure client
The next step is to configure your client. Supabase provides an official [JavaScript/TypeScript client library](https://github.com/supabase/supabase-js) and there are [libraries in other languages](/docs/reference/javascript/installing) built by the community.
This guide will use a Next.js project with the JS client as an example, but the mechanism of setting the authentication token should be similar with other libraries and frameworks.
Assuming a Next.js application, set the following environment variables in an `.env.local` file:
```bash
NEXT_PUBLIC_CLERK_FRONTEND_API=your-frontend-api
NEXT_PUBLIC_SUPABASE_URL=your-supabase-url
NEXT_PUBLIC_SUPABASE_KEY=your-supabase-anon-key
```
**Note**: If you're using Create React App, replace the `NEXT_PUBLIC` prefix with `REACT_APP`
Your Clerk Frontend API can be found on the [API Keys](https://dashboard.clerk.com/last-active?path=api-keys) screen.
![Clerk Frontend API](/docs/img/guides/integrations/clerk/05_clerk-frontend-api.png)
To get the ones needed for Supabase, navigate to the same Settings > API page as before and locate the anon public key and URL.
![Supabase keys](/docs/img/guides/integrations/clerk/06_supabase-keys.png)
**Note**: It is recommended that you enable [Row Level Security](/docs/guides/auth/row-level-security) (RLS) for your database tables and configure access policies as needed.
After setting those three environment variables, you should be able to start up your application development server.
Install the JavaScript client for Supabase with:
```bash
npm install @supabase/supabase-js
```
Initialize the Supabase client by passing it the environment variables.
This can be saved to a common file, for example as `lib/supabaseClient.js`
```jsx
import { createClient } from '@supabase/supabase-js'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL
const supabaseKey = process.env.NEXT_PUBLIC_SUPABASE_KEY
export const supabase = createClient(supabaseUrl, supabaseKey)
export default supabase
```
## Step 4: Set up Clerk Provider
Install the latest Clerk Next.js SDK by running the following:
```bash
npm install @clerk/nextjs@next
```
**Note**: There is also a Clerk library for [React](https://github.com/clerkinc/javascript/tree/main/packages/react) and [React Native with Expo](https://github.com/clerkinc/javascript/tree/main/packages/expo).
After the package is installed, wrap your application with the `<ClerkProvider />` component.
In a Next.js application, this is typically done in `pages/_app.js`:
```jsx
import { ClerkProvider } from '@clerk/nextjs'
function MyApp({ Component, pageProps }) {
return (
<ClerkProvider>
<Component {...pageProps} />
</ClerkProvider>
)
}
export default MyApp
```
## Step 5: Set auth token with Supabase
In order to access the custom JWT, you can use the `getToken` function returned by the Clerk `useAuth` hook and pass it the name of your template (hopefully you remembered from earlier).
**Note**: The `getToken({ template: <your-template-name> })` call is asynchronous and returns a Promise that needs to be resolved before accessing the token value. This token is short-lived for better security and should be called before every request to your Supabase backend. The caching and refreshing of the token is handled automatically by Clerk.
Call `supabase.auth.setAuth(token)` to override the JWT on the current client. The JWT will then be sent to Supabase with all subsequent network requests.
```jsx
import { useAuth } from '@clerk/nextjs'
import supabase from '../lib/supabaseClient'
export default function Home() {
const { getToken } = useAuth()
const fetchData = async () => {
// TODO #1: Replace with your JWT template name
const token = await getToken({ template: 'supabase' })
supabase.auth.setAuth(token)
// TODO #2: Replace with your database table name
const { data, error } = await supabase.from('your_table').select()
// TODO #3: Handle the response
}
return (
<button type="button" onClick={fetchData}>
Fetch data
</button>
)
}
```
## Access user ID in RLS policies
It is common practice to need access to the user identifier on the database level, especially when working with RLS policies in Postgres. Although Supabase provides a special function `auth.uid()` to extract the user ID from the JWT, this does not work with Clerk. The workaround is to write a custom SQL function to read the `sub` property from the JWT claims.
In the **SQL Editor** section of the Supabase dashboard, click New Query and enter the following:
```sql
create or replace function requesting_user_id()
returns text
language sql stable
as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'sub', '')::text;
$$;
```
This will create a `requesting_user_id()` function that can be used within an RLS policy.
For example, this policy would check that the user making the request is authenticated and matches the `user_id` column of a todos table.
## Access user ID in table column
If you would like the requesting user ID from the JWT to automatically populate a text type column in your database table, you can set the **Default Value** field to the previously defined `requesting_user_id()` function.
![Set requesting_user_id() as default value](/docs/img/guides/integrations/clerk/07_requesting-user-id.png)
## Resources
- [Clerk + Supabase starter repo](https://github.com/clerkinc/clerk-supabase-starter)
- [Next.js + Supabase + Clerk tutorial](https://clerk.com/blog/nextjs-supabase-todos-with-multifactor-authentication)
- [Clerk guide for Next.js Authentication](https://clerk.com/docs/nextjs/get-started-with-nextjs)
- [Clerk Community Discord channel](https://discord.com/invite/b5rXHjAg7A)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,108 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'cloudflare-workers',
title: 'Cloudflare Workers',
description:
"Using Supabase from your Cloudflare Workers just got even easier.",
}
Using Supabase in Cloudflare Workers has always been a great way to interact with your data from the edge. Supabase-js communicates with your Supabase Postgres instance via HTTP using PostgREST, so you never need to worry about running out of database connections.
In this guide we'll walk you through a new addition to the Cloudflare Workers dashboard - the ability to authenticate directly with your Supabase account, and automatically inject your Supabase environment variables into your Worker code.
## How To Enable Supabase Integration in Cloudflare Workers
Start by heading to the [Cloudflare Dashboard](https://dash.cloudflare.com), go to the Workers & Pages tab and hit 'Create Application' followed by 'Create Worker'.
![Cloudflare Dashboard 2](/docs/img/guides/integrations/cloudflare-integration/2.png)
Deploy the Hello World example Worker. Once it's deployed hit 'Configure Worker'.
![Cloudflare Dashboard 3](/docs/img/guides/integrations/cloudflare-integration/3.png)
On the configuration page select the Settings tab, followed by the Integrations option.
You should now see the database integration options. On the Supabase card, click 'Add Integration'
![Cloudflare Dashboard 4](/docs/img/guides/integrations/cloudflare-integration/4.png)
After reviewing and accepting the terms, you will be shown the option to connect and a Supabase popup should appear.
Follow the flow by selecting your Supabase Org and the Project you wish to connect to. If you don't have any projects yet, head over to the [Supabase Dashboard](https://supabase.com/dashboard) to create one.
![Cloudflare Dashboard 5](/docs/img/guides/integrations/cloudflare-integration/5.png)
Once it's connected you will be given the option to select which Supabase Key you want to pull into the Worker context.
The `Anon` key here is one that always adhires to the Database's RLS policies (read more on [Row Level Security](https://supabase.com/docs/guides/auth/row-level-security)).
The `Service Role` is typically ok to use in backend contexts, such as Cloudflare Workers, but note that this key **bypasses your Row Level Security policies**, and has the ablity to read, write, and delete any data in your database.
![Cloudflare Dashboard 6](/docs/img/guides/integrations/cloudflare-integration/6.png)
Once this is done the `SUPABASE_KEY` and `SUPABASE_URL` environment variables will now be available from your Cloudflare Worker code.
![Cloudflare Dashboard 7](/docs/img/guides/integrations/cloudflare-integration/7.png)
You can now install the supabase-js client in your Worker:
`npm install @supabase/supabase-js`
Then you can initiate the Supabase client, and start querying your data:
```javascript
import { createClient } from '@supabase/supabase-js'
export default {
async fetch(request, env) {
const supabase = createClient(env.SUPABASE_URL, env.SUPABASE_KEY)
const { data, error } = await supabase.from('countries').select('*')
if (error) throw error
return new Response(JSON.stringify(data), {
headers: {
'Content-Type': 'application/json',
},
})
},
}
```
The snippet above assumes you already have a `countries` table. Run the following in the [SQL Editor in the Supabase Dashboard](https://supabase.com/dashboard/project/_/sql) if you wish to install this demo schema:
```sql
create table countries (
id serial primary key,
name varchar(255) not null
);
insert into countries
(name)
values
('Oceania');
insert into countries
(name)
values
('Genovia');
insert into countries
(name)
values
('Wakanda');
insert into countries
(name)
values
('Lilliput');
```
Remember that you don't need to use supabase-js to connect to your Supabase database, you can connect "directly" to the underlying Postgres database using the connection string (every Supabase database comes pre-installed with a [connection pooler](https://supabase.com/docs/guides/database/connecting-to-postgres#connection-pool)), or you can try Cloudflare's new [TCP socket method of connecting to Postgres](https://blog.cloudflare.com/workers-tcp-socket-api-connect-databases/) directly from Cloudflare Workers.
- [Cloudflare Integration Docs](https://developers.cloudflare.com/workers/learning/integrations/databases/#supabase).
- [Cloudflare Dashboard](dash.cloudflare.com/).
- [Cloudflare Integration Announcement](https://blog.cloudflare.com/announcing-database-integrations/).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,203 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'dhiwise',
title: 'DhiWise',
description:
'Get started with Supabase and DhiWise. Convert your Figma designs into Flutter apps, store data, and authenticate your users',
}
This guide explains how to connect Supabase backend to DhiWise Flutter application quickly.
[DhiWise](https://www.dhiwise.com/) is a Developer tool to convert Figma designs into React and Flutter applications. It lets you quickly integrate Databases and APIs into your React and Flutter Apps.
If you don't have a DhiWise account, create one [here](https://app.dhiwise.com).
DhiWise supports easy Supabase Integration in just five steps.
Let's get started!
## Step 1: SignIn to Supabase
Go to [Supabase](https://supabase.com/), Click `Sign In`, and create a new account by authenticating with **GitHub**. If you already have an account, you will be logged in.
## Step 2: Create a new project in Supabase
Click on `New project` from the Dashboard and select an organization. If you don't have an organization, create one using `+ New organization.`
- Give your Supabase project a `name.`
- Enter a secure `Database Password.`
- Choose the `region` where your app's backend is hosted.
- Click `Create new project.`
![New Project](/docs/img/guides/integrations/dhiwise/newProj.png)
## Step 3: Find the API key and URL
Once your project is created, you can access the API Key and URL string, Or if you already have an account go to your `organization-> app-> settings-> API`.
![auth keys](/docs/img/guides/integrations/dhiwise/authKeys.png)
## Step 4: Integrations
There are two ways you can integrate Supabase into your DhiWise Flutter applications.
### Authentication
You can integrate `Supabase Email/Password SignUp` or `Supabase Email/Password SignIn` on your components.
- Open the screen of your flutter application
- Go to the component on which you want to add authentication
- on the `onClick` method - select `authentication`
- From the list, If you want SignUp - select `SignUp with Email/Password`; otherwise, select `SignIn with Email/Password` from Supabase Auth section
![Auth](/docs/img/guides/integrations/dhiwise/auth.gif)
And that's it. Supabase authentication will be added to the selected component.
After downloading the application source code,
1. Add Supabase URL and Supabase public key inside **_lib/core/utils/initial_bindings_** file.
2. For additional details, refer ***https://supabase.com/docs/guides/with-flutter***
### Working with Data
When you first integrate Supabase in your DhiWise Flutter application, You will be asked to add [Supabase auth key and URL](##step-3-find-the-api-key-and-url). When you add them, all the tables available in your Supabase project will be synced in DhiWise. You can integrate Select and Create queries on your Flutter screen for a particular table in DhiWise.
### Select records
![Create](/docs/img/guides/integrations/dhiwise/select.png)
#### **Step 1:**
Select the screen from the screen list where you want to integrate Supabase.
#### **Step 2:**
Next, go to the view where you want to add Integration, and from the suggestion box for the `onClick` property, choose `Supabase integration,` which will take you to the Integration screen. Where you will be asked to `Enter function name.` Enter the name of your function and click `Submit.`
#### **Step 3:**
After submitting the function name, you will be asked to select a type of Supabase integration. To retrieve data from Supabase, choose `select.`
#### **Step 4:**
Next, select the table from which you want to fetch records from the listed Tables.
#### **Step 5:**
Select the type of integration
| Type | Description |
| ------------ | ------------------------------------------------- |
| **Single** | Used to fetch a single record from the database. |
| **Multiple** | Used to fetch multiple records from the database. |
<Admonition type="info">
For Multiple types, you need to set `data limit,` `order by, and `order.`
</Admonition>
#### **Step 6:**
You will be redirected to the API Integration screen, where you can set request and response.
For request binding, the below types are supported. Also, Select the operator for comparison before moving forward.
| Type | Description |
| ----------------------- | ---------------------------------------------------------- |
| **View** | Select any component from your screen. |
| **Constant** | Select a constant you've created in your app. |
| **Get from preference** | Select the key you want to fetch from preference. |
| **Navigation argument** | Select data that's been passed from one screen to another. |
For response binding, the below types are supported.
| Type | Description |
| ---------------------- | ------------------------------------- |
| **View** | Select any component from the screen. |
| **Save to preference** | Storing the data to preference. |
#### **Select 7:**
`Handle action` - Select the action you wish to take once the Supabase call has either been accepted successfully or refused due to an error.
Available action for On success and On error are,
1. [Show Alert](https://docs.dhiwise.com/docs/flutter/show-alert)
2. [Navigation](https://docs.dhiwise.com/docs/flutter/navigation)
#### **Step 8:**
Finally, you have added Supabase to your application to fetch records on your screen!
<Admonition type="tip" label="Example">
Suppose you want to fetch records from Supabase and populate the item list on your screen. You can integrate Supabase as discussed above and bind the response with your list view.
</Admonition>
<h3> Create records </h3>
#### **Step 1:**
Choose the screen you wish to integrate Supabase for from the list of screens.
#### **Step 2:**
Next, switch to the component you want to add Integration, and on the `onClick` property, choose `Supabase integration,` which will take you to its integration screen, where you will be asked to **Enter function name**, which will be used in generated code. Enter the name for it and click `Submit`
#### **Step 3:**
After submitting the function name, you will be asked to select a type of Supabase integration. For example, to create a record in Supabase, choose `Create.`
#### **Step 4:**
Next, select the table where you want to create a record from the listed Tables.
#### **Step 5:**
If you want to create a Single record, Select **Select**. Otherwise, **Multiple**.
#### **Step 6:**
Now, you will be redirected to the API Integration screen, where you can set request and response.
For request binding, the below types are supported.
| Type | Description |
| ----------------------- | ---------------------------------------------------------- |
| **View** | Select any component from the screen |
| **Constant** | Select a constant you've created in your app. |
| **Get from preference** | Select the key you want to fetch from preference. |
| **Navigation argument** | Select data that's been passed from one screen to another. |
For response binding, the below types are supported.
| Type | Description |
| ---------------------- | ------------------------------------ |
| **View** | Select any component from the screen |
| **Save to preference** | Storing the data to preference. |
#### **Select 7:**
`Handle action` - Select the action you wish to take once the Supabase call has either been accepted successfully or refused due to an error.
Available action for On success and On error are,
1. [Show Alert](https://docs.dhiwise.com/docs/flutter/show-alert)
2. [Navigation](https://docs.dhiwise.com/docs/flutter/navigation)
#### **Step 9:**
Finally, you have added Supabase to your application to create records from your screen data!
## Resources
- [DhiWise Official Website](https://dhiwise.com)
- [DhiWise Documentation](https://docs.dhiwise.com)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,137 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'directus',
title: 'Directus',
description:
'In this guide, we will show you how to create a Supabase project, install the Directus platform locally and configure the two to connect.',
}
In this guide, we will demonstrate how to create a new Supabase project, install a fresh instance of the Directus platform, then configure the two to work together seamlessly. If you're unfamiliar with either of these systems, don't worry! We'll start off with an overview of each platform and explain how they complement each other, noting any overlap in capabilities.
## Introduction
![Supabase App](/docs/img/guides/integrations/directus/supabase-20220608A.webp)
[Supabase](https://supabase.com/) is an open-source Firebase alternative that provides a PostgreSQL database, storage, authentication, and a dynamic REST API based on your schema. While it is possible to self-host Supabase on your own infrastructure, this article will focus on Supabase Cloud's Free plan, which is the fastest and easiest way to get started.
![Directus App](/docs/img/guides/integrations/directus/directus-20220608A.webp)
[Directus](https://directus.io/) is an open-source data platform that layers on top of any SQL database, providing a powerful suite of tools. The Directus Engine provides dynamic REST and GraphQL APIs based on your schema, hooks and automation, authentication and access control, and file transformations. Directus Studio enables engineers and non-technical users alike to browse, manage, and visualize database content through a no-code app.
}
Supabase is a suite of open-source tools making Postgres databases, file storage, authentication, and edge functions more accessible to developers of all skill levels. Directus is also developer tooling and additionally provides a Data Studio that is safe and intuitive enough for anyone, including non-technical users, to use. This is the crucial bit that gives the two platforms such a strong “network effect.”
When these two systems are brought together, you get a scalable datastore, limitless connectivity options, and a no-code app that allows your technical and business teams to collaborate together efficiently.
The two platforms share an overlap of capabilities that deepens their integration and offers developers the freedom of choice across a broader spectrum of connectivity. Key areas of intersection include:
The ability to generate _powerful_ APIs dynamically to connect data
User management and fine-grained access control
Digital asset storage and management.
More importantly, Directus and Supabase share a common vision for your data, making them quite symbiotic. Both solutions are completely open-source, with self-hosted and cloud deployment options available. They are unopinionated in their approach, with vendor-agnostic data storage, and they both focus on providing a polished developer experience along with comprehensive documentation.
By linking the Supabase database with your Directus Project, _you get a superset of data tools._ You'll benefit from Supabase's Postgres database and its _dev-centric_ admin app with the raw power to run SQL queries, **_as well as_** the Directus no-code app, which enables intuitive permissions-based data access for the whole team.
Let's dive into how we actually set up and link these two platforms to create a modern data stack powerhouse.
## Create a Supabase Project
As mentioned, while you can [deploy Supabase locally](/docs/guides/getting-started/local-development). For the purpose of this guide, we'll use Supabase Cloud:
1. Create a **Supabase** account by signing in with GitHub.
2. Give your organization a name (this can be changed later).
3. Click **New Project** and select your organization.
4. Follow the prompts, setting a project Name, Database Password, Region, and Pricing Plan, then click **Create New Project**.
5. After your project has been provisioned, navigate to **Settings > Database** in the sidebar.
6. Scroll down to **Connection Info** and take note of your database's **Host**, **Database Name**, **Port**, **User**, and **Password**. You will need to enter this during your Directus project setup.
## Optional: Add PostGIS to Support Geometry and Mapping
To take full advantage of the built-in geometry and mapping features Directus offers, we recommend enabling Geometric Data Support. To add PostGIS, follow these steps:
![Enable PostGis](/docs/img/guides/integrations/directus/enable-PostGIS-20220608A.webp)
1. From the sidebar, navigate to **Database > Extensions**.
2. Use the search bar to look up `PostGIS`.
3. Toggle the PostGIS option to enable it.
## Set up Directus
At the time of writing this article, [Directus Cloud](https://directus.cloud/) does not yet support hybrid deployments for connecting an external database. So, we'll be deploying a self-hosted instance to connect with Supabase. To install a self-hosted instance of Directus that's connected to our Supabase project, follow these steps:
1. Run the following command in your terminal:
```bash
npm init directus-project example-project
```
2. Using the up/down arrow keys, select `Postgres` from the list:
```bash
? Choose your database client Postgres
```
3. Next, you will be prompted to input database credentials. Add in the Supabase Database Connection Info noted above as follows:
- **Database Host** – The IP address for your database.
- **Port** – Port number your database is running on.
- **Database Name** – Name of your existing database.
- **Database User** – Name of existing user in database.
- **Database Password** – Password to enter database.
- **Enable SSL** – Select Y for yes or N for no.
- **Root** – The root name.
4. Now, simply set an email and password for your first Directus admin account. To be clear, this is Directus-specific, and is unrelated to your database user:
```bash
Create your first admin user:
? Email: admin@example.com
? Password: ********
```
Once this is complete, you should see details about your new project:
```bash
Your project has been created at <file-path>/example-project.
The configuration can be found in <file-path>/example-project/.env
```
5. Lastly, navigate to your new project folder (in this case `example-project`) and start the platform:
```bash
cd example-project
npx directus start
```
**Please note:** To prevent public accessibility when using the supabase-js library,turn on row level security (RLS) on all these tables inside of the Supabase Dashboard. By default when RLS is turned on these tables cannot be read from or written to with the supabase-js library.
That's it! Your project is now up and running locally. You can access the Directus Studio in the browser via the URL displayed, and log in with the Directus admin credentials you entered above:
```bash
✨ Server started at http://localhost:8055
```
In a matter of minutes, we've created a flexible data backend, with access to an intuitive no-code app for managing and visualizing data along with a robust connectivity toolkit. This modern data stack is flexible and scalable enough to power any data-driven project… all you need to do is build the frontend!
## Next Steps
From here, the sky's the limit on what you can build. You'll probably want to invite some new collaborators to your project and start architecting your data model.
Below are some additional resources to dive in and start exploring these two platforms:
**Directus**
- See the [Directus guides](https://directus.io/guides/).
- Join the Directus community on [Discord](https://directus.chat/).
- Check out the source code on the official [Directus GitHub Repo](https://github.com/directus/directus).
**Supabase**
- Explore the [Supabase documentation](https://supabase.com/docs)
- Join the Supabase community on [Discord](https://discord.supabase.com/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,237 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'draftbit',
title: 'Draftbit',
description: 'Connect your Supabase postgres database to your Low-code mobile app.',
}
This guide explains how to connect a Supabase back-end to a Draftbit front-end and then configure all CRUD operations necessary to build a simple mobile app.
[Draftbit](https://draftb.it/3Fkbask) is a "pro-code" low-code mobile app building platform. Draftbit exports React Native source code that is 100% run on open-source languages and libraries.
Draftbit is back-end agnostic and connects to Supabase via REST API.
> Note: For the demonstration purpose of this guide, we are using a pre-populated database in Supabase. We are calling `Groceries`. To follow along, rename it any way you prefer.
![Prepopulated Database](/docs/img/guides/integrations/draftbit/prepopulated-database.png)
If you don’t have a Draftbit account, create one [here](https://draftb.it/3Fkbask). Once you’ve got your account set up, Create a New App. You can select `Start From a Blank App` for this demo and proceed to the Builder interface.
## Step 1: Get the RESTful endpoint and Project API key
To connect the REST API in the Draftbit app, the following fields are required:
- Base URL of the REST API, which is in the format: `https://<your-domain>.supabase.co/rest/v1` where the `<your-domain` is a unique domain name generated by Supabase.
- The `supabase-key` is the secret key.
You can find these unique values in the API settings of your Supabase account.
- Click the Settings button from the top menu bar.
- In Settings, select **API**.
- In the **Project URL** section, select and copy the URL. It is the Base URL of your Supabase REST API. It will be required to make a connection to the Draftbit app.
- Also, under `Project API keys`, select and copy the API key under `anon`. It is required for every request made to the Supabase database.
![Get Supabase connection string](/docs/img/guides/integrations/draftbit/endpoint.png)
## Step 2: Save Supabase API key as Authorization Header in Draftbit
To authorize your Draftbit app with Supabse, in the builder interface:
- Open the **Settings** tab from the top menu bar.
- In Project Settings, navigate to App Variables.
- Enter a name to access the API Key such as `Authorization_Header`. When making the service connection in the next section, it will be passed as the value for the header `Authorization`.
- The value of this key requires you to enter an authorization token that starts with syntax `Bearer <your-api-key>` (the space between `Bearer` and `<your-api-key>` is required). Click **Add** after adding the value.
- Enter another key name to access the API Key such as `Api_Key_Header`. When making the service connection in the next section, it will be passed as the header `apiKey` value.
- The value of this key requires you to enter an authorization token that starts with syntax is `<your-api-key>`. Click **Add** after adding the value.
- Click **Save** to save these keys and close the modal.
![Add header values in Draftbit](/docs/img/guides/integrations/draftbit/authheader.png)
## Step 3: Add Supabase RESTful endpoint in Draftbit
In your Draftbit builder interface:
- Open the **API & Cloud Services** modal from the top menu bar.
- From the **Connect a service** menu, click on **Rest API**.
- In Step 1: Enter a name for your REST API. Then, paste your `Base URL` (from the first section) into the Base URL field.
- In Step 2: Under **Key** add `Authorization` and `apikey`. Then, under **Value**, select the global variables (from the previous section) to add the actual values for both keys.
- Click Save.
![Create a API service in Draftbit](/docs/img/guides/integrations/draftbit/service.png)
## Making API requests with Supabase & Draftbit
### GET request to Fetch all records
In this section, let's populate a Fetch component with all the data from a simple Supabase and then display the data fetched from the Supabase data table in a List component.
For reference, here is a how the Components tree looks like for this screen:
![Components tree](/docs/img/guides/integrations/draftbit/ctree.png)
The next step is to create an endpoint. Let's try fetching all the data using a `GET` HTTP request. Select the Supabase service in the **API & Cloud Services** modal, and then:
- Click **Add endpoint**.
- In Step 1: enter the name for the endpoint. Make sure the **Method** select is `GET`.
- In Step 2: add the base name path: `/groceries/select=*`, where `groceries` is the table name in Supabase.
- In Step 4: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase.
- Click Save.
![Creating a GET request endpoint](/docs/img/guides/integrations/draftbit/get-request.gif)
In the Builder, on the app screen:
- Select the Fetch component in the Components tree and go to the [Data tab from Properties Panel](doc:introduction-to-the-builder#properties-panel).
- For **Service**, select the name of the Supabase Service.
- For **Endpoint**, select the endpoint you want to fetch the data from.
- Select the List component in the Components and go to the [Data tab from Properties Panel](doc:introduction-to-the-builder#properties-panel). In Data, select `Top-Level Response` from the dropdown menu.
- Then, select the Text component in the Components and then go to the Data tab from the Properties Panel.
- Add a `{{varName}}` value (inside the curly braces) to represent a column field from the Supabase. For example, add `{{title}}` to represent the column name from the Supabase Base.
- Under **Variables**, you will see the variable name defined in the previous step. From the dropdown menu, select the appropriate field that represents the data field.
![Fetching data on app screen](/docs/img/guides/integrations/draftbit/fetchall.gif)
### GET request to fetch single row
Open the **API & Cloud services** modal from the top menu, select the Supabase service, and then:
- Click **Add endpoint**.
- In Step 1: enter a name for the endpoint.
- In Step 2: add the `/groceries/column-name=eq.{{column-name}}` variable. Then, add a Test value for the `{{column-name}}`. For example, it can be the `title` or the `id`.
- In Step 4: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase.
- Click Save.
![Creating endpoint to fetch a single row](/docs/img/guides/integrations/draftbit/getsingle.gif)
On app screen:
- Select the Fetch component in the Components tree and go to the Data tab from Properties Panel
- For **Service**, select the name of the Supabase Service.
- For **endpoint**, select the endpoint you want to fetch the data from.
- Set the value for the `id` in the Configuration > URL Structure section to Navigation > id.
- Select the List component in the Components and go to the Data tab from Properties Panel. In Data, select `Top-Level Response` from the dropdown menu.
- Then, select the Text component in the Components and then go to the Data tab from the Properties Panel.
- Add a `{{varName}}` value (inside the curly braces) to represent the column field from the Supabase. For example, add `{{title}}` to represent the field and value from the Supabase data table.
- Under **Variables**, you will see the variable name defined in the previous step. From the dropdown menu, select the appropriate field that represents the data field.
![Displaying data from single row](/docs/img/guides/integrations/draftbit/fetchsingle.png)
### POST request to submit a new row
Submitting new Data from the Draftbit app to Supabase's REST API requires the request to be sent using the HTTP `POST` method.
For this section, you need to use at least one component that accepts user input and has a Field Name prop to POST data using Supabase REST API.
You can use one of the following components in Draftbit:
- Text Input
- Text Area/Text Field
- Checkbox
- Slider
- Radio Button Group
- Radio Button
In addition, you need a Touchable component like a Button to attach the POST action. After you have created these components, we will create the `POST` endpoint:
- Click **Add endpoint**.
- In Step 1: enter a name for the endpoint and select the Method to `POST`.
- In Step 2: enter the base name as path: `/groceries`.
- In Step 3: add a valid Body structure to submit a POST request. Add one or many `{{variable}}` for test values. Click Body Preview to validate the structure of the Body in the request. For the example, let's create a variable called `{{inputValue}}`.
- In Step 4: to see the new row added to the Supabase data table as JSON response inside the Builder, you have to pass a new header called `Prefer` with its value as `return=representation`.
- In Step 5: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase and click Save.
![Make a POST request to add new data to Supabase database](/docs/img/guides/integrations/draftbit/postrequest.gif)
Once you follow the above steps, you should get a 200 OK response with exactly the new record as a JSON you have entered for your schema.
An example of how Body in a request will look like:
```json
{
"title": {{inputValue}}
}
```
Where `title` is the column name in your Supabase database table.
In Draftbit, using a Touchable or a Button component, you can trigger the action **API Request** to submit the data to the endpoint.
Now, there is a working `POST` request in Draftbit. Map its response to the components on your screen in Draftbit.
First, for each input component, make sure you have set the Field Names (found in the Configs tab, second from the left) to unique values. For example, in the screen below, there is one TextInput field component with the value of the `Field Name` prop of `textInputValue`.
![Field Name prop on a TextInput component](/docs/img/guides/integrations/draftbit/textinput.png)
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
In the API request action:
- In **Service**, select the name to Supabase API Service.
- In **Endpoint**, select the name of the Endpoint.
- Then add the configuration for the body request to be sent by selecting the values for `{{inputValue}}`.
![Setting up the API Request to send a POST request](/docs/img/guides/integrations/draftbit/postapirequest.png)
After completing the above steps, you can trigger the API request to submit new data to the Supabase database.
### PATCH request to Update a new record
Updating an existing record from the Draftbit app to Supabase's REST API requires the request to be sent using the HTTP `PATCH` method.
After you have created your screen components in the Draftbit builder, open the Supabase service and make the `PATCH` endpoint:
- Click **Add endpoint**.
- In Step 1: enter a name for the endpoint and select the Method to `PATCH`.
- In Step 2: enter the base name as path: `/groceries?id=eq.{{id}}`, where `id` is the value of an existing record in the database.
- In Step 3: add a valid Body structure to submit a PATCH request. Add one or many `{{variable}}` for test values depending on the structure of your app. Click Body Preview to validate the structure of the Body in the request. For the example, let's create a variable called `{{inputValue}}`.
- In Step 5: click the **Test** button next to the Endpoint input to verify the response coming from the Supabase and click Save.
![Creating an endpoint for PATCH request](/docs/img/guides/integrations/draftbit/patch.gif)
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
In the API request action:
- In **Service**, select the name to Supabase API Service.
- In **Endpoint**, select the name of the Endpoint.
- Then add the configuration for the query param, and the body request to be sent by selecting the values for `{{inputValue}}`.
![Setting up the API Request to send a PATCH request](/docs/img/guides/integrations/draftbit/patchapirequest.png)
After completing the above steps, you can trigger the API request to update existing data in the Supabase database.
### DELETE request to remove an existing record
The `DELETE` request is to the Supabase with an item's `column-name` to remove that particular record from the table. You can use a [filter from Supabase](https://supabase.io/docs/reference/javascript/using-filters) to filter the value of a specific `column-name`.
After you have created your screen components in the Draftbit builder, open the Supabase service and create the `DELETE` endpoint:
- Click **Add endpoint**.
- In Step 1: enter a name for the endpoint and select the Method to `DELETE`.
- In Step 2: add `/groceries/columnName=eq.{{columnName}}`. Then, add a Test value for the `{{columnName}}`. For example, the `{{columnName}}` here can be `id` of the record.
- In Step 4: click the **Test** button next to the Endpoint input to verify the response from the Supabase.
- Click Save.
![Creating an endpoint for DELETE request](/docs/img/guides/integrations/draftbit/delete.gif)
Next, on your Button component, go to the Interactions tab in the Properties panel located on the far-right-hand side. Select an Action called `API request`.
In the API Request action:
- In **Service**, select the name to Supabase API Service.
- In **Endpoint**, select the name of the Endpoint.
- Then, add the configuration for the query request to be sent by selecting a value. For example, in this case it will be the `id` of the record coming from the Navigation parameter.
![Setting up the API Request to send a DELETE request](/docs/img/guides/integrations/draftbit/deleteapirequest.gif)
## Resources
- [Draftbit](https://draftb.it/3Fkbask) official website.
- [Draftbit Community](https://community.draftbit.com/home).
- [Draftbit](https://docs.draftbit.com/) documentation.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,110 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'estuary',
title: 'Estuary',
description:
'Create a real-time data pipeline connecting Firestore and Supabase to make migration simple.',
}
Estuary Flow is a platform for creating [real-time data pipelines at scale](https://www.estuary.dev/).
It combines the intuitive interface of an ELT service with an event-driven runtime and a variety of open-source connectors.
You can use Flow to migrate your data from Firestore to the Postgres database in your Supabase project.
You do this by building a real-time pipeline that captures data from Firestore and materializes (loads) that data to Postgres.
Once created, the pipeline backfills all your historical data from Firestore and continues to process new data events in real time.
## Prerequisites
Before you begin, you'll need:
- An Estuary account. [Head to the web app to start for free](https://dashboard.estuary.dev).
- For your Firestore database:
- A Google service account with read access to your Firestore database, via [roles/datastore.viewer](https://cloud.google.com/datastore/docs/access/iam). You can assign this role when you [create the service account](https://cloud.google.com/iam/docs/creating-managing-service-accounts#creating), or [add it to an existing service account](https://cloud.google.com/iam/docs/granting-changing-revoking-access#single-role).
- A generated [JSON service account key](https://cloud.google.com/iam/docs/creating-managing-service-account-keys#creating) for the service account.
- A Supabase project
## Step 1: Capture your Firestore data
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 [**Captures** tab](https://dashboard.estuary.dev/captures) of the Flow web app and choose **New Capture**.
2. Locate and select the **Google Firestore** card.
A form appears with the properties required for a Firestore 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`.
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.
5. Click **Next**.
Flow uses the provided configuration to initiate a connection with Firestore. It maps each collection in the Firestore database to a Flow collection.
6. Optionally, use the **Collection Selector** to remove any collections you don't need to migrate to Supabase.
7. Click **Save and Publish**.
You'll see a notification when the capture publishes successfully.
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.
## Step 2: Materialize your collections to Postgres
Next, you'll add a Postgres materialization to connect the captured collections to tables in your Supabase Postgres database.
1. On the **Create Materialization** page, search for and select the **PostgreSQL** tile.
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 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.
4. Click **Next**.
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. 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.
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 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.
## Resources
For more information, visit the [Flow docs](https://docs.estuary.dev/). In particular:
- [Guide to create a Data Flow](https://docs.estuary.dev/guides/create-dataflow/)
- [Firestore capture connector](https://docs.estuary.dev/reference/Connectors/capture-connectors/google-firestore/)
- [Postgres materializaiton connector](https://docs.estuary.dev/reference/Connectors/materialization-connectors/PostgreSQL/)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,34 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'fezto',
title: 'Frontend Zero to One',
description: 'Create an app automatically from your Supabase Postgres using OpenAPI',
video: 'https://www.youtube.com/v/GOC6a0_AlgI',
}
[Frontend Zero to One is](https://www.fezto.xyz) is a service which creates an app for your Supabase Postgres database on-the-fly without any drag and drop, using the OpenAPI spec provided by PostgREST.
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/GOC6a0_AlgI"
frameBorder="0"
allow="accelerometer; encrypted-media; gyroscope; picture-in-picture"
allowfullscreen
></iframe>
</div>
# Setup
In the [Supabase control panel](https://supabase.com/dashboard/), open your project and click the Settings cog icon, and then "API".
You will need:
1. From the Project URL copy the project ID from https://your-project-id.supabase.co
2. From the "Project API keys" section copy the "anon" "public" API Key into the
Paste both into the [FEZTO Supabase setup page](https://www.fezto.xyz/#/supabase) and click Launch.
You can now share and bookmark the browser URL which includes the projectID and anon key with others to launch the same app.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,144 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'flutterflow',
title: 'FlutterFlow',
description:
'FlutterFlow is a low-code tool that allows you to build Flutter apps incredibly fast.',
canonical: 'https://docs.flutterflow.io/actions/actions/backend-database/supabase',
}
<Admonition type="caution">
FlutterFlow and Supabase integration is currently in alpha, and supported features may be limited.
</Admonition>
[FlutterFlow](https://flutterflow.io/) is a low-code builder for developing native mobile applications using Flutter. You can use the simple drag-and-drop interface to build your app faster than traditional development.
This guide gives you a quick overview of implementing basic CRUD operations using FlutterFlow and Supabase. You can find the full docs on FlutterFlow and Supabase [here](https://docs.flutterflow.io/actions/actions/backend-database/supabase).
<div className="video-container">
<iframe
src="https://www.youtube-nocookie.com/embed/hw9Q-NjASbU"
frameBorder="1"
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
allowFullScreen
></iframe>
</div>
## Step 1: Connect FlutterFlow to Supabase
Before we dive into the code, this guide assumes that you have the following ready:
- [Supabase](https://database.new/) project created
- Have setup tables in your Supabase project
- [FlutterFlow](https://app.flutterflow.io/) project created
You can then connect your Supabase project to your FlutterFlow project with the following steps:
1. In your Supabase project, navigate to Project Settings > API. Copy the Project URL.
2. Return to FlutterFlow, navigate to Settings and Integrations > Integrations > Supabase. Turn on the toggle (i.e., enable Supabase) and paste the API URL.
3. Similarly, from the Supabase API section, copy the anon key (under Project API keys) and paste it inside the FlutterFlow > Settings and Integrations > Integrations > Supabase > Anon Key.
4. Click on the Get Schema button. This will show the list of all tables with their schema (structure) created in Supabase.
5. (Optional) If you have defined an Array for any Column Data Type in Supabase, you must set its type here. To do so, tap the "Click to set Array type" and choose the right one.
<video width="99%" muted playsInline controls={true}>
<source
src="https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/flutterflow/connect-flutterflow-to-supabase.mp4"
type="video/mp4"
/>
</video>
## Step 2: Inserting rows
Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget.
1. Select the Widget (e.g., Button) on which you want to define the action.
2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window.
1. Click on + Add Action.
2. On the right side, search and select the Supabase > Insert Row action.
3. Set the Table to your table name (e.g., assignments).
4. Under the Set Fields section, click on the + Add Field button.
5. Click on the Field name and scroll down to find the Value Source dropdown and change it to From Variable.
6. Click on UNSET and select Widget State > Name of the TextField.
7. Similarly, add the field for the other UI elements.
<video width="99%" muted playsInline controls={true}>
<source
src="https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/flutterflow/insert.mp4"
type="video/mp4"
/>
</video>
## Step 3: Selecting and displaying rows
To query a Supabase table on a ListView:
1. Select the ListView widget. Make sure you choose the ListView widget, not the ListTile.
2. Select Backend Query from the properties panel (the right menu), and click Add Backend Query.
3. Set the Query Type to Supabase Query.
4. Select your Table from the dropdown list
5. Set the Query Type to List of Rows.
6. Optional: If you want to display the limited result, say, for example, you have thousands of entries, but you want to display only 100, you can specify the limit.
7. Click Confirm.
<video width="99%" muted playsInline controls={true}>
<source
src="https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/flutterflow/select.mp4"
type="video/mp4"
/>
</video>
## Step 4: Updating rows
Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget.
1. Select the Widget (e.g., Button) on which you want to define the action.
2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window.
1. Click on + Add Action.
2. On the right side, search and select the Supabase > Update Row action.
3. Set the Table to your table name (e.g., assignments).
4. Optional: If you want to get the rows after the update is finished, enable the Return Matching Rows option.
5. Now, you must set the row you want to update. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click + Add Filter button inside the Matching Rows section.
1. Set the Field Name to the field that contains the IDs. Typically, this is the id column.
2. Set the Relation to Equal To because you want to find a row with the exact id.
3. Into the Value Source, you can select the From Variable and provide the id of the row for which you just updated values in the UI.
6. Under the Set Fields section, click on the + Add Field button.
7. Click on the field name.
8. Scroll down to find the Value Source dropdown and change it to From Variable.
9. Click on UNSET and select Widget State > Name of the TextField.
10. Similarly, add the field for the other UI elements.
## Step 5: Deleting rows
Go to your project page on FlutterFlow and follow the steps below to define the Action to any widget.
1. Select the Widget (e.g., Button) on which you want to define the action.
2. Select Actions from the Properties panel (the right menu), and click Open. This will open an Action flow Editor in a new popup window.
1. Click on + Add Action.
2. On the right side, search and select the Supabase -> Delete Row action.
3. Set the Table to your table name (e.g., assignments).
4. Optional: Later, if you want to know which rows were deleted from a table, enable the Return Matching Rows option.
5. Now, you must set the row you want to delete. Usually, this is done by finding a row in a table that matches the current row ID. To do so, click + Add Filter button inside the Matching Rows section.
1. Set the Field Name to the field that contains the IDs. Typically, this is the id column.
2. Set the Relation to Equal To because you want to find a row with the exact id.
3. Into the Value Source, you can select the From Variable and provide the id of the row you want to delete.
<video width="99%" muted playsInline controls={true}>
<source
src="https://obuldanrptloktxcffvn.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/flutterflow/delete.mp4"
type="video/mp4"
/>
</video>
## Resources
You can find more detailed guides on FlutterFlow’s docs.
- [FlutterFlow Supabase available actions](https://docs.flutterflow.io/actions/actions/backend-database/supabase)
- [Retrieving Data from Supabase on FlutterFlow](https://docs.flutterflow.io/data-and-backend/supabase/supabase-database/retrieving-data)
- [Adding data to Supabase DB from FlutterFlow](https://docs.flutterflow.io/data-and-backend/supabase/supabase-database/adding-data)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,53 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'forestadmin',
title: 'ForestAdmin',
description:
'Get started with Supabase and Forest Admin, a tool for automatically generating an Admin Panel without having to build it.',
}
This guide outlines how to instantly generate an Admin Panel on top of your Supabase backend.
[Forest Admin](https://www.forestadmin.com/) offers an off-the-shelf Admin Panel system that can reduce the amount of time and effort needed to create, maintain, and manage internal tools.
It automatically builds a backend API and provides a user-friendly interface to Create, Read, Update and Delete, Search, Segment your data, trigger custom actions, control permissions, and set up workflows on top of your app's data.
![forest-admin-collections](/docs/img/guides/integrations/forestadmin/forest-admin-collections.png)
If you don’t have a Forest Admin account, you can create one in a few minutes [here](https://app.forestadmin.com/signup).
Let’s get started!
## Step 1: Configure your Supabase Backend
If you already have a Supabase project set up, simply go to the Project Settings / Database tab to access the Database Settings and retrieve the connection string (URI tab). This is the only information you will need to connect your Supabase account to Forest Admin.
![supabase-db-credentials](/docs/img/guides/integrations/forestadmin/supabase-db-credentials.png)
If you don't have anything set up on Supabase yet, you can [create a Project](https://supabase.com/dashboard/new/_) in just a few seconds. Once done, just go to the Database tab and create your first table.
![supabase-db-create-table](/docs/img/guides/integrations/forestadmin/supabase-db-create-table.png)
## Step 2: Connect the database to Forest Admin
First, you have to create a new project on Forest Admin:
![forestadmin-create-project](/docs/img/guides/integrations/forestadmin/forestadmin-create-project.png)
Then, you can use Forest Admin's Instant Setup for the Cloud mode to quickly get started. Alternatively, you can host the generated backend admin API on your own (Advanced setup), giving you full control of the backend code. In this guide, we will use the Cloud mode and the Instant Setup.
![forestadmin-hosting](/docs/img/guides/integrations/forestadmin/forestadmin-hosting.png)
Finally, you can enter the database credentials you obtained in Step 1 and set them in Forest Admin by using the Connection URI mode. Don't forget to replace the password in the connection string with the database password you set in Supabase. Note that if you forget it, you can always go to your Supabase Database settings and reset your database password.
![forestadmin-db-credentials](/docs/img/guides/integrations/forestadmin/forestadmin-db-credentials.png)
## You're all done!
There it is, the configuration of Forest Admin is now complete and your admin panel is now ready-to-use with all the features of an admin panel provided out of the box. You can now browse or manipulate all your data in a structured way, use search with support for complex filters, build dashboards, invite your team mates and start collaborating around your business operations and much more.
## Resources
- [Forest Admin](https://www.forestadmin.com/) official website.
- [Forest Admin GitHub](https://github.com/ForestAdmin).
- [Forest Admin](https://docs.forestadmin.com/documentation-portal/) documentation.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,107 +0,0 @@
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://supabase.com/dashboard/projects), click `New project` and set the name to adminPanel.
![Create Supabase Project for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-illa-project.png)
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.
![Create Supabase Table for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-table-1.png)
![Config Supabase Table for ILLA Admin Panel](/docs/img/guides/integrations/illa/supabase-table-2.png)
Fill out the info in the table. The database is now set up.
### Step 2: Build UI on ILLA Cloud
On [ILLA Cloud](https://cloud.illacloud.com/), click Create New to create a new application.
![Create new project on ILLA Builder](/docs/img/guides/integrations/illa/supabase-illa-create-project.png)
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.
![Build UI with ILLA Builder](/docs/img/guides/integrations/illa/supabase-illa-UI.png)
### Step 3: Connect to Supabase and config CRUD
Note down the database connection information under [Project Settings](https://supabase.com/dashboard/project/hdcfnsxpwwgboqomdrhp/settings/database) in Supabase.
![Note information in supabase](/docs/img/guides/integrations/illa/supabase-information.png)
In the Action List, click `+ New` and select Supabase DB.
![Connect ILLA to Supabase](/docs/img/guides/integrations/illa/supabase-illa-connect.png)
Fill out the form to connect to your Supabase instance. Test connection and save resource.
![Config Supabase in ILLA](/docs/img/guides/integrations/illa/supabase-illa-connect-2.png)
Click `Create Action` to create an action with the Supabase resource and config your CRUD.
![Select Supabase resource in ILLA](/docs/img/guides/integrations/illa/supabase-illa-select.png)
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:
![Show Supabase data on ILLA components](/docs/img/guides/integrations/illa/supabase-illa-show-data.png)
## 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
@@ -1,163 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'keyri',
title: 'Keyri',
description:
'QR authentication for an easy and flexible biometric solution across all platforms.',
video: 'https://www.youtube.com/v/jrjrcpc2PFQ',
}
Keyri can be used to incorporate sign-in-with-QR functionality into your Supabase app, allowing users to scan a QR code on your web app with your mobile app and be instantly logged into the web app without having to input any credentials.
Configuration is split into Web and Mobile components. On web, the Keyri QR Widget needs to be installed along with an event listener, and in your mobile app, install the Keyri SDK and pass into it the user's refresh token when sign-in-with-QR is initiated. When the refresh token lands in your web app, it's passed into Supabase's `setSession()` method.
# Sign up for Keyri
First make a free account on the Keyri dashboard ([https://app.keyri.com](https://app.keyri.com)). On Add Your Application, set a name and input the domain on which your app will eventually be deployed. You can create multiple application in Keyri to account for your development, staging, and production environments
![](https://archbee-image-uploads.s3.amazonaws.com/FQ4YmCkDokMJylbTAsoOR/HvTIja3KfgKUIMiNVKAqP_screen-shot-2022-10-13-at-21524-pm.png)
Note your application key from the Keys and Credentials section - this will be used in the Mobile portion of the implementation
![](https://archbee-image-uploads.s3.amazonaws.com/FQ4YmCkDokMJylbTAsoOR/KnD6LkWs-PUDtTS1sT9Rz_screen-shot-2022-10-13-at-21746-pm.png)
# Web
<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.
Next, embed KeyriQR.html in your login page as an iFrame within the desired div. This serves as the widget that displays the dynamic QR code and connects with the Keyri API.
```html
<div>
<iframe
title='KeyriQR'
src='/KeyriQR.html'
id='qr-frame'
height='300'
width='300'
scrolling='no'
style={{ border: 'solid 5px white' }}
></iframe>
</div>
```
Next, for the same login view, set up an event listener to pick up the session token that the iFrame emits when the QR code is scanned by your app.
```javascript
useEffect(() => {
window.addEventListener('message', async (evt) => {
if (evt.data.keyri && evt.data.data && document.location.origin == evt.origin) {
const { data } = evt;
if (!data.error) {
let refresh_token = JSON.parse(data.data).refreshToken;
await handleQrLogin(refresh_token);
} else if (data.error) {
console.log(`Keyri error: ${data.message}`);
}
}
});
```
That's it!
# Mobile
<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
First, install the Flutter SDK, found at flutter.dev
Make sure to add Flutter to your PATH, for example:&#x20;
```shell
export PATH="$PATH:`pwd`/flutter/bin"
```
### Apple - initial setup
Download the latest version of Xcode from the Mac App Store. Make sure the Xcode provided simulator is using a 64-bit device (iPhone 5s or later). You can check the device by viewing the settings in the simulator’s **Hardware > Device** or **File > Open Simulator** menus.
### Android - initial setup
Download the latest version of [Android Studio](https://developer.android.com/studio). Install Android SDK and needed emulator(s).
### Create Project
Run this command in your terminal/shell at the desired location for your new project
```shell
$ flutter create my_app
```
You can then CD into the new directory, and run the test app with&#x20;
```shell
flutter run
```
This is a good test - if things are configured correctly so far you should see the default Flutter test app deployed.
### Add dependencies (Keyri and Supabase)
Open your Pubspec.yaml file, which should be at the top level directory in your new project
Add Keyri and Supabase under **dependencies**
![](https://archbee-image-uploads.s3.amazonaws.com/FQ4YmCkDokMJylbTAsoOR/jlAfOTEchuZpBq8TeXhJZ_screen-shot-2022-09-29-at-060908.png)
One can now access Supabase and Keyri sdks in their Flutter code
### Utilize the two together
1. Make a request to Supabase to authenticate the user
2. Parse the response to extract the token
3. Authenticate using Keyri
1. Below, we show how to utilize the EasyKeyriAuth function, which takes the user through scanning the code, creating the session, displaying the confirmation screen, and finalizing the payload transmission
- Note - you can find your App Key in the Keyri Developer Portal​
2. Alternatively, intermediate functions in the Keyri SDK, discussed in the mobile docs, can provide control over displaying a custom QR Scanner and/or Confirmation screen
```kotlin
// Sign in user with email and password
// Alternatively one can utilize the Supabase API to accomplish the same thing
final response = await client.auth.signIn(email: 'email', password: 'password');
if (response.error != null) {
// Error
print('Error: ${response.error?.message}');
} else {
// Success
final session = response.data;
// This is the payload that needs to be send through Keyri
final refreshToken = session.refreshToken
// EasyKeyriAuth guides the user through scanning and parsing the QR, confirming the session, and configuring the payload
// One can also use the initiateQRSession method to use the Keyri Scanner with a custom Confirmation screen
// Or the ProcessLink method if you have your own scanner or are using deep linking
await keyri
.easyKeyriAuth([App Key],
'{"refreshToken":"$refreshToken"}', [email])
.then((authResult) => _onAuthResult(authResult))
.catchError((error, stackTrace) => _onError(error));
}
```
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,337 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'onesignal',
title: 'OneSignal',
description:
'OneSignal allows you to send cloud messages to your users. Combine OneSignal with your Supabase apps and you can reach out to your users whenever there is a change in your database.',
video: 'https://www.youtube.com/v/mw0DLwItue4',
}
[OneSignal](https://onesignal.com/) is a tool that allows you to send messages across different channels such as the following to keep your users engaged.
- Push notifications
- SMS
- Emails
- In-app notifications
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.
![Entity Diagram](/docs/img/guides/integrations/onesignal/diagram.png)
We will create a simple ordering app and use Supabase Database Webhooks in conjunction with Edge Function to provide a real-time push notification experience.
You can find the complete example app along with the edge functions code to send the notifications [here](https://github.com/supabase-community/onesignal).
![Ordering app UI](/docs/img/guides/integrations/onesignal/app-ui.png)
## Step 1: Getting started
Before we dive into the code, this guide assumes that you have the following ready
- [Supabase](https://supabase.com/) project created
- [OneSignal](https://onesignal.com/) app created
- [Supabase CLI](https://supabase.com/docs/guides/cli) installed on your machine
Let’s create a Next.js app with tailwind CSS pre-installed
```bash
npx create-next-app -e with-tailwindcss --ts
```
We will then install the Supabase and OneSignal SDK.
```bash
npm i @supabase/supabase-js
npm i react-onesignal
```
After that, follow the instructions [here](https://documentation.onesignal.com/docs/web-push-custom-code-setup) to set up OneSignal for the web. You can set the URL of the app as a local host if you want to run the app locally, or add a remote URL if you want to deploy your app to a public hosting. You should add the file you obtain in step 4 of the instruction under the `public` directory of your Next.js app like [this](https://github.com/supabase-community/onesignal/tree/main/app/public).
## Step 2: Build Next.js app
The Next.js app will have a login form for the user to sign in, and a button that they can press to make an order once they are signed in. Update the `index.tsx` file to the following.
```tsx pages/index.tsx
import { createClient, User } from '@supabase/supabase-js'
import type { NextPage } from 'next'
import Head from 'next/head'
import React, { useEffect, useState } from 'react'
import OneSignal from 'react-onesignal'
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!
const supabaseAnonKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!
const oneSignalAppId = process.env.NEXT_PUBLIC_ONESIGNAL_APP_ID!
const supabase = createClient(supabaseUrl, supabaseAnonKey)
const Home: NextPage = () => {
const [user, setUser] = useState<User | null>(null)
const [oneSignalInitialized, setOneSignalInitialized] = useState<boolean>(false)
/**
* Initializes OneSignal SDK for a given Supabase User ID
* @param uid Supabase User ID
*/
const initializeOneSignal = async (uid: string) => {
if (oneSignalInitialized) {
return
}
setOneSignalInitialized(true)
await OneSignal.init({
appId: oneSignalAppId,
notifyButton: {
enable: true,
},
allowLocalhostAsSecureOrigin: true,
})
await OneSignal.setExternalUserId(uid)
}
const sendMagicLink = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const { email } = Object.fromEntries(new FormData(event.currentTarget))
if (typeof email !== 'string') return
const { error } = await supabase.auth.signInWithOtp({ email })
if (error) {
alert(error.message)
} else {
alert('Check your email inbox')
}
}
// Place a order with the selected price
const submitOrder = async (event: React.FormEvent<HTMLFormElement>) => {
event.preventDefault()
const { price } = Object.fromEntries(new FormData(event.currentTarget))
if (typeof price !== 'string') return
const { error } = await supabase.from('orders').insert({ price: Number(price) })
if (error) {
alert(error.message)
}
}
useEffect(() => {
const initialize = async () => {
const initialUser = (await supabase.auth.getUser())?.data.user
setUser(initialUser ?? null)
if (initialUser) {
initializeOneSignal(initialUser.id)
}
}
initialize()
const authListener = supabase.auth.onAuthStateChange(async (event, session) => {
const user = session?.user ?? null
setUser(user)
if (user) {
initializeOneSignal(user.id)
}
})
return () => {
authListener.data.subscription.unsubscribe()
}
}, [])
return (
<>
<Head>
<title>OneSignal Order Notification App</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className="flex items-center justify-center min-h-screen bg-black">
{user ? (
<form className="flex flex-col space-y-2" onSubmit={submitOrder}>
<select
className="bg-gray-50 border border-gray-300 text-gray-900 text-sm rounded block p-2"
name="price"
>
<option value="100">$100</option>
<option value="200">$200</option>
<option value="300">$300</option>
</select>
<button type="submit" className="py-1 px-4 text-lg bg-green-400 rounded">
Place an Order
</button>
</form>
) : (
<form className="flex flex-col space-y-2" onSubmit={sendMagicLink}>
<input
className="border-green-300 border rounded p-2 bg-transparent text-white"
type="email"
name="email"
placeholder="Email"
/>
<button type="submit" className="py-1 px-4 text-lg bg-green-400 rounded">
Send Magic Link
</button>
</form>
)}
</main>
</>
)
}
export default Home
```
There is quite a bit of stuff going on here, but basically, it’s creating a simple UI for the user to sign in using the [magic link](https://supabase.com/docs/guides/auth/auth-magic-link), and once the user is signed in, will initialize OneSignal to ask the user to receive notifications on the website.
Notice that inside the `initializeOneSignal()` function, we are setting the Supabase user ID as an [external user ID of OneSignal](https://documentation.onesignal.com/docs/external-user-ids). This allows us to later send push notifications to the user using their Supabase user ID from the backend, which is very handy.
```tsx
await OneSignal.setExternalUserId(uid)
```
The front-end side of things is done here. Let’s get into the backend.
We also need to set our environment variables. Create a `.env.local` file and use the following template to set the environment variables. You can find your Supabase configuration in your dashboard under `settings > API`, and you can find the OneSignal app ID from `Settings > Keys & IDs`
```bash
NEXT_PUBLIC_SUPABASE_URL=YOUR_SUPABASE_URL
NEXT_PUBLIC_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
NEXT_PUBLIC_ONESIGNAL_APP_ID=YOUR_ONESIGNAL_APP_ID
```
![Where to find OneSignal App ID](/docs/img/guides/integrations/onesignal/onesignal-app-id.png)
## Step 3: Create the Edge Function
Let’s create an edge function that will receive [database webhooks](https://supabase.com/docs/guides/database/webhooks) from the database and calls the OneSignal API to send the push notification.
```bash
supabase functions new notify
```
Replace the contents of `supabase/functions/notify/index.ts` with the following
```tsx
import { serve } from 'https://deno.land/std@0.177.0/http/server.ts'
import * as OneSignal from 'https://esm.sh/@onesignal/node-onesignal@1.0.0-beta7'
const _OnesignalAppId_ = Deno.env.get('ONESIGNAL_APP_ID')!
const _OnesignalUserAuthKey_ = Deno.env.get('USER_AUTH_KEY')!
const _OnesignalRestApiKey_ = Deno.env.get('ONESIGNAL_REST_API_KEY')!
const configuration = OneSignal.createConfiguration({
userKey: _OnesignalUserAuthKey_,
appKey: _OnesignalRestApiKey_,
})
const onesignal = new OneSignal.DefaultApi(configuration)
serve(async (req) => {
try {
const { record } = await req.json()
// Build OneSignal notification object
const notification = new OneSignal.Notification()
notification.app_id = _OnesignalAppId_
notification.include_external_user_ids = [record.user_id]
notification.contents = {
en: `You just spent $${record.price}!`,
}
const onesignalApiRes = await onesignal.createNotification(notification)
return new Response(JSON.stringify({ onesignalResponse: onesignalApiRes }), {
headers: { 'Content-Type': 'application/json' },
})
} catch (err) {
console.error('Failed to create OneSignal notification', err)
return new Response('Server error.', {
headers: { 'Content-Type': 'application/json' },
status: 400,
})
}
})
```
If you see bunch of errors in your editor, it's because your editor is not configured to use Deno. Follow the official setup guide [here](https://deno.land/manual@v1.28.3/getting_started/setup_your_environment) to setup your IDE to use Deno.
The function receives a `record` object, which is the row inserted in your `orders` table, and constructs a notification object to then send to OneSignal to deliver the push notification.
We also need to set the environment variable for the function. Create a `.env` file under your `supabase` directory and paste the following.
```bash
ONESIGNAL_APP_ID=YOUR_ONESIGNAL_APP_ID
USER_AUTH_KEY=YOUR_USER_AUTH_KEY
ONESIGNAL_REST_API_KEY=YOUR_ONESIGNAL_REST_API_KEY
```
`ONESIGNAL_APP_ID` and `ONESIGNAL_REST_API_KEY` can be found under `Settings > Keys & IDs` of your OneSignal app, and `USER_AUTH_KEY` can be found by going to `Account & API Keys` page by clicking your icon in the top right corner and scrolling to the `User Auth Key` section.
![Where to find OneSignal User Auth Key](/docs/img/guides/integrations/onesignal/onesignal-api-key.png)
Once your environment variables are filled in, you can run the following command to set the environment variable.
```bash
supabase secrets set --env-file ./supabase/.env
```
At this point, the function should be ready to be deployed! Run the following command to deploy your functions to the edge! The `no-verify-jwt` flag is required if you plan to call the function from a webhook.
```bash
supabase functions deploy notify --no-verify-jwt
```
## Step 4: Setting up the Supabase database
Finally, we get to set up the database! Run the following SQL to set up the `orders` table.
```sql
create table
if not exists public.orders (
id uuid not null primary key default uuid_generate_v4 (),
created_at timestamptz not null default now (),
user_id uuid not null default auth.uid (),
price int8 not null
);
```
As you can see, the `orders` table has 4 columns and 3 of them have default values. That means all we need to send from the front-end app is the price. That is why our insert statement looked very simple.
```tsx
const { error } = await supabase.from('orders').insert({
price: 100,
})
```
Let’s also set up the webhook so that whenever a new row is inserted in the `orders` table, it calls the edge function. Go to `Database > Webhooks` and create a new Database Webhook. The table should be set to `orders` and Events should be inserted. The type should be HTTP Request, the HTTP method should be POST, and the URL should be the URL of your edge function. Hit confirm to save the webhook configuration.
![Supabase Webhooks configuration](/docs/img/guides/integrations/onesignal/webhook.png)
At this point, the app should be complete! Run your app locally with `npm run dev`, or deploy your app to a hosting service and see how you receive a push notification when you place an order!
Remember that if you decide to deploy your app to a hosting service, you would need to create another OneSignal app configured for your local address.
![Ordering app UI](/docs/img/guides/integrations/onesignal/app-ui.png)
## Resources
This particular example was using Next.js, but you can apply the same principles to implement send push notification, SMS, Emails, and in-app-notifications on other platforms as well.
- [OneSignal + Flutter + Supabase example](https://github.com/OneSignalDevelopers/onesignal-supabase-sample-integration-supabase)
- [OneSignal Mobile Quickstart](https://documentation.onesignal.com/docs/mobile-sdk-setup)
- [OneSignal Documentation](https://documentation.onesignal.com/docs/onesignal-platform)
- [OneSignal Onboarding guide](https://documentation.onesignal.com/docs/onboarding-with-onesignal)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,608 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'passage',
title: 'Passage',
description:
'Build a Next.js application that uses passkey authentication with Passage and Row Level Security (RLS) with Supabase.',
}
This guide steps through building a Next.js application with Passage and Supabase. We will use Passage to authenticate users and manage tokens, while using Supabase for storing data and enforcing authorization logic using Row Level Security policies.
The full code example for this guide can be found [here](https://github.com/passageidentity/supabase-passage-example).
[Passage](https://passage.id) is a passwordless authentication platform that makes it simple for developers to add passkey authentication to their apps and websites, providing better security and simpler sign in for your users. They provide simple frontend components that handle all of the complexity of passwordless login for developers in just two lines of codes. Passage also provides session management, user management, and in-depth customization capabilities.
Next.js is a web application framework built on top of React. We will be using it for this example, as it allows us to write server-side logic within our application. Passage’s [frontend elements](https://docs.passage.id/frontend/passage-element) and [Node.js SDK](https://docs.passage.id/backend/overview/node) are designed to work for Next.js.
For this guide, you will need a Passage account which can be created [here](https://console.passage.id/register), and a Supabase account which can be created [here](https://supabase.com/dashboard/).
## 1. Create a Passage application
In the [Passage Console](https://console.passage.id), create a new application with the following configuration:
* Application name: Todo Application
* Authentication origin: http://localhost:3000
* Redirect URL: /dashboard
![Passage Console dashboard](/docs/img/guides/integrations/passage/01.png)
![Fill out the required fields to create a new application](/docs/img/guides/integrations/passage/02.png)
## 2. Create and configure a Supabase project
#### Create a new project
In the [Supabase dashboard](https://supabase.com/dashboard/), click `New Project`.
Enter a name for your project and create a secure database password.
#### Create a table schema
We are building Todo list application, similar to the [Supabase demo application](https://github.com/supabase/supabase/tree/master/examples/todo-list/nextjs-todo-list) so we will need a table for the todo list items.
Create a new table in the Table Editor view.
Set the Name field to `todo`.
Select Enable Row Level Security (RLS).
Create the following new columns.
* `title` as `text`
* `user_id` as `text` with a default value of `auth.user_id()`
* `is_complete` as `bool` with a default value of `false`
Click `Save` to create the table.
![Table schema.](/docs/img/guides/integrations/passage/03.png)
#### Add initial data to the table
From the Table editor, select the `todo` table and click `Insert row`. Fill out the required fields with an example todo item, leaving the `user_id` as NULL and click `Save`.
![Example todo item.](/docs/img/guides/integrations/passage/04.png)
After adding a few todo items, the table editor view will look like this:
![Table with multiple todo items.](/docs/img/guides/integrations/passage/05.png)
## 3. Build a Next.js app
#### Create Next.js app
Create a new Next.js project on the command line. You can choose your settings through the setup wizard - for this guide we will use JavaScript instead of TypeScript. This example uses the create script in Next v13.2.
```sh
npx create-next-app <name-of-project>
cd <name-of-project>/
```
You should be able to use the default settings for the project, but here are the settings used for the example app.
![Settings for Next.js application.](/docs/img/guides/integrations/passage/06.png)
#### Configure your ENV
Create a `.env` file and enter the following values.
```sh
NEXT_PUBLIC_PASSAGE_APP_ID=get-from-passage-settings
PASSAGE_API_KEY=get-from-passage-settings
NEXT_PUBLIC_SUPABASE_URL=get-from-supabase-dashboard
NEXT_PUBLIC_SUPABASE_ANON_KEY=get-from-supabase-dashboard
SUPABASE_JWT_SECRET=get-from-supabase-dashboard
```
The Supabase values can be found under `Project->Settings->API Settings`.
![Supabase environment variables.](/docs/img/guides/integrations/passage/07.png)
The Passage values can be found under `General->Settings` and `General->API Keys`.
![Passage environment variables.](/docs/img/guides/integrations/passage/08.png)
![Passage environment variables.](/docs/img/guides/integrations/passage/09.png)
> The `PASSAGE_API_KEY` and `SUPABASE_JWT_SECRET` are secret values and should never be shared publicly. They will only be used in the server-side code of the Next.js application.
Restart your Next.js development server to read in the environment variables.
```bash
npm run dev
```
## 4. Add Passage login to your app
#### Add Passage Element
Install the `@passageidentity/passage-elements` package.
```bash
npm install @passageidentity/passage-elements
```
Create a new folder called components with a new login file `components/login.js` and add the following content.
```js
// components/login.js
import { useEffect } from 'react';
const PassageLogin = () => {
useEffect(()=>{
require('@passageidentity/passage-elements/passage-auth');
}, []);
return (
<>
<passage-auth app-id={process.env.NEXT_PUBLIC_PASSAGE_APP_ID}></passage-auth>
</>
)
}
export default PassageLogin
```
Then update `pages/index.js` to include the login component.
```js
// pages/index.js
import styles from '@/styles/Home.module.css'
import PassageLogin from '@/components/login'
export default function Home(props) {
return(
<div className={styles.main}>
<PassageLogin />
</div>
)
}
```
When we have a successful registration the Passage element will request a redirect to `/dashboard` per the redirect URL we set during app creation.
Create a new file `pages/dashboard.js` for this new route with the following content:
```js
// pages/dashboard.js
import styles from '@/styles/Home.module.css'
export default function Dashboard({isAuthorized, userID, todos}) {
return(
<div className={styles.main}>
<div className={styles.container}>
<p>
You've logged in!
</p>
</div>
</div>
)
}
```
Now when you visit `http://localhost:3000` in a browser you will have a fully functioning and passwordless login page!
![Simple app with Passage login page.](/docs/img/guides/integrations/passage/10.png)
Go ahead and go through the registration process. You will be able to register an account with either a passkey or a magic link. Once you've logged in, you will notice that you just get redirected to `/dashboard` page.
The login was successful, but we need to build in the functionality to know when a user is authenticated and show them the appropriate view.
#### Use Passage to verify the JWT
Now we will need to use a Passage SDK to verify the JWT from Passage.
Install the Passage Node.js library.
```bash
npm install @passageidentity/passage-node
```
Create a utils folder and a file called `utils/passage.js` with the following content.
```js
// utils/passage.js
import Passage from '@passageidentity/passage-node';
const passage = new Passage({
appID: process.env.NEXT_PUBLIC_PASSAGE_APP_ID,
apiKey: process.env.PASSAGE_API_KEY,
});
export const getAuthenticatedUserFromSession = async (req, res) => {
try {
const userID = await passage.authenticateRequest(req);
if (userID) {
return {isAuthorized: true, userID: userID};
}
} catch (error) {
// authentication failed
return {isAuthorized: false, userID: ''};
}
}
```
This will be used in the `getServerSideProps()` function to check authentication status for a user. Add this function to `index.js` then update the `Home` function to use the props.
```js
// pages/index.js
import styles from '@/styles/Home.module.css'
import PassageLogin from '@/components/login'
import { getAuthenticatedUserFromSession } from '@/utils/passage'
import { useEffect } from 'react'
import Router from 'next/router';
export default function Home({isAuthorized}) {
useEffect(()=> {
if(isAuthorized){
Router.push('/dashboard')
}
})
return(
<div className={styles.main}>
<PassageLogin />
</div>
)
}
export const getServerSideProps = async (context) => {
const loginProps = await getAuthenticatedUserFromSession(context.req, context.res)
return {
props: {
isAuthorized: loginProps.isAuthorized?? false,
userID: loginProps.userID?? ''
},
}
}
```
We will also use this logic on the dashboard page to check if a user is authenticated. If not we should redirect them to the login page. We will also add a quick sign out button using Passage while we are at it.
```js
// pages/dashboard.js
import styles from '@/styles/Home.module.css'
import { useEffect } from 'react';
import Router from 'next/router';
import { getAuthenticatedUserFromSession } from '@/utils/passage'
import { PassageUser } from '@passageidentity/passage-elements/passage-user'
export default function Dashboard({isAuthorized, userID}) {
useEffect(() => {
if(!isAuthorized){
Router.push('/');
}
})
const signOut = async ()=>{
new PassageUser().signOut()
Router.push('/')
}
return (
<div className={styles.main}>
<h1>
Welcome {userID}!{' '}
</h1>
<br></br>
<button onClick={signOut}>Sign Out</button>
</div>
)
}
export const getServerSideProps = async (context) => {
const loginProps = await getAuthenticatedUserFromSession(context.req, context.res)
return {
props: {
isAuthorized: loginProps.isAuthorized?? false,
userID: loginProps.userID?? '',
},
}
}
```
The app can now tell the difference between an authenticated and unauthenticated user. When you log into the application, you will be redirected to the dashboard and see this message.
![Authenticated users can see their user ID.](/docs/img/guides/integrations/passage/11.png)
## 5. Integrate Supabase into Next.js app
Passage and Supabase do not currently allow for custom signing secrets. Therefore, we will need to extract the necessary claims from the Passage JWT and sign a new JWT to send to Supabase.
Because of the sensitive nature of this functionality, we will handle the authentication and JWT exchange in Next.js’s server-side rendering function `getServerSideProps()`. Imports used in this function will not be bundled client-side. Additionally, the JWT provided by Passage is stored in a cookie which is automatically passed to `getServerSideProps()`.
#### Sign Passage token for Supabase
Install the Supabase client SDK and the popular Node package `jsonwebtoken`, which allows us to easily work with JWTs.
```bash
npm install @supabase/supabase-js jsonwebtoken
```
Create a new file called `utils/supabase.js` and add the following content. This function accepts a Passage user ID and then creates and signs a Supabase JWT for that user. This allows Supabase to verify the token and authenticate the user when making Supabase calls.
```js
// utils/supabase.js
import { createClient } from '@supabase/supabase-js'
import jwt from 'jsonwebtoken'
const getSupabase = (userId) => {
const options = {}
if (userId) {
const payload = {
userId,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
const token = jwt.sign(payload, process.env.SUPABASE_JWT_SECRET)
options.global = {
headers: {
Authorization: `Bearer ${token}`,
},
}
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY,
options
)
return supabase
}
export { getSupabase }
```
#### Enable Row Level Security (RLS) in Supabase
To enable users to view and create their own todo items we need to write a RLS policy. Our policy will check the currently logged in user is to determine whether or not they should have access. Let's create a PostgreSQL function to extract the current user from our new JWT.
Navigate back to the Supabase dashboard, select `SQL Editor` from the sidebar menu, and click `New query`. This will create a new query called `new sql snippet`, which will allow us to run any SQL against our Postgres database.
Write the following and click `Run`.
```sql
create or replace function auth.user_id() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
```
You should see the output `Success, no rows returned`. This created a function called `auth.user_id()` which will return the `userId` field of our JWT payload.
> To learn more about PostgresSQL functions, check out this [deep dive video](https://www.youtube.com/watch?v=MJZCCpCYEqk).
Now we can create a policy that checks whether the current user is the owner of a todo item. From the `Authentication` sidebar menu in Supabase, click `Policies` then create a new policy.
![RLS policies for a table.](/docs/img/guides/integrations/passage/12.png)
Choose `For full customization create a policy from scratch` and add the following.
![Policy to restrict access to todo items.](/docs/img/guides/integrations/passage/13.png)
This policy is calling the function we just created to get the currently logged in user's ID `auth.user_id() `and checking whether this matches the `user_id` column for the current todo. If it does, then it will allow the user to select it, otherwise it will deny access.
Click `Review` and then `Save policy`.
> Note: To learn more about RLS and policies, check out this [deep dive video](https://www.youtube.com/watch?v=Ow_Uzedfohk).
#### Fetch data from Supabase
Now we can fetch data from Supabase specific to that user. We will update `pages/dashboard.js` to do the following:
1. authenticate the user using Passage
2. create and sign a JWT for the user with the Supabase secret
3. query Supabase to fetch a user’s todo list items
```js
// pages/dashboard.js
import styles from '@/styles/Home.module.css'
import { useEffect } from 'react';
import Router from 'next/router';
import { getAuthenticatedUserFromSession } from '@/utils/passage'
import { getSupabase } from '../utils/supabase'
export default function Dashboard({isAuthorized, userID, todos}) {
useEffect(() => {
if(!isAuthorized){
Router.push('/');
}
})
return(
<div className={styles.main}>
<div className={styles.container}>
<h1>
Welcome {userID}!{' '}
</h1>
<br></br>
<button onClick={signOut}>Sign Out</button>
<br></br>
<div className={styles.list}>
{todos?.length > 0 ? (
todos.map((todo) => <li key={todo.id}>{todo.title}</li>)
) : (
<p>You have completed all todos!</p>
)}
</div>
</div>
</div>
)
}
export const getServerSideProps = async (context) => {
const loginProps = await getAuthenticatedUserFromSession(context.req, context.res)
if(loginProps.isAuthorized){
const supabase = getSupabase(loginProps.userID)
const {data} = await supabase.from('todo').select()
return {
props: {
isAuthorized: loginProps.isAuthorized?? false,
userID: loginProps.userID?? '',
todos: data?? [],
},
}
} else {
return {
props: {
isAuthorized: loginProps.isAuthorized?? false,
userID: loginProps.userID?? ''
},
}
}
}
```
When we reload our application, we are still getting the empty state for todos.
This is because we enabled Row Level Security, which blocks all requests by default and lets you granularly control access to the data in your database.
#### Update the UserID data
The last thing we need to do is update the `user_id` columns for our existing todos. Head back to the Supabase dashboard, and select `Table editor` from the sidebar. You will see that the `user_id` field is NULL for all of our todo items.
![User ID is NULL for all todo items.](/docs/img/guides/integrations/passage/14.png)
To get the user ID for our Passage user, go back to the Passage Console and check the `Users` tab.
![Get User ID from Passage.](/docs/img/guides/integrations/passage/15.png)
Copy this user ID and update two of the three rows in the Supabase database to match this user ID. When you are done, the database table will look like this.
![Updated User ID in Supabase.](/docs/img/guides/integrations/passage/16.png)
Now when we refresh the application, we will see the todo items for our user!
![Authenticated users can see their todo items.](/docs/img/guides/integrations/passage/17.png)
## Bonus: Add todo items
To build out a bit more functionality in our application, we can now let users add items to their to do list. Create a file `pages/api/addTodo.js` with the following content.
```js
// pages/api/addTodo.js
import { getSupabase } from "../../utils/supabase";
export default async function handler(req, res) {
const { userID, todo } = req.body;
const supabase = getSupabase(userID);
const { data, error } = await supabase
.from("todo")
.insert({ title: todo })
.select()
.single();
if (error) return res.status(400).json(error);
res.status(200).json(data);
}
```
Then update `pages/dashboard.js` to include a form for submitting new to do items. The complete file will look like this.
```js
//pages/dashboard.js
import styles from "@/styles/Home.module.css";
import { useEffect, useState } from "react";
import Router from "next/router";
import { getAuthenticatedUserFromSession } from "@/utils/passage";
import { getSupabase } from "../utils/supabase";
import { PassageUser } from "@passageidentity/passage-elements/passage-user";
export default function Dashboard({ isAuthorized, userID, initialTodos }) {
const [todos, setTodos] = useState(initialTodos);
useEffect(() => {
if (!isAuthorized) {
Router.push("/");
}
});
const handleSubmit = async (e) => {
e.preventDefault();
const data = new FormData(e.target);
const todo = data.get("todo");
const res = await fetch("/api/addTodo", {
method: "POST",
headers: {
"Content-Type": "application/json",
},
body: JSON.stringify({ todo, userID }),
}).then((res) => res.json());
setTodos([...todos, res]);
};
const signOut = async () => {
new PassageUser().signOut();
Router.push("/");
};
return (
<div className={styles.main}>
<div className={styles.container}>
<h1>Welcome {userID}! </h1>
<br></br>
<button onClick={signOut}>Sign Out</button>
<br></br>
<div className={styles.list}>
{todos?.length > 0 ? (
todos.map((todo) => <li key={todo.id}>{todo.title}</li>)
) : (
<p>You have completed all todos!</p>
)}
</div>
<form onSubmit={handleSubmit}>
<label>
Todo: <input type="text" name="todo" />
</label>
<button>Submit</button>
</form>
</div>
</div>
);
}
export const getServerSideProps = async (context) => {
const loginProps = await getAuthenticatedUserFromSession(
context.req,
context.res
);
if (loginProps.isAuthorized) {
const supabase = getSupabase(loginProps.userID);
const { data } = await supabase
.from("todo")
.select()
.is("is_complete", false);
return {
props: {
isAuthorized: loginProps.isAuthorized ?? false,
userID: loginProps.userID ?? "",
initialTodos: data ?? [],
},
};
} else {
return {
props: {
isAuthorized: loginProps.isAuthorized ?? false,
userID: loginProps.userID ?? "",
},
};
}
};
```
Finally, we need to add a new RLS policy in Supabase to allow users to insert their own todo items.
![RLS policy for inserting todo items.](/docs/img/guides/integrations/passage/18.png)
That's it! Now the website has form for submitting new items for the to do list.
![Authenticated users can create todo items](/docs/img/guides/integrations/passage/19.png)
## Resources
* [Passage website](https://passage.id)
* [Complete developer documentation](https://docs.passage.id)
* [Passage Github](https://github.com/passageidentity), including SDKs and example apps for Next.js
* [Developer community](https://discord.com/invite/445QpyEDXh) on Discord
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,73 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'pgmustard',
title: 'pgMustard',
description:
'Troubleshoot slow queries on Supabase with pgMustard, a visualization tool that also gives advice.',
}
This guide explains how to troubleshoot slow queries on Supabase using `explain` and pgMustard.
[pgMustard](https://pgmustard.com/) is a visualization tool for [`explain analyze`](https://www.postgresql.org/docs/current/using-explain.html#USING-EXPLAIN-ANALYZE) that also gives performance tips.
## Step 1: Get the query plan from Supabase
Use `explain analyze` to get a query plan from Postgres. This will run the query behind the scenes, so be careful with data modification queries.
pgMustard requires plans to be in json format, and the buffers, verbose, and settings parameters allow it to give better tips.
So a good prefix for your query would be:
```jsx
explain (analyze, format json, buffers, verbose, settings)
```
Run the query, and copy the output.
![01-supabase-run-query](/docs/img/guides/integrations/pgmustard/01-supabase-run-query.png)
If you’re using the Supabase SQL Editor, this is easily copied from the cell titled `QUERY PLAN`, as seen above.
If you have any trouble, check out pgMustard’s guide for [getting a query plan](https://www.pgmustard.com/getting-a-query-plan).
## Step 2: Paste the query plan into pgMustard
Paste the json output into pgMustard and press Submit.
![02-paste-plan-pgmustard](/docs/img/guides/integrations/pgmustard/02-paste-plan-pgmustard.png)
## Step 3: Look through the top tips and slowest operations
Review the top tips in pgMustard. These are scored on a scale of 0 to 5 stars, based on how much time-saving potential they have (5 stars meaning lots of potential).
![03-review-tips-pgmustard](/docs/img/guides/integrations/pgmustard/03-review-tips-pgmustard.png)
Click one of the tips, or one of the operations, to see more information.
![04-click-tip-pgmustard](/docs/img/guides/integrations/pgmustard/04-click-tip-pgmustard.png)
## Step 4: Consider your options
If you get some promising suggestions, you may wish to explore them.
If you don’t get any tips, your query might be quite fast for the amount of work it’s doing.
For the example we saw in Step 3, let's try adding an index on the `customer_name` field in Supabase.
![05-create-index-supabase](/docs/img/guides/integrations/pgmustard/05-create-index-supabase.png)
Going through Steps 1-3 again, we now get an efficient index scan, that will scale nicely as our data grows.
![06-check-pgmustard](/docs/img/guides/integrations/pgmustard/06-check-pgmustard.png)
We could look into why Postgres isn’t choosing to do an index-only scan here, but pgMustard is letting us know that it doesn’t think we’ll gain much by doing so, by scoring the tip 0.3 out of 5.
## Resources
- [pgMustard](https://www.pgmustard.com) official website.
- [pgMustard explain glossary](https://www.pgmustard.com/docs/explain).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,572 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'picket',
title: 'Picket',
description:
"Get the best of web2 and web3. Picket allows your users to log in with their wallet without sacrificing Supabase's awesome data management and security features.",
}
[Picket](https://picketapi.com) is a developer-first, multi-chain web3 auth platform. With Picket, you can easily authenticate users via their wallets and token gate anything.
This guide steps through building a simple todo list Next.js application with Picket and Supabase. We use Picket to allow users to login into our app with their wallets and leverage Supabase's Row Level Security (RLS) to securely store user information off-chain.
> Checkout a [live demo](https://picket-supabase-auth-example-v36z.vercel.app/) of a Picket + Supabase integration
The code for this guide is based of [this example repo](https://github.com/picketapi/picket-supabase-auth-example).
## Requirements
- You have [Supabase](https://supabase.com) account. If you don't, sign up at https://supabase.com/
- You have a [Picket](https://picketapi.com) account. If you don't, sign up at https://picketapi.com/
- You've read the [Picket Setup Guide](https://docs.picketapi.com/picket-docs/quick-start-guides/quick-start-guides/start-here-setup)
- Familiarity with [React](https://reactjs.org/) and [Next.js](https://nextjs.org/)
## Step 1: Create a Picket Project
First, we'll create a new project in our [Picket dashboard](https://picketapi.com/dashboard).
Click the `Create New Project` button at the top of the Projects section on your [Picket dashboard](https://picketapi.com/dashboard). Edit the project to give it a memorable name.
![Picket project settings](/docs/img/guides/integrations/picket/picket_project.png)
We're done for now! We'll revisit this project when we are setting up environment variables in our app.
## Step 2: Create a Supabase Project
From your [Supabase dashboard](https://supabase.com/dashboard/), click `New project`.
Enter a `Name` for your Supabase project.
Enter a secure `Database Password`.
Select the any `Region`.
Click `Create new project`.
![Supabase project settings](/docs/img/guides/integrations/picket/supabase_project.png)
## Step 3: Create new New Table with RLS in Supabase
### Create a `todos` Table
From the sidebar menu in the [Supabase dashboard](https://supabase.com/dashboard/), click `Table editor`, then `New table`.
Enter `todos` as the `Name` field.
Select `Enable Row Level Security (RLS)`.
Create four columns:
- `name` as `text`
- `wallet_address` as `text`
- `completed` as `bool` with the default value `false`
- `created_at` as timestamptz with a default value of `now()`
Click `Save` to create the new table.
![todos table](/docs/img/guides/integrations/picket/new_table.png)
### Setup Row Level Security (RLS)
Now we want to make sure that only the `todos` owner, the user's `wallet_address`, can access their todos. The key component of the this RLS policy is the expression
```sql
((auth.jwt() ->> 'walletAddress'::text) = wallet_address)
```
This expression checks that the wallet address in the requesting JWT access token is the same as the `wallet_address` in the `todos` table.
![RLS policy](/docs/img/guides/integrations/picket/rls_policy.png)
## Step 4: Create a Next.js app
Now, let's start building!
Create a [new Typescript Next.js app](https://nextjs.org/docs/getting-started)
```bash
npx create-next-app@latest --typescript
```
Create a `.env.local` file and enter the following values
- `NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY` => Copy the publishable key from the Picket project you created in step 1
- `PICKET_PROJECT_SECRET_KEY` => Copy the secret key from the Picket project you created in the step 1
- `NEXT_PUBLIC_SUPABASE_URL` => You can find this URL under "Settings > API" in your Supabase project
- `NEXT_PUBLIC_SUPABASE_ANON_KEY` => You can find this project API key under "Settings > API" in your Supabase project
- `SUAPBASE_JWT_SECRET`=> You can find this secret under "Settings > API" in your Supabase project
```bash
NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY="YOUR_PICKET_PUBLISHABLE_KEY"
PICKET_PROJECT_SECRET_KEY="YOUR_PICKET_PROJECT_SECRET_KEY"
NEXT_PUBLIC_SUPABASE_URL="YOUR_SUPABASE_URL"
NEXT_PUBLIC_SUPABASE_ANON_KEY="YOUR_SUPABASE_ANON_KEY"
SUPABASE_JWT_SECRET="YOUR_SUPABASE_JWT_SECRET"
```
## Step 5: Setup Picket for Wallet Login
> For more information on how to setup [Picket](https://picketapi.com) in your Next.js app, checkout the [Picket getting started guide](https://docs.picketapi.com/picket-docs/quick-start-guides/quick-start-guides/wallet-login)
> After initializing our app, we can setup Picket.
Install the Picket [React](/picket-docs/reference/libraries-and-sdks/react-sdk-picket-react) and [Node](/picket-docs/reference/libraries-and-sdks/node.js-library-picket-node) libraries
```bash
npm i @picketapi/picket-react @picketapi/picket-node
```
Update `pages/_app.tsx` to setup the `PicketProvider`
```tsx
import '../styles/globals.css'
import type { AppProps } from 'next/app'
import { PicketProvider } from '@picketapi/picket-react'
export default function App({ Component, pageProps }: AppProps) {
return (
<PicketProvider apiKey={process.env.NEXT_PUBLIC_PICKET_PUBLISHABLE_KEY!}>
<Component {...pageProps} />
</PicketProvider>
)
}
```
Update `pages/index.tsx` to let users log in and out with their wallet
```tsx
import { GetServerSideProps } from 'next'
import { useRouter } from 'next/router'
import { useCallback } from 'react'
import styles from '../styles/Home.module.css'
import { usePicket } from '@picketapi/picket-react'
import { cookieName } from '../utils/supabase'
type Props = {
loggedIn: boolean
}
export default function Home(props: Props) {
const { loggedIn } = props
const { login, logout, authState } = usePicket()
const router = useRouter()
const handleLogin = useCallback(async () => {
let auth = authState
// no need to re-login if they've already connected with Picket
if (!auth) {
// login with Picket
auth = await login()
}
// login failed
if (!auth) return
// create a corresponding supabase access token
await fetch('/api/login', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
accessToken: auth.accessToken,
}),
})
// redirect to their todos page
router.push('/todos')
}, [authState, login, router])
const handleLogout = useCallback(async () => {
// clear both picket and supabase session
await logout()
await fetch('/api/logout', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
})
// refresh the page
router.push('/')
}, [logout, router])
return (
<div className={styles.container}>
<main className={styles.main}>
{loggedIn ? (
<button onClick={handleLogout}>Log Out to Switch Wallets</button>
) : (
<button onClick={handleLogin}>Log In with Your Wallet</button>
)}
</main>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async ({ req }) => {
// get supabase token server-side
const accessToken = req.cookies[cookieName]
if (!accessToken) {
return {
props: {
loggedIn: false,
},
}
}
return {
props: {
loggedIn: true,
},
}
}
```
## Step 6: Issue a Supabase JWT on Wallet Login
Great, now we have setup a typical Picket Next.js app. Next, we need to implement the log in/out API routes to allow users to securely query our Supabase project.
First, install dependencies
```bash
npm install @supabase/supabase-js jsonwebtoken cookie js-cookie
```
Create a utility function to create a Supabase client with a custom access token in `utils/supabase.ts`
```ts
import { createClient, SupabaseClientOptions } from '@supabase/supabase-js'
export const cookieName = 'sb-access-token'
const getSupabase = (accessToken: string) => {
const options: SupabaseClientOptions<'public'> = {}
if (accessToken) {
options.global = {
headers: {
// This gives Supabase information about the user (wallet) making the request
Authorization: `Bearer ${accessToken}`,
},
}
}
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL!,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
options
)
return supabase
}
export { getSupabase }
```
Create new api route `pages/api/login.ts`. This route validates the Picket access token then issues another equivalent Supabase access token for us to use with the Supabase client.
```ts
import type { NextApiRequest, NextApiResponse } from 'next'
import jwt from 'jsonwebtoken'
import cookie from 'cookie'
import Picket from '@picketapi/picket-node'
import { cookieName } from '../../utils/supabase'
// create picket node client with your picket secret api key
const picket = new Picket(process.env.PICKET_PROJECT_SECRET_KEY!)
const expToExpiresIn = (exp: number) => exp - Math.floor(Date.now() / 1000)
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { accessToken } = req.body
// omit expiration time,.it will conflict with jwt.sign
const { exp, ...payload } = await picket.validate(accessToken)
const expiresIn = expToExpiresIn(exp)
const supabaseJWT = jwt.sign(
{
...payload,
},
process.env.SUPABASE_JWT_SECRET!,
{
expiresIn,
}
)
// Set a new cookie with the name
res.setHeader(
'Set-Cookie',
cookie.serialize(cookieName, supabaseJWT, {
path: '/',
secure: process.env.NODE_ENV !== 'development',
// allow the cookie to be accessed client-side
httpOnly: false,
sameSite: 'strict',
maxAge: expiresIn,
})
)
res.status(200).json({})
}
```
And now create an equivalent logout api route `/pages/api/logout.ts` to delete the Supabase access token cookie.
```ts
import type { NextApiRequest, NextApiResponse } from 'next'
import cookie from 'cookie'
import { cookieName } from '../../utils/supabase'
export default async function handler(_req: NextApiRequest, res: NextApiResponse) {
// Clear the supabase cookie
res.setHeader(
'Set-Cookie',
cookie.serialize(cookieName, '', {
path: '/',
maxAge: -1,
})
)
res.status(200).json({})
}
```
We can now login and logout to the app with our wallet!
## Step 7: Interacting with Data in Supabase
Now that we can login to the app, it's time to start interacting with Supabase. Let's make a todo list page for authenticated users.
Create a new file `pages/todos.tsx`
```tsx
import { GetServerSideProps } from 'next'
import Head from 'next/head'
import Link from 'next/link'
import { useState, useMemo } from 'react'
import jwt from 'jsonwebtoken'
import Cookies from 'js-cookie'
import styles from '../styles/Home.module.css'
import { getSupabase, cookieName } from '../utils/supabase'
type Todo = {
name: string
completed: boolean
}
type Props = {
walletAddress: string
todos: Todo[]
}
const displayWalletAddress = (walletAddress: string) =>
`${walletAddress.slice(0, 6)}...${walletAddress.slice(-4)}`
export default function Todos(props: Props) {
const { walletAddress } = props
const [todos, setTodos] = useState(props.todos)
// avoid re-creating supabase client every render
const supabase = useMemo(() => {
const accessToken = Cookies.get(cookieName)
return getSupabase(accessToken || '')
}, [])
return (
<div className={styles.container}>
<Head>
<title>Picket 💜 Supabase</title>
</Head>
<main className={styles.main}>
<h1 className={styles.title}>Your Personal Todo List</h1>
<div
style={{
maxWidth: '600px',
textAlign: 'left',
fontSize: '1.125rem',
margin: '36px 0 24px 0',
}}
>
<p>Welcome {displayWalletAddress(walletAddress)},</p>
<p>
Your todo list is stored in Supabase and are only accessible to you and your wallet
address. Picket + Supabase makes it easy to build scalable, hybrid web2 and web3 apps.
Use Supabase to store non-critical or private data off-chain like user app preferences
or todo lists.
</p>
</div>
<div
style={{
textAlign: 'left',
fontSize: '1.125rem',
}}
>
<h2>Todo List</h2>
{todos.map((todo) => (
<div
key={todo.name}
style={{
margin: '8px 0',
display: 'flex',
alignItems: 'center',
}}
>
<input
type="checkbox"
checked={todo.completed}
onChange={async () => {
await supabase.from('todos').upsert({
wallet_address: walletAddress,
name: todo.name,
completed: !todo.completed,
})
setTodos((todos) =>
todos.map((t) => (t.name === todo.name ? { ...t, completed: !t.completed } : t))
)
}}
/>
<span
style={{
margin: '0 0 0 8px',
}}
>
{todo.name}
</span>
</div>
))}
<div
style={{
margin: '24px 0',
}}
>
<Link
href={'/'}
style={{
textDecoration: 'underline',
}}
>
Go back home &rarr;
</Link>
</div>
</div>
</main>
</div>
)
}
export const getServerSideProps: GetServerSideProps<Props> = async ({ req }) => {
// example of fetching data server-side
const accessToken = req.cookies[cookieName]
// require authentication
if (!accessToken) {
return {
redirect: {
destination: '/',
},
props: {
walletAddress: '',
todos: [],
},
}
}
// check if logged in user has completed the tutorial
const supabase = getSupabase(accessToken)
const { walletAddress } = jwt.decode(accessToken) as {
walletAddress: string
}
// get todos for the users
// if none exist, create the default todos
let { data } = await supabase.from('todos').select('*')
if (!data || data.length === 0) {
let error = null
;({ data, error } = await supabase
.from('todos')
.insert([
{
wallet_address: walletAddress,
name: 'Complete the Picket + Supabase Tutorial',
completed: true,
},
{
wallet_address: walletAddress,
name: 'Create a Picket Account (https://picketapi.com/)',
completed: false,
},
{
wallet_address: walletAddress,
name: 'Read the Picket Docs (https://docs.picketapi.com/)',
completed: false,
},
{
wallet_address: walletAddress,
name: 'Build an Awesome Web3 Experience',
completed: false,
},
])
.select('*'))
if (error) {
// log error and redirect home
console.error(error)
return {
redirect: {
destination: '/',
},
props: {
walletAddress: '',
todos: [],
},
}
}
}
return {
props: {
walletAddress,
todos: data as Todo[],
},
}
}
```
This is a long file, but don't be intimidated. The page is actually straightforward. It
1. Verifies server-side that the user is authenticated and if they are not redirects them to the homepage
2. Checks to see if they already have `todos` . If so, it returns them. If not, it initializes them for the users
3. We render the `todos` and when the user selects or deselects a todo, we update the data in the database
## Step 8: Try it Out!
And that's it. If you haven't already, run your app to test it out yourself
```bash
# start the app
npm run dev
# open http://localhost:3000
```
### What's Next?
- Explore the [Picket documentation](https://docs.picketapi.com/picket-docs/)
- Play with the live [Picket + Supabase demo](https://picket-supabase-auth-example-v36z.vercel.app/)
- Checkout Picket's [example Github repositories](https://github.com/picketapi)
### Common Use-Case for Picket + Supabase
- Account linking. Allow users to associate their wallet address(es) with their existing web2 account in your app
- Leverage Supabase's awesome libraries and ecosystem while still enabling wallet login
- Store app-specific data, like user preferences, about your user's wallet adress off-chain
- Cache on-chain data to improve your DApp's performance
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,313 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'plasmic',
title: 'Plasmic',
description:
'Get started with Supabase and Plasmic, an open-source framework for building internal tools.',
}
In this guide, we will show you how to build a crowd-sourced Pokemon Pokedex, by connecting **Supabase**, an open source Firebase backend alternative, with **Plasmic**, a visual builder for the web. While many users leverage Plasmic to quickly launch and iterate on landing pages, in this tutorial we’ll show just how powerful Plasmic can be as a general-purpose visual builder for React, which can be used to design and implement fully featured read-write applications.
You can play with the live demo here:
[https://plasmic-supabase-demo.vercel.app/](https://plasmic-supabase-demo.vercel.app/)
You can also take a look at the Plasmic project here:
[https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6](https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6)
> You’ll need to enable 3rd-party cookies in your browser for the project to properly load.
![pokedex-screenshot](/docs/img/guides/integrations/plasmic/application-screenshot-00.png)
At a high level,
- **Supabase** is used to store the database of Pokemon (backed by Postgres) and provides an authentication backend. Our code base includes React components for querying the database, displaying this data, and supporting user sessions.
- **Plasmic** is used to create the pages and visual design of the application. We import our Supabase components into the Studio, which can be visually assembled and configured there (e.g. for displaying data).
- Plasmic designed pages are rendered back into the Next.js application.
## Step 1: Set up your Backend on Supabase
- On the [Supabase dashboard](https://supabase.com/dashboard/), click `New project` and set the name of the project.
By default, Supabase will already be set up for user signups with email, with users being stored in a `users` table.
![create-project-supabase](/docs/img/guides/integrations/plasmic/create-project-supabase-01.png)
- Navigate to the `Table Editor` on the left side navigation bar. Here we can create a `New table` to store our Pokemon entries. Make sure you are in the `schema public` view. Create a new table called `entries`, with 6 columns:
- `id`: is a unique ID for the entry. This column should be generated automatically as the primary column.
- `user_id`: Create a relation to the `user` table by clicking on the link icon next to the column name. Here, you can select the `id` column of the `user` table.
- `name`, `description`, `imageUrl`: This will store the name, description, and imageUrl for each Pokemon.
- `inserted_at` : This will be an automatically populated column, set to when the row was first inserted.
> Note: In this tutorial we’ve turned off “[Row Level Security (RLS)](/docs/guides/auth/row-level-security)”. In practice, you will want to create policies that restrict who gets to create, edit, and delete posts. By turning this off, any user can modify the database without restrictions.
![create-table-supabase](/docs/img/guides/integrations/plasmic/create-table-supabase-02.png)
For your convenience, feel free to import the following CSV into Supabase to pre-populate your database. In order to import, you must select `Import data via spreadsheet`, in the new table dialog box. (It does not work on existing tables.)
[pokedex-export.csv](/docs/img/guides/integrations/plasmic/pokedex-export.csv)
## Step 2: Set up your codebase
We have a working code example for you [here](https://github.com/plasmicapp/plasmic/tree/master/examples/supabase-demo). This starter comes with all of the code components you need to get started querying Supabase through Plasmic Studio.
> Code components are React components defined in your code base that we import into Plasmic Studio for use. Your project will be configured to look for these at `http://localhost:3000/plasmic-host` You can use these components in your design, as well as style them. Check out `supabase-demo/plasmic-init.ts` to see how they are registered with Plasmic.
First, clone the repo to your development machine and install the dependencies.
```bash
git clone git@github.com:plasmicapp/plasmic.git
cd plasmic/examples/supabase-demo/
yarn install
```
Copy `.env.example` to `.env.local`, which will store the environment variables when running a local development server. Add your Supabase project’s URL and public key, which you can find in the `API` tab on the left pane of your Supabase dashboard.
Now run the dev server, which listens at `http://localhost:3000`
```bash
yarn dev
```
## Step 3: Explore the existing application
Navigate to [http://localhost:3000](http://localhost:3000) in your web browser. The project will already be set up for user signups, logins, and an admin interface for adding and editing Pokemon to the database. Feel free to sign up with your email address for an account and add Pokemon to the database. Supabase will require that you verify your email address before you can log in.
If you pre-populated the database in Step 1. you should see the following homepage after logging in. Otherwise, feel free to add Pokemon manually via the UI.
![application-screenshot](/docs/img/guides/integrations/plasmic/application-screenshot-03.png)
## Step 4: Clone the Plasmic project
Now let’s try to make some additions! The code base is currently configured to a read-only copy of the Plasmic project. Let’s make an editable copy first.
Open the default starter Plasmic project here:
[https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6](https://studio.plasmic.app/projects/66RKaSPCwKxYjCfXWHCxn6)
![clone-project-plasmic](/docs/img/guides/integrations/plasmic/clone-project-plasmic-04.png)
To make an editable copy, click on the `Copy Project` button in the blue bar. This will clone the project and redirect you to your copy.
### Step 4a: Configure your code base to use the new Plasmic project
Take note of the `project ID` and `API token`. You can find the project ID in the URL:
`https://studio.plasmic.app/projects/PROJECTID`.
The API token can be found by clicking the `Code` button in the top bar.
![api-token-plasmic](/docs/img/guides/integrations/plasmic/api-token-plasmic-05.png)
Now go back to `.env.local` and update the corresponding project ID and token fields.
### Step 4b: Configure your Plasmic project app host
To tell Plasmic to look for your code components on your dev server, you’ll need to update your project’s app host to `http://localhost:3000/plasmic-host`.
> Note: At this point, you’ll need to keep your dev server running at `http://localhost:3000`for the project to load.
<video width="99%" autoPlay muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/plasmic/configure-app-host-plasmic-05.mp4"
type="video/mp4"
/>
</video>
After restarting the dev server and Plasmic Studio, you should now be able to make edits across Plasmic Studio and your codebase.
## Step 5: Create a new page for our Pokedex gallery
Let’s make a visual gallery for our Pokemon by using the code components from the code base.
Create a new page called `Gallery`, and set a path for this page (`/gallery`).
<video width="99%" autoPlay muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/plasmic/create-gallery-page-plasmic-06.mp4"
type="video/mp4"
/>
</video>
Insert a `SupabaseGrid` by searching the AddDrawer (by clicking the blue + button)
> For source see `components/CodeComponents/DatabaseComponents.tsx`
![add-supabasegrid-plasmic](/docs/img/guides/integrations/plasmic/add-supabasegrid-plasmic-07.png)
Then in the right-hand panel, configure the props on `SupabaseGrid`.
- `tableName` should match the table you created in Supabase
- `tableColumns` are a comma-delimited list of columns you want to select from the table
- We also set the number of columns and spacing shown in the grid
![set-props-plasmic](/docs/img/guides/integrations/plasmic/set-props-plasmic-08.png)
The `SupabaseGrid` will loop over the rows from the query.
Now customize the repeated content by inserting instances of `SupabaseField`. Select the type of content and a selector string to fetch a single value. In the example below, we use `{{row.imageUrl}}` to retrieve the `imageUrl` column of the row. Apply any styling and layout you want on these elements.
![add-supabasefield-plasmic](/docs/img/guides/integrations/plasmic/add-supabasefield-plasmic-09.png)
### Putting it all together (video)
For your convenience, the following video shows you how to create the page end-to-end.
<video width="99%" autoPlay muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/guides/integrations/plasmic/end-to-end-plasmic-10.mp4"
type="video/mp4"
/>
</video>
## Step 6: Check your dev server
If you have been running your development server this whole time, you’ll see that we have been automatically fetching and rebuilding your site as you make changes in Plasmic Studio. If you need to restart your dev server, just run:
```bash
yarn dev
```
See the results at `http://localhost:3000/gallery`.
## How does this all work under the hood?
### SupabaseGrid
`SupabaseGrid` is a code component that was registered in `plasmic-init.ts`. The `props` field is used to tell the Plasmic Studio the component prop interface, which allows us to expose these props in the right pane as shown in the screenshots earlier. See the docs for details on [component registration](https://docs.plasmic.app/learn/registering-code-components/).
```tsx
// plasmic-init.ts
...
PLASMIC.registerComponent(SupabaseGrid, {
name: "SupabaseGrid",
props: {
tableName: "string",
tableColumns: "string",
queryFilters: "object",
children: {
type: "slot",
defaultValue: {
type: "text",
value: "Placeholder",
},
},
numColumns: {
type: "number",
defaultValue: 4,
},
columnGap: {
type: "number",
defaultValue: 16,
},
rowGap: {
type: "number",
defaultValue: 16,
},
count: "number",
loading: {
type: "slot",
defaultValue: {
type: "text",
value: "Loading...",
},
},
},
importPath: "./components/CodeComponents/DisplayCollections",
});
```
### SupabaseQuery
`SupabaseGrid` wraps a `SupabaseQuery` component, where we perform the query based on the provided props and store the result in a `SupabaseQueryContext`. This will be used in downstream components to display the data.
```tsx
// supabase-demo/components/CodeComponents/DatabaseComponents.tsx
export function SupabaseQuery(props: SupabaseQueryProps) {
// These props are set in the Plasmic Studio
const { children, tableName, columns, className, filters, single } = props;
const [result, setResult] = React.useState<any[] | undefined>(undefined);
...
// Performs the Supabase query
let query = supabase.from(tableName!).select(columns + ",id");
query = applyFilter(query, validFilters, contexts);
const { data, error, status } = await (single ? query.single() : query.order('id', { ascending: false }));
if (error && status !== 406) {
throw error;
} else if (data) {
setResult(data);
}
...
// Save the result in a `SupabaseQueryContext for use with downstream components
return (
<div className={className}>
<SupabaseQueryContext.Provider value={result}>
{children}
</SupabaseQueryContext.Provider>
</div>
);
}
```
Note that this code component is defined in your codebase. Feel free to augment it to expose more powerful querying capabilities to the Plasmic Studio.
### SupabaseGridCollection
`SupabaseGrid` also nests a `SupabaseGridCollection` under the `SupabaseQuery`. This code component is a simple CSS grid, where we retrieve the Supabase query results from `SupabaseQueryContext`, and iterate over the results. For each row, we populate a `RowContext`, which will be used by the children to read the results of a single row. Note the use of `repeatedElement`, a special convenience function that enables the component’s children to be repeated. In this case, this represents a single card to be shown in the gallery.
```tsx
// supabase-demo/components/CodeComponents/DisplayCollections.tsx
export function SupabaseGridCollection(props: SupabaseGridCollectionProps) {
const supabaseQuery = React.useContext(SupabaseQueryContext)
const { children, columns, columnGap, rowGap, count, className, loading, testLoading } = props
const result = supabaseQuery
if (!result || testLoading) {
return loading
}
return (
<div
style={{
display: 'grid',
gridTemplateColumns: `repeat(${columns}, 1fr)`,
columnGap: `${columnGap}px`,
rowGap: `${rowGap}px`,
}}
className={className}
>
{result.slice(0, count).map((row: any, i: any) => (
<RowContext.Provider value={row} key={row.id}>
<div key={row.id}>{repeatedElement(i === 0, children)}</div>
</RowContext.Provider>
))}
</div>
)
}
```
### SupabaseField
`SupabaseField` will either render a `SupabaseTextField` or `SupabaseImgField` depending on the type. These code components simply read a single value from the contexts and display the data.
```tsx
// supabase-demo/components/CodeComponents/DisplayCollections.tsx
export function SupabaseTextField({ name, className }: { name?: string; className?: string }) {
const contexts = useAllContexts()
if (!name) {
return <p>You need to set the name prop</p>
}
return <div className={className}>{getPropValue(name, contexts)}</div>
}
```
In summary, by populating state into React contexts, we can store and retrieve data for use in other code components, which can be used for arbitrarily powerful interactions in Plasmic Studio.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,74 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'polyscale',
title: 'PolyScale',
description:
'The easiest way to get low-latency reads from your Supabase database for multi-region applications is by using PolyScale, a code-free global caching service.',
}
[PolyScale](https://polyscale.ai) is an intelligent, serverless database caching engine which allows low-latency reads from Supabase globally, no coding required. Supabase can be connected to PolyScale in minutes, providing you fast access to your Supabase data around the globe.
This guide explains how to connect Supabase to a PolyScale cache.
The video below illustrates how to get connected. Or you can read the steps below.
<div className="video-container">
<iframe
src="https://www.youtube.com/embed/ZQ8TG-A-CIw?rel=0"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; modestbranding; showinfo=0"
allowfullscreen
width="100%"
height="400px"
></iframe>
</div>
<Admonition type="note">
PolyScale provides caching for TCP connections and GraphQL. Support for caching with the Supabase client is coming soon.
</Admonition>
## Step 0: Create a PolyScale account
If you do not already have a PolyScale account, you can create an account [here](https://app.polyscale.ai/signup). PolyScale offers a free plan and no credit card is required.
## Step 1: Create your PolyScale Cache
### 1.1 Retrieve your Supabase Host
In your Supabase project, click on `Settings > Database` and scroll down to the `Connection info` section to copy your database `Host`.
![supabase-host](/docs/img/guides/integrations/polyscale/supabase-host.png)
### 1.2 Configure your PolyScale Cache
- In your PolyScale account, click on the **New Cache** button
- Give the cache a **Name**
- Select **PostgreSQL** for the **Type**
- Enter the **Host** from Step 1.1 above
- Enter `5432` for the **Port**
- Click **Create**
![create-cache-supabase](/docs/img/guides/integrations/polyscale/create-cache-supabase-400.png)
Your cache is now created. PolyScale automatically checks to see that your database is accessible from all our global PoPs.
## Step 2: Connect to your PolyScale Cache
Using your PolyScale cache is simple -- instead of connecting to your Supabase database directly, you'll replace your original connection string with the PolyScale connection string in your application.
For example, if your original connection string was: `postgres://postgres:zqSPGHFAbPLvVCKw@db.rogpiubvixysbakciwqz.supabase.co:5432`
Your PolyScale connection string would be: `postgres://postgres:zqSPGHFAbPLvVCKw@psedge.global:5432?application_name=a645cb93-fa53-46b2-9d6c-227e357e5bfb`
You can read more about connecting to PolyScale [here](https://docs.polyscale.ai/connecting-to-polyscale#postgresql)
That's it.
## All done!
You can read more about PolyScale [here](https://www.polyscale.ai/) or check out our [documentation](https://docs.polyscale.ai/).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,291 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'prisma',
title: 'Prisma',
description: 'Connect your Supabase postgres database to your Prisma project.',
}
This guide explains how to quickly connect the Postgres database provided by Supabase to a Prisma project.
[Prisma](https://prisma.io) is an [open source](https://github.com/prisma/prisma) next-generation ORM. It consists of the following parts:
- **Prisma Client**: Auto-generated and type-safe query builder for Node.js & TypeScript.
- **Prisma Migrate**: Migration system.
- **Prisma Studio**: GUI to view and edit data in your database.
## Step 1: Get the connection string from Supabase project settings
Go to the settings page from the sidebar and navigate to the **Database** tab. You’ll find the database’s connection string with a placeholder for the password you provided when you created the project.
![Getting the connection string](/docs/img/guides/integrations/prisma/zntcsh3ic91gf1gy8j73.png)
## Step 2: Testing the connection
To make sure that everything works correctly, let’s try the connection string in a Prisma project.
If you already have one, all you need to do is set the `DATABASE_URL` to the connection string (including the password) in your `.env` file, and you’re good to go.
In case you don’t have a Prisma project or this is your first time working with Prisma, you’re going to use the repo from the [quickstart](https://www.prisma.io/docs/getting-started/quickstart) guide.
### Cloning the starter project
Navigate into a directory of your choice and run the following command in your terminal:
```bash
curl https://codeload.github.com/prisma/prisma-examples/tar.gz/latest | tar -xz --strip=2 prisma-examples-latest/databases/postgresql-supabase
```
You can now navigate into the directory and install the project’s dependencies:
```bash
cd postgresql-supabase
npm install
```
### A look at the project’s structure
This project comes with TypeScript configured and has the following structure.
- A `prisma` directory which contains:
- A `seed.ts` file: This is the data used to seed your database.
- A `schema.prisma` file: Where you define the different database models and relations between them.
- A `script.ts` file: where you will run some queries using Prisma Client.
This starter also comes with the following packages installed:
- [`@prisma/client`](https://www.npmjs.com/package/@prisma/client): An auto-generated and type-safe query builder that’s _tailored_ to your data.
- [`prisma`](https://www.npmjs.com/package/prisma): Prisma’s command-line interface (CLI). It allows you to initialize new project assets, generate Prisma Client, and analyze existing database structures through introspection to automatically create your application models.
> Note: Prisma works with both JavaScript and TypeScript. However, to get the best possible development experience, using TypeScript is highly recommended.
### Configuring the project
Create a `.env` file at the root of your project:
```bash
touch .env
```
In the `.env` file, add a `DATABASE_URL` variable and add the connection string from **step 1**. The `.env` file should look like:
```bash .env
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
```
This is what your `schema.prisma` file should look like:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
}
generator client {
provider = "prisma-client-js"
}
model Post {
id Int @id @default(autoincrement())
title String
content String?
published Boolean @default(false)
author User? @relation(fields: [authorId], references: [id])
authorId Int?
}
model User {
id Int @id @default(autoincrement())
email String @unique
name String?
posts Post[]
}
```
To test that everything works correctly, run the following command to create a migration:
```bash
npx prisma migrate dev --name init
```
You can optionally give your migration a name, depending on the changes you made. Since this is the project’s first migration, you’re setting the `--name` flag to “init”. If everything works correctly, you should get the following message in your terminal:
```text
Your database is now in sync with your schema.
:heavy_check_mark: Generated Prisma Client (4.x.x) to ./node_modules/@prisma/client in 111ms
```
This will create a `prisma/migrations` folder inside your `prisma` directory and synchronize your Prisma schema with your database schema.
> **Note**: If you want to skip the process of creating a migration history, you can use the [`prisma db push`](https://www.prisma.io/docs/concepts/components/prisma-migrate/db-push) command instead of `prisma migrate dev`. However, we recommend using `prisma migrate dev` to evolve your database schema in development.
> If you would like to get a conceptual overview of how Prisma Migrate works and which commands to use in what environment, refer to [this page in the Prisma documentation](https://www.prisma.io/docs/concepts/components/prisma-migrate/mental-model).
If you go to your Supabase project, in the table editor, you should see that two tables have been created, a `Post`, `User`, and `_prisma_migrations` tables. The `_prisma_migrations` table is used to keep track of the migration history and ensure that the database schema stays in sync with your Prisma schema.
![tables created in the UI](/docs/img/guides/integrations/prisma/7y4qq4wwvfrheti6r09u.png)
That’s it! You have now successfully connected a Prisma project to a PostgreSQL database hosted on Supabase and ran your first migration.
## Connection pooling with Supabase
If you’re working in a serverless environment (for example Node.js functions hosted on AWS Lambda, Vercel or Netlify Functions), you need to set up [connection pooling](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#serverless-environments-faas) using a tool like [PgBouncer](https://www.pgbouncer.org/). That’s because every function invocation may result in a [new connection to the database](https://www.prisma.io/docs/guides/performance-and-optimization/connection-management#the-serverless-challenge).
Supabase [supports connection management using PgBouncer](/docs/guides/database/connecting-to-postgres#connection-pool) which prevents a traffic spike from overwhelming your database.
Go to the **Database** page from the sidebar in the Supabase dashboard and navigate to **Connection pool** settings:
![Connection pool settings](/docs/img/guides/integrations/prisma/w0oowg8vq435ob5c3gf0.png)
When updating your database schema, you need to use the non-pooled connection URL (like the one used in **step 1**). You can configure the non-pooled connection string by using the `directUrl` property in the datasource block.
Update your `.env` file with the following changes:
1. Rename the `DATABASE_URL` environment variable to `DIRECT_URL`
1. Create a `DATABASE_URL` environment variable and paste in the new connection string from the dashboard as its value
Append the `?pgbouncer=true` flag to the `DATABASE_URL` variable.
Your `.env` file should resemble the following:
```bash .env
# PostgreSQL connection string used for migrations
DIRECT_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:5432/postgres"
# PostgreSQL connection string with pgBouncer config — used by Prisma Client
DATABASE_URL="postgres://postgres:[YOUR-PASSWORD]@db.[YOUR-PROJECT-REF].supabase.co:6543/postgres?pgbouncer=true"
```
Update your Prisma schema by setting the `directUrl` in the datasource block:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
```
> **Note**: This feature is available from Prisma version [4.10.0](https://github.com/prisma/prisma/releases/tag/4.10.0) and higher.
If you want to learn more about Prisma, check out the [docs](https://www.prisma.io/docs/reference/api-reference/prisma-schema-reference#fields). Also in case you have any questions or run into any issue, feel free to start a discussion in the repo’s [discussions section](https://github.com/prisma/prisma/discussions).
## Troubleshooting
### Missing grants
If your database schema is out of sync from your migration history, `prisma migrate dev` will detect a migration history conflict or a [schema drift](https://www.prisma.io/docs/guides/database/developing-with-prisma-migrate/troubleshooting-development#schema-drift). When `prisma migrate dev` detects the drift, it might ask to to reset your database schema. If you choose yes, it will delete the `public` schema along with the default grants defined in your database.
If you run into this problem, create a draft migration using `prisma migrate dev --create-only`, and add the following helper SQL:
```sql
grant usage on schema public to postgres, anon, authenticated, service_role;
grant all privileges on all tables in schema public to postgres, anon, authenticated, service_role;
grant all privileges on all functions in schema public to postgres, anon, authenticated, service_role;
grant all privileges on all sequences in schema public to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on tables to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on functions to postgres, anon, authenticated, service_role;
alter default privileges in schema public grant all on sequences to postgres, anon, authenticated, service_role;
```
Run `prisma migrate dev` to apply the draft migration to the database.
### Using Supabase Auth with Prisma
If you would like to use Supabase Auth and Prisma in your application, you will have to enable the `multiSchema` Preview feature flag in the `generator` block of your Prisma schema:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
```
Next, specify the database schemas you would like to include in your Prisma schema:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
schemas = ["public", "auth"]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["multiSchema"]
}
```
You can then specify what schema a model or enum belongs to using the `@@schema` attribute:
```go
model User {
id Int @id
// ...
@@schema("auth") // or @@schema("public")
}
```
To learn more about using Prisma with multiple database schemas, refer to [this page in the Prisma docs](https://www.prisma.io/docs/guides/database/multi-schema#learn-more-about-the-multischema-preview-feature).
### Using PostgreSQL Row Level Security with Prisma
If you would like to use Row Level Security (RLS) with Prisma, check out the [Prisma Client Extension - Row Level Security example](https://github.com/prisma/prisma-client-extensions/tree/main/row-level-security) that provides the primitives you could use to build and extend Prisma Client in PostgreSQL.
Also check out [useSupabaseRowLevelSecurity](https://github.com/dthyresson/prisma-extension-supabase-rls) Prisma Client extension that supports [Supabase RLS](/docs/guides/auth/row-level-security#authrole) and policies written to use [Supabase auth](/docs/guides/auth/overview).
The example and extension use [Prisma Client extensions](https://www.prisma.io/docs/concepts/components/prisma-client/client-extensions) Preview feature.
### Enabling PosgreSQL extensions
If you would like to use a PostgreSQL extension with Prisma, enable the `postgresqlExtensions` Preview feature flag in the `generator` block of your Prisma schema:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
```
Next, specify the extensions you need in the `datasource` block:
```go
datasource db {
provider = "postgresql"
url = env("DATABASE_URL")
directUrl = env("DIRECT_URL")
extensions = [hstore(schema: "myHstoreSchema"), pg_trgm, postgis(version: "2.1")]
}
generator client {
provider = "prisma-client-js"
previewFeatures = ["postgresqlExtensions"]
}
```
To learn more about using Prisma with PostgreSQL extensions, refer to [this page in the Prisma docs](https://www.prisma.io/docs/concepts/components/prisma-schema/postgresql-extensions).
## Resources
- [Prisma](https://prisma.io) official website.
- [Prisma GitHub](https://github.com/prisma/prisma).
- [Prisma](https://www.prisma.io/docs/) documentation.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,91 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'sequin',
title: 'Sequin',
description:
'Sync platforms like Stripe and Salesforce with your Supabase database in real-time using Sequin.',
}
This guide explains how to quickly setup a sync between Sequin and a Supabase Postgres database.
[Sequin](https://sequin.io) allows you to sync platforms like Stripe and Salesforce with Supabase in real-time. You'll be able to read and write to your [Stripe](https://stripe.com/) customers or [Salesforce](https://www.salesforce.com/) accounts right from the Supabase client using SQL. Here's how to get setup.
## Step 1: Connect Sequin to Supabase
To connect Supabase to Sequin, you'll first need to retrieve the credentials for your Supabase Postgres database:
1. In the Supabase dashboard, go to the settings page and open up your **Database** settings. In the **Connection info** section, you'll find the credentials you need - like `host` and `user`:
![TBD](/docs/img/guides/integrations/sequin/001_supabase_dash.png)
2. In the [Sequin console](https://app.sequin.io), go to your sync's configuration and open the **Destination** section. Select **Launch or Connect** and then click **Connect** to configure the connection to your Supabase Postgres:
![TBD](/docs/img/guides/integrations/sequin/002_connect.png)
3. In the connection modal that appears, enter the `Host` and `Port` for your Supabase database and click **Continue**.
![TBD](/docs/img/guides/integrations/sequin/003_step_1.png)
4. Now, enter the `Database name` and set the `schema` name for your sync. For instance, if your syncing Stripe, you'll likely want to name your synced schema something like `stripe`. Finally, enter the `user` and `password` for your Supabase database and then click **Continue**. Sequin will verify it can properly connect to your database with the correct permissions.
![TBD](/docs/img/guides/integrations/sequin/004_step_2.png)
5. Sequin is now connected to your Supabase Postgres database and will ask you to confirm which database users should be able to access your synced schema. Select all of the users and click **Continue**:
![TBD](/docs/img/guides/integrations/sequin/005_step_3.png)
6. That's it. Sequin will now create a new schema and permissions group in your Supabase database. Name the database connection in Sequin something like `Supabase` and your done!
In the Supabase dashboard, you can go to the **Table Editor** and you'll see a new schema full of your synced platform data.
![TBD](/docs/img/guides/integrations/sequin/006_see_data.png)
## Step 2: Grant Permissions
To ensure the right users can access the synced schema Sequin manages, you'll need to run a couple permission grants.
1. In the Sequin console, click the **Connect** button next to your sync and copy down your `Schema` and unique `Read Group`.
![TBD](/docs/img/guides/integrations/sequin/007_get_read.png)
2. Now, in the Supabase dashboard, go to the **SQL Editor** and run the following permission grants:
```sql
GRANT sequin_read_▒▒▒▒ TO postgres, anon, authenticated, service_role;
GRANT USAGE ON SCHEMA {{your_schema_name}} TO anon, authenticated, service_role;
GRANT ALL ON ALL TABLES IN SCHEMA {{your_schema_name}} TO anon, authenticated, service_role;
ALTER DEFAULT PRIVILEGES FOR ROLE postgres, supabase_admin IN SCHEMA {{your_schema_name}} GRANT ALL ON TABLES TO anon, authenticated, service_role;
```
These permission grants ensure that the various Supabase database users can access and read all the tables in your synced schema.
## Step 3: Configure the Supabase Client
Finally, you'll need to define a new [Supabase client](/docs/reference/javascript/initializing#api-schemas) in your application to access your synced schema. In the file where you initialized your Supabase client, define a new client with a `schema` parameter:
```javascript
export const supabase_schema = createClient(
'https://xyzcompany.supabase.co',
'public-anon-key',
{
schema: {{your_schema_name}},
}
);
```
You'll use this client to query for data in your synced schema.
## Resources
- [Sequin](https://sequin.io) official website.
- [Sequin Console](https://app.sequin.io).
- [Sequin](https://docs.sequin.io/welcome) documentation.
- [Sequin + Supabase + Stripe Tutorial](https://github.com/sequin-io/build-a-saas-with-next-js-supabase-stripe-and-sequin)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,111 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'snaplet',
title: 'Snaplet',
description:
'Get started with Supabase and Snaplet, a developer tool for working with safe, versioned, up-to-date production-like data',
}
This step-by-step guide explains how to use Snaplet to clone your production Supabase project into another development database.
[Snaplet](https://snaplet.dev/) is a developer tool that copies a Postgres database, transforming personal information, so that you can **safely code against actual data.** This functionality makes it possible to easily achieve environment parity in Supabase.
Let's get started!
Follow along in the video below as the founder of Snaplet, Peter Pistorius, takes you through the entire process. Otherwise, you can skip the video and dive into the step-by-step guide.
<div className="video-container">
<iframe
src="https://www.youtube.com/embed/oPtMMhdhEP4?rel=0"
frameborder="0"
allow="accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture; modestbranding; showinfo=0"
allowfullscreen
width="100%"
height="400px"
></iframe>
</div>
## Step 1: Prerequisites
1. **A production Supabase project's connection string:** These can be found in Supabase via `Organization > Project > Database > Connection Pooling > Connection string`
2. **A development Supabase project's connection string:** Same steps as above, but a different project/environment
3. **A read-only role** in Production (recommended): This can be done by running the following statements on Supabase via `Organization > SQL Editor > + New Query`
> To create a read-only role across all schemas you can checkout the Snaplet [docs](https://docs.snaplet.dev/guides/postgresql/#create-a-read-only-role)
## Step 2: Copying your production database
### 2.1. Connect your data source
Navigate to [https://www.snaplet.dev/](https://www.snaplet.dev/) and sign up for a new account (it’s free). Once you have successfully signed up for a new account, create a team, and start by connecting to your Supabase project.
![connect-your-database](/docs/img/guides/integrations/snaplet/connect-your-database.png)
Enter the credentials of your production Supabase project. Find the "Connection string" in Supabase via `Organization > Project > Settings > Database > Connection string` (at the bottom of the page).
The password is the same password you used when creating the Supabase project.
![supabase-connection-db-info](/docs/img/guides/integrations/snaplet/supabase-connection-db-info.png)
You’ll have to confirm providing Snaplet access to your database. Snaplet will prompt you to only provide `read-only` access to your database. Snaplet has a guide in their documentation on how to do so [here](https://docs.snaplet.dev/guides/postgresql/#create-read-only-role).
> Note that whatever connection string you provide here will be that of your Data Source – essentially the production database in a real-life scenario
![checking-database-credentials](/docs/img/guides/integrations/snaplet/checking-database-credentials.png)
### 2.2. Transform your data
![transform-your-data](/docs/img/guides/integrations/snaplet/transform-your-data.png)
The next step is to exclude any schemas that you do not require. You are able to exclude an entire schema by clicking on the drop-down at the top, selecting the schema you would like to exclude and clicking ‘Exclude schema’. Alternatively, you can select a given schema and exclude only specific tables from that particular schema. Exclude any non-required table data (such as logs) and extensions and view your columns.
At this point, Snaplet will automatically detect any columns that have Personally Identifiable Information (PII) and mark them in purple. If there are any additional columns that hold data you would like to anonymise, you can click on the respective column name and provide a replacement value for the data in that column. To complete the onboarding, click on `Review and Save` and proceed to the dashboard.
![create-your-first-snapshot](/docs/img/guides/integrations/snaplet/create-your-first-snapshot.png)
### 2.3. Create a Snapshot
Create a snapshot of your production database. This is what you’re going to restore later into your data target (more on that later in the guide).
## Step 3: Pasting into your development database
### 3.1. Create a data target on Supabase
Your data target is where you want Snaplet to restore the captured snapshot of your production project. This would most likely be either your staging or developer Supabase project. If you don’t already have a developer database setup on Supabase, create a new data target by setting up a new project on Supabase. To create a new project, follow the steps below:
1. Go to [supabase.com/dashboard](https://supabase.com/dashboard/)
2. Click on “new project”
3. Enter your project details
4. Wait for the new database to launch
> Remember the password you use when creating the project. You’ll need this password to connect your database to Snaplet later.
### 3.2. Install the Snaplet CLI
1. Open your terminal and run `curl -sL https://app.snaplet.dev/get-cli/ | bash`
2. Run `snaplet auth`
3. Navigate to [https://app.snaplet.dev/access-token/cli](https://app.snaplet.dev/access-token/cli) to get your access token
4. Paste your access token in the terminal
### 3.3. Restore to the data target
You're now ready to restore your production snapshot into your Supabase development project.
1. Navigate to your project directory
2. Run `snaplet setup` – you will be prompted to enter your database credentials. These are the database credentials of your **data target.** This could be your staging or development database
Once completed, you will be presented with a list of databases that are connected to your Snaplet account.
1. Select a data source from the list
2. Run `snaplet snapshot restore`
## All done!
As a Supabase user, you can see how this solves an issue developers all typically experience when attempting to create multiple development environments and populating each of those environments with data that can be worked with. Snaplet simplifies this process down to creating the respective Supabase projects, connecting the data source (The production database) to Snaplet and telling Snaplet where to restore that data (staging and development databases).
If you want to learn more about Snaplet, you can explore the Snaplet [docs](https://docs.snaplet.dev/). If you have any questions, feel free to [reach out on Discord](https://discord.com/invite/6HUuajc866).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,663 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'stytch',
title: 'Stytch',
description:
"Build a Next.js application powered by password-less authentication from Stytch, and Supabase's Row Level Security (RLS).",
}
In this guide we will build a simple expense tracker web application using Stytch, Supabase, and Next.js.
[Stytch](https://stytch.com?utm_source=supabase&utm_medium=guide) provides an all-in-one platform for passwordless auth. Stytch makes it easy for you to embed passwordless solutions into your websites and apps for better security, better conversion rates, and a better end user experience. Their easy-to-use SDKs and direct API access allows for maximum control and customization. In this example we will use [Email magic links](https://stytch.com/products/email-magic-links?utm_source=supabase&utm_medium=guide) to create and log in our users, and Session management. There is an additional, optional step to enable [Google One Tap](https://stytch.com/blog/improving-conversion-with-google-one-tap?utm_source=supabase&utm_medium=guide) which is an especially high-converting Google OAuth sign-up and login flow.
We will leverage Supabase to store and authorize access to user data. Supabase makes it simple to set up Row Level Security (RLS) policies which ensure users can only read and write data that they are authorized to do so. If you do not already have a Supabase account, you will need to create one.
This guide will use [Next.js](https://nextjs.org/) which is a web application framework built on top of React. Stytch provides a [Node.js library](https://github.com/stytchauth/stytch-node) and a [React library](https://stytch.com/docs/sdks/javascript-sdk) which makes building Next.js apps super easy.
> Note: You can find a completed version of this project on [Github](https://github.com/stytchauth/stytch-nextjs-supabase).
## Step 0: Create a Stytch Account
If you already have a Stytch account you may skip this step.
Go to [Stytch](https://stytch.com?utm_source=supabase&utm_medium=guide), and create an account. Note that Stytch provides two ways to create an account, either via Google OAuth, or through Email magic links.  This is the same user experience we will be building in this guide!
![Stytch redirect URL settings](/docs/img/guides/integrations/stytch/01.png)
## Step 1: Set up Stytch redirect URLs
First we need to add the redirect URLs that will be used during the Email magic link flow. This step helps ensure bad actors cannot spoof your magic links and hijack redirects.
Navigate to your [redirect URL settings](https://stytch.com/dashboard/redirect-urls?utm_source=supabase&utm_medium=guide) in the Stytch dashboard, and under **Test environment** create an entry where the **URL** is `http://localhost:3000/api/authenticate` and the **Type** is `All`.
![Edit Stytch redirect URL settings](/docs/img/guides/integrations/stytch/02.png)
After pressing **Confirm**, the redirect URLs dashboard will update to show your new entry. We will use this URL later on.
![Stytch redirect URL settings](/docs/img/guides/integrations/stytch/03.png)
## Step 2: Create a Supabase project
From your [Supabase dashboard](https://supabase.com/dashboard/), click **New project**.
Enter a `Name` for your Supabase project.
Enter a secure `Database Password`.
Click **Create new project**. It may take a couple minutes for your project to be provisioned.
![New Supabase project settings](/docs/img/guides/integrations/stytch/04.png)
## Step 3: Creating data in Supabase
Once your Supabase project is provisioned, click Table editor, then New table. This tool is available from the sidebar menu in the [Supabase dashboard](https://supabase.com/dashboard/).
Enter `expenses` as the **Name** field.
Select `Enable Row Level Security (RLS)`.
Add three new columns:
- `user_id` as `text`
- `title` as `text`
- `value` as `float8`
Click **Save** to create the new table.
![Creating a new table](/docs/img/guides/integrations/stytch/05.png)
From the Table editor view, select the expenses table and click **Insert row**.
Fill out the title and value fields (leave user_id blank for now) and click **Save**.
![Creating a new row](/docs/img/guides/integrations/stytch/06.png)
Use **Insert Row** to further populate the table with expenses.
![Multiple rows](/docs/img/guides/integrations/stytch/07.png)
## Step 4: Building a Next.js app
Using a terminal, create a new Next.js project:
```bash
npx create-next-app stytch-supabase-example
```
Next, within `stytch-supabase-example` create a `.env.local` file and enter the following values:
```
STYTCH_PROJECT_ENV=test
STYTCH_PROJECT_ID=GET_FROM_STYTCH_DASHBOARD
STYTCH_PUBLIC_TOKEN=GET_FROM_STYTCH_DASHBOARD
STYTCH_SECRET=GET_FROM_STYTCH_DASHBOARD
NEXT_PUBLIC_SUPABASE_URL=GET_FROM_SUPABASE_DASHBOARD
NEXT_PUBLIC_SUPABASE_KEY=GET_FROM_SUPABASE_DASHBOARD
SUPABASE_SIGNING_SECRET=GET_FROM_SUPABASE_DASHBOARD
```
> Note: Stytch values can be found in the project [dashboard](https://stytch.com/dashboard/api-keys?utm_source=supabase&utm_medium=guide) under **API Keys**.
![Stytch API keys](/docs/img/guides/integrations/stytch/08.png)
> Note: Supabase values can be found under **Settings** > **API** for your project.
![Supabase API keys](/docs/img/guides/integrations/stytch/09.png)
Start your Next.js development server to read in the new values from `.env.local`.
```bash
npm run dev
```
You should have a running Next.js application on `localhost:3000`.
## Step 5: Build the Login Form
Now we will replace the default Next.js home page with a login UI. We will use the Stytch React library.
> Note: Stytch provides direct API access for those that want to build login UI themselves
Install the `@stytch/stytch-react` library.
```bash
npm install @stytch/stytch-react
```
In the root directory, create a new folder named `components` and file in that folder named `/StytchLogin.js`. Within this file, paste the snippet below. This will configure, and style the Stytch React component to use Email magic links.
```jsx
// components/StytchLogin.js
import React from 'react'
import { Stytch } from '@stytch/stytch-react'
const stytchConfig = {
loginOrSignupView: {
products: ['emailMagicLinks'],
emailMagicLinksOptions: {
loginRedirectURL: 'http://localhost:3000/api/authenticate',
loginExpirationMinutes: 30,
signupRedirectURL: 'http://localhost:3000/api/authenticate',
signupExpirationMinutes: 30,
createUserAsPending: true,
},
},
style: {
fontFamily: '"Helvetica New", Helvetica, sans-serif',
width: '321px',
primaryColor: '#0577CA',
},
}
const StytchLogin = ({ publicToken }) => {
return (
<Stytch
publicToken={publicToken}
loginOrSignupView={stytchConfig.loginOrSignupView}
style={stytchConfig.style}
/>
)
}
export default StytchLogin
```
Additionally, create a profile component by creating a file called `Profile.js` in `/components`. We will use this component to render our expenses stored in Supabase later on.
```jsx
// components/Profile.js
import React from 'react'
import Link from 'next/link'
export default function Profile({ user }) {
return (
<div>
<h1>Welcome {user.userId}</h1>
<h2>Your expenses</h2>
{user.expenses?.length > 0 ? (
user.expenses.map((expense) => (
<p key={expense.id}>
{expense.title}: ${expense.value}
</p>
))
) : (
<p>You have no expenses!</p>
)}
<Link href="/api/logout" passHref>
<button>
<a>Logout</a>
</button>
</Link>
</div>
)
}
```
Finally, replace the contents of the file `/pages/index.js` to render our new `StytchLogin` and `Profile` components.
```jsx
// pages/index.js
import styles from '../styles/Home.module.css'
import Profile from '../components/Profile'
import StytchLogin from '../components/StytchLogin'
const Index = ({ user, publicToken }) => {
let content
if (user) {
content = <Profile user={user} />
} else {
content = <StytchLogin publicToken={publicToken} />
}
return <div className={styles.main}>{content}</div>
}
export async function getServerSideProps({ req, res }) {
const user = null // Will update later
return {
props: { user, publicToken: process.env.STYTCH_PUBLIC_TOKEN },
}
}
export default Index
```
On `localhost:3000` there is now a login form prompting for your email address.
![Email login step one](/docs/img/guides/integrations/stytch/10.png)
Enter your email address and press **Continue with email**.
![Email login step two](/docs/img/guides/integrations/stytch/11.png)
In your inbox you will find a login request from your app.
![Email login step three](/docs/img/guides/integrations/stytch/12.png)
However, if you click the link in the email you will get a 404. We need to build an API route to handle the email magic link authentication.
## Step 6: Authenticate and start a session
To make authentication easier we will use the Stytch Node.js library. Run
```bash
npm install stytch
```
Additionally, we will need to store the authenticated session in a cookie. Run
```bash
npm install cookies-next
```
Create a new folder named `utils` and inside a file named`stytchLogic.js` with the following contents
```jsx
// utils/stytchLogic.js
import * as stytch from 'stytch'
import { getCookie, setCookies, removeCookies } from 'cookies-next'
export const SESSION_COOKIE = 'stytch_cookie'
let client
const loadStytch = () => {
if (!client) {
client = new stytch.Client({
project_id: process.env.STYTCH_PROJECT_ID,
secret: process.env.STYTCH_SECRET,
env: process.env.STYTCH_PROJECT_ENV === 'live' ? stytch.envs.live : stytch.envs.test,
})
}
return client
}
export const getAuthenticatedUserFromSession = async (req, res) => {
const sessionToken = getCookie(SESSION_COOKIE, { req, res })
if (!sessionToken) {
return null
}
try {
const stytchClient = loadStytch()
const resp = await stytchClient.sessions.authenticate({
session_token: sessionToken,
})
return resp.session.user_id
} catch (error) {
console.log(error)
return null
}
}
export const revokeAndClearSession = async (req, res) => {
const sessionToken = getCookie(SESSION_COOKIE, { req, res })
if (sessionToken) {
try {
const stytchClient = loadStytch()
await stytchClient.sessions.revoke({
session_token: sessionToken,
})
} catch (error) {
console.log(error)
}
removeCookies(SESSION_COOKIE, { req, res })
}
return res.redirect('/')
}
export const authenticateTokenStartSession = async (req, res) => {
const { token, type } = req.query
let sessionToken
try {
const stytchClient = loadStytch()
const resp = await stytchClient.magicLinks.authenticate(token, {
session_duration_minutes: 30,
})
sessionToken = resp.session_token
} catch (error) {
console.log(error)
const errorString = JSON.stringify(error)
return res.status(400).json({ errorString })
}
setCookies(SESSION_COOKIE, sessionToken, {
req,
res,
maxAge: 60 * 60 * 24,
secure: true,
})
return res.redirect('/')
}
```
This logic is responsible for setting up the Stytch client we will use to call the API. It provides functions we will use to login, logout, and validate user sessions.
In order to complete the email login flow, create a new file `pages/api/authenticate.js` with the contents:
```jsx
// pages/api/authenticate.js
import { authenticateTokenStartSession } from '../../utils/stytchLogic'
export default async function handler(req, res) {
return authenticateTokenStartSession(req, res)
}
```
We will also create a logout API endpoint with similar contents. In `pages/api/logout.js` include the following:
```jsx
// pages/api/logout.js
import { revokeAndClearSession } from '../../utils/stytchLogic'
export default async function handler(req, res) {
return revokeAndClearSession(req, res)
}
```
Finally, update `pages/index.js` by importing `getAuthenticatedUserFromSession`, and calling it to set the user variable in `getServerSideProps`.
```jsx
// pages/index.js
import styles from '../styles/Home.module.css'
import StytchLogin from '../components/StytchLogin'
import Profile from '../components/Profile'
import { getAuthenticatedUserFromSession } from '../utils/stytchLogic'
const Index = ({ user, publicToken }) => {
let content
if (user) {
content = <Profile user={user} />
} else {
content = <StytchLogin publicToken={publicToken} />
}
return <div className={styles.main}>{content}</div>
}
export async function getServerSideProps({ req, res }) {
const userId = await getAuthenticatedUserFromSession(req, res)
if (userId) {
return {
props: { user: { userId }, publicToken: process.env.STYTCH_PUBLIC_TOKEN },
}
}
return {
props: { publicToken: process.env.STYTCH_PUBLIC_TOKEN },
}
}
export default Index
```
Return to `localhost:3000`, and login again by sending yourself a new email. Upon clicking through in the email you should be presented with “Welcome $USER_ID”. If you refresh the page, you should remain in an authenticated state. If you press **Logout** then you should return to the login screen.
![Profile page](/docs/img/guides/integrations/stytch/13.png)
Now that we have a working login flow with persistent authentication it is time to pull in our expense data from Supabase.
## Step 7: Requesting user data from Supabase
First, install the Supabase client:
```bash
npm install @supabase/supabase-js
```
In order to pass an authenticated `user_id` to Supabase we will package it within a JWT. Install jsonwebtoken:
```bash
npm install jsonwebtoken
```
Create a new file `utils/supabase.js` and add the following:
```jsx
// utils/supabase.js
import { createClient } from '@supabase/supabase-js'
import jwt from 'jsonwebtoken'
const getSupabase = (userId) => {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
)
if (userId) {
const payload = {
userId,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
}
supabase.auth.session = () => ({
access_token: jwt.sign(payload, process.env.SUPABASE_SIGNING_SECRET),
})
}
return supabase
}
export { getSupabase }
```
Our payload for the JWT will contain our user's unique identifier from Stytch, their `user_id`. We are signing this JWT using Supabase's signing secret, so Supabase will be able to validate it is authentic and hasn't been tampered with in transit.
Let's load our expenses from Supabase on the home page! Update `pages/index.js` a final time to make a request for expense data from Supabase.
```jsx
import styles from '../styles/Home.module.css'
import StytchLogin from '../components/StytchLogin'
import Profile from '../components/Profile'
import { getAuthenticatedUserFromSession } from '../utils/stytchLogic'
import { getSupabase } from '../utils/supabase'
const Index = ({ user, publicToken }) => {
let content
if (user) {
content = <Profile user={user} />
} else {
content = <StytchLogin publicToken={publicToken} />
}
return <div className={styles.main}>{content}</div>
}
export async function getServerSideProps({ req, res }) {
const userId = await getAuthenticatedUserFromSession(req, res)
if (userId) {
const supabase = getSupabase(userId)
const { data: expenses } = await supabase.from('expenses').select('*')
return {
props: {
user: { userId, expenses },
publicToken: process.env.STYTCH_PUBLIC_TOKEN,
},
}
} else {
return {
props: { publicToken: process.env.STYTCH_PUBLIC_TOKEN },
}
}
}
export default Index
```
When we reload our application, we are still getting the empty state for expenses.
This is because we enabled Row Level Security, which blocks all requests by default and lets you granularly control access to the data in your database. To enable our user to select their expenses we need to write a RLS policy.
## Step 8: Write a policy to allow select
Our policy will need to know who our currently logged in user is to determine whether or not they should have access. Let's create a PostgreSQL function to extract the current user from our new JWT.
Navigate back to the Supabase dashboard, select SQL from the sidebar menu, and click **New query**. This will create a new query,, which will allow us to run any SQL against our Postgres database.
Write the following and click **Run**.
```sql
create or replace function auth.user_id() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
```
You should see the output `Success, no rows returned`. This created a function called `auth.user_id()`, which will inspect the `userId` field of our JWT payload.
> Note: To learn more about PostgreSQL functions, check out this [deep dive video](https://www.youtube.com/watch?v=MJZCCpCYEqk).
Let's create a policy that checks whether this user is the owner of an expense.
Select **Authentication** from the Supabase sidebar menu, click **Policies**, then **New Policy**.
![Supabase authentication page](/docs/img/guides/integrations/stytch/14.png)
From the modal, select **For full customization create a policy from scratch** and add the following.
![Supabase create policy page](/docs/img/guides/integrations/stytch/15.png)
This policy is calling the function we just created to get the currently logged in user's `user_id` `auth.user_id()` and checking whether this matches the `user_id` column for the current expense. If it does, then it will allow the user to select it, otherwise it will continue to deny.
Click Review and then Save policy. After you've saved, click Enable RLS on the table to enable the policy we just created.
> Note: To learn more about RLS and policies, check out this [video](https://www.youtube.com/watch?v=Ow_Uzedfohk).
The last thing we need to do is update the `user_id` columns for our existing expenses.
Head back to the Supabase dashboard, and select Table editor from the sidebar. You will notice each entry has `user_id` set to `NULL`. We need to update this value to the proper `user_id`.
![Supabase null users in table](/docs/img/guides/integrations/stytch/16.png)
To get the `user_id` for our Stytch user, you can pull it from the welcome page in our example app (eg `user-test-61497d40-f957-45cd-a6c8-5408d22e93bc`).
![Get user_id](/docs/img/guides/integrations/stytch/17.png)
Update each row in Supabase to this `user_id`.
![Populate user_id](/docs/img/guides/integrations/stytch/18.png)
Return to `localhost:3000`, and you will see your expenses listed.
![Listed expenses](/docs/img/guides/integrations/stytch/19.png)
We now have a basic expense tracker application powered by Stytch, Supabase, and Next.js. From here you could add additional features like adding, editing, and organizing your expenses further.
> Note: You can find a completed version of this project on [Github](https://github.com/stytchauth/stytch-nextjs-supabase).
## Optional: Add Google One Tap
In this optional step, we will extend our application to allow users to login with Google One Tap in addition to Email magic links.
You will need to follow the first four steps of [this guide](https://stytch.com/docs/oauth?utm_source=supabase&utm_medium=guide#guides_google-sdk) to create a Google project, set up Google OAuth consent, and configure credentials and redirect URLs.
First, we will make some adjustments to the `StytchLogin` component. We will update the configuration, so that it uses both Google OAuth, and Email magic links.
```jsx
// components/StytchLogin.js
import React from 'react'
import { Stytch } from '@stytch/stytch-react'
const stytchConfig = {
loginOrSignupView: {
products: ['oauth', 'emailMagicLinks'],
oauthOptions: {
providers: [
{
type: 'google',
one_tap: true,
position: 'embedded',
},
],
loginRedirectURL: 'http://localhost:3000/api/authenticate?type=oauth',
signupRedirectURL: 'http://localhost:3000/api/authenticate?type=oauth',
},
emailMagicLinksOptions: {
loginRedirectURL: 'http://localhost:3000/api/authenticate',
loginExpirationMinutes: 30,
signupRedirectURL: 'http://localhost:3000/api/authenticate',
signupExpirationMinutes: 30,
createUserAsPending: true,
},
},
style: {
fontFamily: '"Helvetica New", Helvetica, sans-serif',
width: '321px',
primaryColor: '#0577CA',
},
}
const StytchLogin = ({ publicToken }) => {
return (
<Stytch
publicToken={publicToken}
loginOrSignupView={stytchConfig.loginOrSignupView}
style={stytchConfig.style}
/>
)
}
export default StytchLogin
```
We also need to make an adjustment to the function `authenticateTokenStartSession` in `stytchLogic.js`. Stytch has separate authentication endpoints for Email magic links and OAuth, so we need to route our token correctly.
```jsx
// utils/stytchLogic.js
// leave the rest of the file contents as is
export const authenticateTokenStartSession = async (req, res) => {
const { token, type } = req.query
let sessionToken
try {
const stytchClient = loadStytch()
if (type == 'oauth') {
const resp = await stytchClient.oauth.authenticate(token, {
session_duration_minutes: 30,
session_management_type: 'stytch',
})
sessionToken = resp.session.stytch_session.session_token
} else {
const resp = await stytchClient.magicLinks.authenticate(token, {
session_duration_minutes: 30,
})
sessionToken = resp.session_token
}
} catch (error) {
console.log(error)
const errorString = JSON.stringify(error)
return res.status(400).json({ errorString })
}
setCookies(SESSION_COOKIE, sessionToken, {
req,
res,
maxAge: 60 * 60 * 24,
secure: true,
})
return res.redirect('/')
}
```
With these two changes you will now have a working Google One Tap authentication method along with email magic links.
![Google One Tap](/docs/img/guides/integrations/stytch/20.png)
## Resources
- [Stytch blog](https://stytch.com/blog?utm_source=supabase&utm_medium=guide)
- [Stytch documentation](https://stytch.com/docs?utm_source=supabase&utm_medium=guide)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,406 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'supertokens',
title: 'SuperTokens',
description:
'Create a Next.js application secured by SuperTokens and PostgreSQL Row Level Security.',
}
[SuperTokens](https://www.supertokens.com) is an open source authentication solution which provides many stratergies for authenticating and managing users. You can use the managed service for easy setup or you can self host the solution to have complete control over your data.
In this guide we will build a simple web application using SuperTokens, Supabase, and Next.js. You will be able to sign up using SuperTokens and your email and user ID will be stored in Supabase. Once authenticated the frontend will be able to query Supabase and retrieve the user's email. Our example app will be using the [Email-Password and Social Login](https://supertokens.com/docs/thirdpartyemailpassword/introduction) recipe for authentication and session management.
We will use Supabase to store and authorize access to user data. Supabase makes it simple to setup Row Level Security(RLS) policies which ensure users can only read and write data that belongs to them.
### Demo App
You can find a demo app using SuperTokens, Supabase and Nexts.js on [Github](https://github.com/supertokens/supertokens-auth-react/tree/master/examples/with-supabase)
## Step 1: Create a new Supabase project
From your [Supabase dashboard](https://supabase.com/dashboard/), click `New project`.
Enter a `Name` for your Supabase project.
Enter a secure `Database Password`.
Select the same `Region` you host your app's backend in.
Click `Create new project`.
![New Supabase project settings](/docs/img/guides/integrations/supertokens/supabase_dashboard_create.png)
## Step 2: Creating tables in Supabase
From the sidebar menu in the [Supabase dashboard](https://supabase.com/dashboard/), click `Table editor`, then `New table`.
Enter `users` as the `Name` field.
Select `Enable Row Level Security (RLS)`.
Remove the default columns
Create two new columns:
- `user_id` as `text` as primary key
- `email` as `text`
Click `Save` to create the new table.
![Users table](/docs/img/guides/integrations/supertokens/supabase_table_create.png)
## Step 3: Setup your Next.js App with SuperTokens.
Since the scope of this guide is limited to the integration between SuperTokens and Supabase, you can refer to the SuperTokens website to see [how to setup your Next.js app with SuperTokens](https://supertokens.com/docs/thirdpartyemailpassword/nextjs/about).
Once you finish setting up your app, you will be greeted with the following screen
![SuperTokens Auth Screen](/docs/img/guides/integrations/supertokens/supertokens_thirdpartyemailpassword_auth_screen.png)
## Step 4: Creating a Supabase JWT to access Supabase
In our Nextjs app when a user signs up, we want to store the user's email in Supabase. We would then retrieve this email from Supabase and display it on our frontend.
To use the Supabase client to query the database we will need to create a JWT signed with your Supabase app's signing secret. This JWT will also need to contain the user's userId so Supabase knows an authenticated user is making the request.
To create this flow we will need to modify SuperTokens so that, when a user signs up or signs in, a JWT signed with Supabase's signing secret is created and attached to the user's session. Attaching the JWT to the user's session will allow us to retrieve the Supabase JWT on the frontend and backend (post session verification), using which we can query Supabase.
We want to create a Supabase JWT when we are creating a SuperTokens' session. This can be done by overriding the `createNewSession` function in your backend config.
```ts
// config/backendConfig.ts
import ThirdPartyEmailPasswordNode from "supertokens-node/recipe/thirdpartyemailpassword";
import SessionNode from "supertokens-node/recipe/session";
import { TypeInput } from "supertokens-node/lib/build/types";
import { appInfo } from "./appInfo";
import jwt from "jsonwebtoken";
let backendConfig = (): TypeInput => {
return {
framework: "express",
supertokens: {
connectionURI: "https://try.supertokens.com",
},
appInfo,
recipeList: [
ThirdPartyEmailPasswordNode.init({...}),
SessionNode.init({
override: {
functions: (originalImplementation) => {
return {
...originalImplementation,
// We want to create a JWT which contains the users userId signed with Supabase's secret so
// it can be used by Supabase to validate the user when retrieving user data from their service.
// We store this token in the accessTokenPayload so it can be accessed on the frontend and on the backend.
createNewSession: async function (input) {
const payload = {
userId: input.userId,
exp: Math.floor(Date.now() / 1000) + 60 * 60,
};
const supabase_jwt_token = jwt.sign(payload, process.env.SUPABASE_SIGNING_SECRET);
input.accessTokenPayload = {
...input.accessTokenPayload,
supabase_token: supabase_jwt_token,
};
return await originalImplementation.createNewSession(input);
},
};
},
},
}),
],
isInServerlessEnv: true,
};
};
```
As seen above, we will be using the `jsonwebtoken` library to create a JWT signed with Supabase's signing secret whose payload contains the user's userId.
We will be storing this token in the `accessTokenPayload` which will essentially allow us to access the `supabase_token` on the frontend and backend whilst the user is logged in.
## Step 5: Creating a Supabase client
Create a new file called `utils/supabase.ts` and add the following:
```ts
// utils/supabase.ts
import { createClient } from '@supabase/supabase-js'
const getSupabase = (access_token) => {
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_KEY
)
supabase.auth.session = () => ({
access_token,
})
return supabase
}
export { getSupabase }
```
This will be our client for talking to Supabase. We can pass it an `access_token` and it will be attached to our request. This `access_token` is the same as the `supabase_token` we had created earlier.
## Step 6: Inserting users into Supabase when they sign up:
In our example app there are two ways for signing up a user. Email-Password and Social Login based authentication. We will need to override both these APIs such that when a user signs up, their email mapped to their userId is stored in Supabase.
```ts
// config/backendConfig.ts
import ThirdPartyEmailPasswordNode from "supertokens-node/recipe/thirdpartyemailpassword";
import SessionNode from "supertokens-node/recipe/session";
import { TypeInput } from "supertokens-node/lib/build/types";
import { appInfo } from "./appInfo";
import jwt from "jsonwebtoken";
import { getSupabase } from "../utils/supabase";
let backendConfig = (): TypeInput => {
return {
framework: "express",
supertokens: {
connectionURI: "https://try.supertokens.com",
},
appInfo,
recipeList: [
ThirdPartyEmailPasswordNode.init({
providers: [...],
override: {
apis: (originalImplementation) => {
return {
...originalImplementation,
// the thirdPartySignInUpPost function handles sign up/in via Social login
thirdPartySignInUpPOST: async function (input) {
if (originalImplementation.thirdPartySignInUpPOST === undefined) {
throw Error("Should never come here");
}
// call the sign up/in api for social login
let response = await originalImplementation.thirdPartySignInUpPOST(input);
// check that there is no issue with sign up and that a new user is created
if (response.status === "OK" && response.createdNewUser) {
// retrieve the accessTokenPayload from the user's session
const accessTokenPayload = response.session.getAccessTokenPayload();
// create a supabase client with the supabase_token from the accessTokenPayload
const supabase = getSupabase(accessTokenPayload.supabase_token);
// store the user's email mapped to their userId in Supabase
const { error } = await supabase
.from("users")
.insert({ email: response.user.email, user_id: response.user.id });
if (error !== null) {
throw error;
}
}
return response;
},
// the emailPasswordSignUpPOST function handles sign up via Email-Password
emailPasswordSignUpPOST: async function (input) {
if (originalImplementation.emailPasswordSignUpPOST === undefined) {
throw Error("Should never come here");
}
let response = await originalImplementation.emailPasswordSignUpPOST(input);
if (response.status === "OK") {
// retrieve the accessTokenPayload from the user's session
const accessTokenPayload = response.session.getAccessTokenPayload();
// create a supabase client with the supabase_token from the accessTokenPayload
const supabase = getSupabase(accessTokenPayload.supabase_token);
// store the user's email mapped to their userId in Supabase
const { error } = await supabase
.from("users")
.insert({ email: response.user.email, user_id: response.user.id });
if (error !== null) {
throw error;
}
}
return response;
},
};
},
},
}),
SessionNode.init({...}),
],
isInServerlessEnv: true,
};
};
```
As seen above, we will be overriding the `emailPasswordSignUpPOST` and `thirdPartySignInUpPOST` APIs such that if a user signs up, we retrieve the Supabase JWT (which we created in the `createNewSession` function) from the user's accessTokenPayload and send a request to Supabase to insert the email-userid mapping.
## Step 7: Retrieving the user's email on the frontend
Now that our backend is setup we can modify our frontend to retrieve the user's email from Supabase.
```tsx
// pages/index.tsx
import React, { useState, useEffect } from 'react'
import Head from 'next/head'
import styles from '../styles/Home.module.css'
import ThirdPartyEmailPassword, {
ThirdPartyEmailPasswordAuth,
} from 'supertokens-auth-react/recipe/thirdpartyemailpassword'
import dynamic from 'next/dynamic'
import { useSessionContext } from 'supertokens-auth-react/recipe/session'
import { getSupabase } from '../utils/supabase'
export default function Home() {
return (
// We will wrap the ProtectedPage component with ThirdPartyEmailPasswordAuth so only an
// authenticated user can access it. This will also allow us to access the users session information
// within the component.
<ThirdPartyEmailPasswordAuth>
<ProtectedPage />
</ThirdPartyEmailPasswordAuth>
)
}
function ProtectedPage() {
// retrieve the authenticated user's accessTokenPayload and userId from the sessionContext
const { accessTokenPayload, userId } = useSessionContext()
if (sessionContext.loading === true) {
return null
}
const [userEmail, setEmail] = useState('')
useEffect(() => {
async function getUserEmail() {
// retrieve the supabase client who's JWT contains users userId, this will be
// used by supabase to check that the user can only access table entries which contain their own userId
const supabase = getSupabase(accessTokenPayload.supabase_token)
// retrieve the user's name from the users table whose email matches the email in the JWT
const { data } = await supabase.from('users').select('email').eq('user_id', userId)
if (data.length > 0) {
setEmail(data[0].email)
}
}
getUserEmail()
}, [])
return (
<div className={styles.container}>
<Head>
<title>SuperTokens 💫</title>
<link rel="icon" href="/favicon.ico" />
</Head>
<main className={styles.main}>
<p className={styles.description}>
You are authenticated with SuperTokens! (UserId: {userId})
<br />
Your email retrieved from Supabase: {userEmail}
</p>
</main>
</div>
)
}
```
As seen above we will be using SuperTokens `useSessionContext` hook to retrieve the authenticated user's `userId` and `accessTokenPayload`. Using React's `useEffect` hook we can use the Supabase client to retrieve the user's email from Supabase using the JWT retrieved from the user's `accessTokenPayload` and their `userId`.
## Step 8: Create Policies to enforce Row Level Security for Select and Insert requests
To enforce Row Level Security for the `Users` table we will need to create policies for Select and Insert requests.
These polices will retrieve the userId from the JWT and check if it matches the userId in the Supabase table
To do this we will need a PostgreSQL function to extract the userId from the JWT.
The payload in the JWT will have the following structure:
```
// JWT payload
{
userId,
exp
}
```
To create the PostgreSQL function, lets navigate back to the Supabase dashboard, select `SQL` from the sidebar menu, and click `New query`. This will create a new query called `new sql snippet`, which will allow us to run any SQL against our Postgres database.
Write the following and click `Run`.
```sql
create or replace function auth.user_id() returns text as $$
select nullif(current_setting('request.jwt.claims', true)::json->>'userId', '')::text;
$$ language sql stable;
```
This will create a function called `auth.user_id()`, which will inspect the `userId` field of our JWT payload.
### SELECT query policy
Our first policy will check whether the user is the owner of the email.
Select `Authentication` from the Supabase sidebar menu, click `Policies`, and then `New Policy` on the `Users` table.
![Create new policy](/docs/img/guides/integrations/supertokens/create_policy.png)
From the modal, select `Create a policy from scratch` and add the following.
![Policy settings for SELECT](/docs/img/guides/integrations/supertokens/policy_config_select.png)
This policy is calling the PostgreSQL function we just created to get the currently logged in user's ID `auth.user_id()` and checking whether this matches the `user_id` column for the current `email`. If it does, then it will allow the user to select it, otherwise it will continue to deny.
Click `Review` and then `Save policy`.
### INSERT query policy
Our second policy will check whether the `user_id` being inserted is the same as the `userId` in the JWT.
Create another policy and add the following:
![Policy settings for INSERT](/docs/img/guides/integrations/supertokens/policy_config_insert.png)
Similar to the previous policy we are calling the PostgreSQL function we created to get the currently logged in user's ID `auth.user_id()` and check whether this matches the `user_id` column for the row we are trying to insert. If it does, then it will allow the user to insert the row, otherwise it will continue to deny.
Click `Review` and then `Save policy`.
## Step 9: Test your changes
You can now sign up and you should see the following screen:
![SuperTokens App Authenticated](/docs/img/guides/integrations/supertokens/supabase_app_authenticated_screen.png)
If you navigate to your table you should see a new row with the user's `user_id` and `email`.
![Supabase Users table](/docs/img/guides/integrations/supertokens/table_with_user.png)
## Resources
- [SuperTokens](https://supertokens.com/) official website.
- [SuperTokens community](https://supertokens.com/discord).
- [SuperTokens documentation](https://supertokens.com/docs/guides).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,248 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'vercel',
title: 'Vercel',
description:
"The fastest way to get up and running with an application that uses Supabase is with Vercel's Next.js starter and Supabase integration.",
}
This guide steps through using Vercel's dashboard to create a Next.js project integrated with Supabase. To further streamline the process, we will be using the Next.js starter template, which can be automatically forked to a new GitHub repo, without leaving the dashboard!
If you don’t have a Vercel account, create one [here](https://vercel.com/signup).
## Step 1: Create a Supabase project
This guide could use an existing Supabase project, but to create the `todo` demo from scratch, navigate to [Supabase](https://supabase.com/dashboard/), click `Sign In` and authenticate with GitHub to login or register a new account.
From the Supabase dashboard, click `New project` and select an organization.
> Note: You may need to create an organization first.
Give your project a `name`, `password`, select a `region` close to your potential users and click `Create new project`.
![Create a Supabase project](/docs/img/guides/integrations/vercel/create-supabase-project.png)
Supabase will take a couple of minutes to configure the infrastructure.
Once this is finished, navigate to `SQL Editor` from the sidebar menu and click `New query`.
This will create a new SQL snippet called "New Query". Copy and paste the following and click `Run`.
```sql
create table todos (
id bigint generated by default as identity primary key,
title text,
is_complete boolean default false,
created_at timestamp with time zone default timezone('utc'::text, now()) not null
);
alter table todos enable row level security;
create policy "Anyone can view todos" on todos for
select using (true);
create policy "Anyone can add new todos" on todos for
insert with check (true);
insert into todos(title)
values
('Create Supabase project'),
('Create Vercel project'),
('Install Supabase integration');
```
This will create a new todos table, enable row level security, add policies for selecting and inserting data, and add some example rows.
> Note: To simplify this example, we are allowing anyone to `select` and `insert` rows on the `todos` table. Usually, these actions would be locked down to only allow logged in users to perform them. Check out [this video](https://www.youtube.com/watch?v=Ow_Uzedfohk) to learn more about Row Level Security and policies.
## Step 2: Create Vercel project
From your [Vercel dashboard](https://vercel.com/dashboard), click `New Project`.
![Create new Vercel project](/docs/img/guides/integrations/vercel/create-vercel-project.png)
Under the `Clone Template` menu, click `Next.js`.
![Clone Next.js template](/docs/img/guides/integrations/vercel/clone-next-js-template.png)
In the `Create Git Repository` section, click `GitHub`, select your username under `GIT SCOPE`, enter a name for your project, choose whether you want your repo `private` or `public`, and click `Create`.
![New GitHub repo settings](/docs/img/guides/integrations/vercel/repo-settings.png)
This will create a new GitHub repository, clone and commit the Next.js starter project, then build and deploy your new project to Vercel.
Once you have been redirected to the `Congratulations` screen, click `Go to Dashboard`.
Navigate to `Settings`, `Integrations`, then click `Browse Marketplace`.
Search for `Supabase` and click the Supabase integration.
![Supabase integration](/docs/img/guides/integrations/vercel/supabase-integration.png)
Click `Add Integration`. Select your account from the `Vercel Scope` dropdown, and click `CONTINUE`.
![Choose scope](/docs/img/guides/integrations/vercel/choose-scope.png)
Choose `Specific Projects` and select your new Vercel project from the dropdown, and click `Add Integration`.
![Choose project](/docs/img/guides/integrations/vercel/choose-project.png)
From the Supabase popup, select your new Vercel Project and Supabase project from the dropdowns.
![Supabase integration](/docs/img/guides/integrations/vercel/link-vercel-to-supabase.png)
## Step 3: Clone GitHub repo
The fastest way to get this project running locally is to clone the repo that Vercel created for us.
Navigate back to the Vercel project `Overview` page, and click `View Git Repository`.
![Vercel Project Dashboard](/docs/img/guides/integrations/vercel/vercel-project-dashboard.png)
This will open the GitHub repo. From here, click the arrow next to `Code` and copy the url from the dropdown.
![GitHub repo url](/docs/img/guides/integrations/vercel/github-project-url.png)
Open a terminal window or CLI and run the following command to clone the GitHub repo.
```bash
git clone your-repo-url.git
```
Open the project in your code editor of choice, and update the contents of `pages/index.js` to the following:
```jsx
import styles from '../styles/Home.module.css'
export default function Home() {
return <div className={styles.container}>working</div>
}
```
Run a local development server.
```bash
npm run dev
```
Navigate to `http://localhost:3000` to confirm the project is "working".
## Step 4: Pull environment variables from Vercel
First, we need to login to Vercel using their CLI tool.
```bash
npx vercel login
```
This will ask if we are happy to install `vercel`. Type `y` and hit `Enter`.
We will then need to authenticate Vercel by selecting `Continue with GitHub`.
This will open a browser window where you need to authenticate with your GitHub account.
Next, we need to link our Vercel project.
```bash
npx vercel link
```
Step through the prompts to link the Vercel project.
![Link project from Vercel](/docs/img/guides/integrations/vercel/vercel-link.png)
Copy the environment variables from our Vercel project.
```bash
npx vercel env pull
```
This will create a `.env` file containing our Supabase environment variables. Rename this file to `.env.local` to automatically ignore it from git.
## Step 5: Install Supabase.js
Install the `supabase-js` library.
```bash
npm i @supabase/supabase-js
```
Create a new file called `/utils/supabase.js` and add the following.
```jsx
import { createClient } from '@supabase/supabase-js'
export default createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL,
process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY
)
```
Create a new file called `/components/NewTodo.js` and add the following.
```jsx
import { useState } from 'react'
import supabase from '../utils/supabase'
export default ({ reload }) => {
const [title, setTitle] = useState('')
const addTodo = async (e) => {
e.preventDefault()
await supabase.from('todos').insert({ title })
reload()
setTitle('')
}
return (
<form onSubmit={addTodo}>
<input value={title} onChange={(e) => setTitle(e.target.value)} />
</form>
)
}
```
This component will be responsible for writing a new `todo` to Supabase.
Let's import our new component in `pages/index.js` and display a list of todos.
```jsx
import { useState, useEffect } from 'react'
import styles from '../styles/Home.module.css'
import supabase from '../utils/supabase'
import NewTodo from '../components/NewTodo'
export default function Home() {
const [todos, setTodos] = useState([])
const fetchTodos = async () => {
const { data } = await supabase.from('todos').select('*')
setTodos(data)
}
useEffect(() => {
fetchTodos()
}, [])
return (
<div className={styles.container}>
<NewTodo reload={fetchTodos} />
{todos.map((todo) => (
<p key={todo.id}>{todo.title}</p>
))}
</div>
)
}
```
## Resources
- [Vercel official website](https://vercel.com).
- [Vercel blog](https://vercel.com/blog).
- [Vercel docs](https://vercel.com/docs).
- [Vercel Integration docs](https://vercel.com/docs/integrations)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,337 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'weweb',
title: 'WeWeb',
description: 'Build user interfaces on top of existing databases.',
}
This guide explains how to connect a Supabase back-end to a WeWeb front-end and then configure all the CRUD operations necessary to build an Admin Portal with user authentication, roles, and permissions.
[WeWeb](https://dashboard.weweb.io/sign-up) is a low-code front-end builder that allies the short learning curve of no-code with the freedom of code.
It connects to Supabase via two native integrations:
- one for data manipulation, and
- another for user authentication.
If you don't have an WeWeb account, you can create one [here](https://dashboard.weweb.io/sign-up).
Let's get started!
## Step 1: Add the Supabase Data Source Plugin in WeWeb
In order to read Supabase data in WeWeb, you'll first need to add the Supabase Data Source Plugin:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-11.38.44@2x.png)
Once you've added it, you will be invited to share your Supabase project URL and public API key:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-11.39.33@2x.png)
In Supabase, you can find both your project URL and public key in the `Settings` > `API` menu:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-11.45.46@2x.png)
Once you have added both to WeWeb, you will have the option to enable realtime tables if you wish to do so:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-18-at-12.22.43@2x.png)
**🚨 Warning 🚨**
> Realtime is disabled by default in Supabase for better database performance and security. Learn more about [realtime functionalities](/docs/guides/realtime).
## Step 2: GET Data from Supabase
Once you click on `Add a Collection`, you will be invited to give your Collection a name and choose Supabase as a Data source:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-11.55.15@2x.png)
You will then be able to select the Table from which you want to pull data:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-11.57.09.gif)
Notice that this gives you access to 2 separate modes to access the fields in the table:
1. a "Guided" mode, and
2. an "Advanced" mode.
### Guided mode
By default, the "Guided" mode returns the data from all the fields.
In the example below, we decide to exclude the data from the `created_at` field in our `vehicles` table:
_![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-12.04.37@2x.png)_
As a result, WeWeb does not fetch the `created_at` field.
This is helpful because we can exclude data that we don't want to load in the frontend, either because we don't need it or because it's confidential.
### Advanced mode
In our database, we created 2 separate tables for vehicles and locations.
In the `vehicles` table, we made a reference to the `locations` table in our `location_id` field so we know where each car is:
_![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-12.30.20@2x.png)_
The problem is, the link only gives us the id of the location in the `locations` table.
If you choose the "Advanced" mode, you will be able to get the `name` field of the location instead of the `id`.
How?
By [making custom queries to Supabase](https://supabase.com/docs/reference/javascript/select):
_![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-18-at-12.06.17.gif)_
In the example above, we are telling Supabase:
- from the table selected in the Collection – in this case the `vehicles` table – please send me the data in the `id`, `model`, and `mileage` fields
- look for the `location_id` in the `vehicles` table in the `locations` table and send me the data in the corresponding `name` field
If we only ask for the data from the `location` field of the `vehicles` table, Supabase will only return the `id`:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-12.39.12@2x.png)
**🚨 Warning 🚨**
> If you have enabled Row-Level Security in Supabase, make sure you have also [added a Policy](https://supabase.com/docs/learn/auth-deep-dive/auth-policies) that allows users to read the data in the table. Otherwise, WeWeb won't be able to get the data.
## Step 3: Display Supabase Data in WeWeb
Assuming you were able to fetch data from Supabase in a WeWeb Collection, you'll be able to bind the data from that Collection on your WeWeb pages.
In the example below, we chose to display the car model and mileage in the [Data Grid element](https://docs.weweb.io/add-elements/elements/data-grid.html) that comes out-of-the-box in WeWeb:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-21-at-12.31.05@2x.png)
We chose this element because it includes a built-in inline editing mode we'll want to use later for our CRUD operations.
#### 🔥 Pro Tip 🔥
> In WeWeb, you can [bind arrays of data to any Container](https://docs.weweb.io/binding-filtering/display-data.html). Just bear in mind that the first child of the Container you bind the Collection to will be the repeated item. With that in mind, you might want the first child Element to be another Container with a number of items inside like a title, description, button or image.
## Step 4: Update a record in Supabase
Once you've added a Supabase Collection of data to WeWeb, you might want to allow users to manipulate the data in that Collection.
In order to do so, you'll need to create a Workflow in WeWeb.
In the example below, we are using the "Update row" Workflow that comes by default with WeWeb's Data Grid Element.
The trigger is `On Row update`.
Since we added the Supabase Data Source Plugin above, we have access to all the CRUD actions available in Supabase:
- Select
- Insert
- Update
- Upsert
- Delete
In this case, we choose the "Update" action:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-15.27.20@2x.png)
Then, in our "Update" action, we select the `vehicles` table and map the `id` to the id of the Workflow Event:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-17.16.51@2x.png)
Finally, we tell WeWeb we want to update the `mileage` field in our Supabase table, and send the value in the `mileage` column of our Data Grid:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-17.20.24@2x.png)
And that's it!
If you switch to Preview mode, you will be update your Supabase table from your WeWeb Data Grid:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-17.35.20.gif)
**🔥 Pro Tip 🔥**
> By default, the fields in the Data Grid Element are Text fields but you can change the input type to Number if you need to send numerical data to your database:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-17.22.58@2x.png)
## Restrict who can modify a record in Supabase
By default, all the data in the tables that are in the `public` schema of your Supabase project can be read, updated, or deleted.
Supabase allows you to [enable Row-Level Security](https://supabase.com/docs/learn/auth-deep-dive/auth-row-level-security) for each of your tables:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-16.10.39@2x.png)
If you want to restrict certain actions to specific users or roles, you'll need to:
- add Supabase authentication to your WeWeb project, and
- [write SQL policies in Supabase](https://supabase.com/docs/learn/auth-deep-dive/auth-policies).
We provide a number of policy templates to get you started:
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-16.17.32@2x.png)
In the example below, we say that users can:
1. update a record
2. in the "locations" table of the "public" schema
3. if they are authenticated
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-16.19.28@2x.png)
**🔥 Pro Tip 🔥**
> Once you enable RLS on a Supabase table, you won't be able to access the data in a WeWeb Collection unless you've added a policy.
![](https://weweb-changelog.ghost.io/content/images/2022/10/CleanShot-2022-10-13-at-16.28.51.gif)
## Step 4: Add User Authentication
Once you are able to display Supabase data in WeWeb, you might want to restrict access to certain users or display specific data based on a user's role.
In order to do that, you'll need to add WeWeb's Supabase Auth Plugin.
### Add Supabase Auth Plugin in WeWeb
Supabase comes with an in-built authentication system which you can use in WeWeb.
To add the Supabase Auth Plugin in WeWeb, go to `Plugins` > `Authentication`:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-15.00.47@2x.png)
Assuming you have already provided your Supabase project URL and public API key when setting up the Supabase Data source, the only thing left to do will be to add your private API key:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-15.04.04@2x.png)
In Supabase, you can find your private API key in `Settings` > `API`:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-15.05.40@2x.png)
**🚨 Warning 🚨**
> As the name suggests, you'll want to keep this API key secret! Assuming you copy it properly in the "Private API key" field of the Supabase Auth Plugin and don't use it anywhere else in your Weweb project, Weweb will never make it public.
You will then be invited to choose a page to redirect _unauthenticated_ users, i.e. users who are NOT signed-in:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-09-at-15.17.43@2x.png)
**🚨 Warning 🚨**
> When you setup your Login Workflow, make sure you don't redirect unauthenticated users to a page that is only accessible to authenticated users. Otherwise, you'll be creating an **infinite loop** and your app will crash.
### Create User Sign Up and Sign In Workflows
In the `Add` > `UI kit` menu of WeWeb, you can find ready-made Sign in and Sign up Forms:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-11.45.02@2x.png)
Once you've added a Form to the Canvas, you'll be able to style it whichever way you want.
In the example below, we added an image with the logo of our project to a Sign up Form and changed the background color of the `Create Form` Container:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-11.49.24@2x.png)
To allow users to sign up, you'll need to create a Sign up Workflow on the Form Container:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-11.50.42@2x.png)
**🔥 Pro Tip 🔥**
> It's important that the Workflow is on the Form Container and not the Sign up Button because we want to validate the fields of the Form when users submit it.
In the Workflow, you will choose the `On submit` trigger and add the Supabase `Sign up` Action:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-11.58.23.gif)
Then, you'll want to map the email, password, and metadata information in the Form to the email, password, and metadata in Supabase before choosing what page the new user should be redirected to:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-12.02.32.gif)
In the example above, we made sure to add the user's name as an item in that user's metadata.
In Supabase, you can find the user's metadata in JSON format in a dedicated field of the `users` table, named `raw_user_meta_data`:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-12.12.46@2x.png)
The same logic will apply to any Supabase Action you want to trigger.
## Adding User Roles & Permissions
Now let's say we want to gate content and set different permissions based on a user's role.
### Adding Roles in Supabase
In Supabase, we'll need to create a `roles` table with a list of roles and a join table that links the `roles` table with our `users` table.
First, let's create a `roles` table with three roles and make sure that each role had a UUID and a `name`:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-18.39.08@2x.png)
**🚨 Warning 🚨**
> In order for the integration to work with the Users tab in WeWeb, it is crucial that the role title is a text field named `name`.
### Joining Roles and Users in Supabase
Second, let's create a `userRoles` join table:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-18.45.10@2x.png)
In the join table above, you can see we have an `id` field that is uniquely identifiable thanks to a UUID.
This unique `id` is linked to a `userId`, which is also a UUID, more specifically, it is the UUID we find in the `id` field of the `users` table in the `auth` schema:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-18.51.00@2x.png)
Each row in our `userRoles` table is also linked to a `roleId` which is the UUID we find in the `id` field of the `roles` table in the `public` schema:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-18.52.57.gif)
### Linking Users in WeWeb to Roles and Users in Supabase
Once we've added our list of roles in Supabase and created an empty join table to link our roles with users, it's time to go to WeWeb.
In `Plugins` > `Supabase Auth` > `3. Roles table`, we'll click `refresh` and select the relevant Supabase tables we just created:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-19.00.54@2x.png)
Once you've told WeWeb where to find the `roles` and the join table in Supabase, you'll be able to easily view and maintain user roles in the `Users` tab in WeWeb:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-19.03.13.gif)
When you make a change to a User in WeWeb, it will automatically be updated in Supabase.
## Users vs Profiles
So far, we've showed you how to work with the default `users` table that Supabase generates in the `auth` schema when you create a new project.
Note that, for security purposes, the information in that `users` table is not exposed on the auto-generated API.
How does this affect your project in WeWeb?
### Let users update their information
Let's say you want to let authenticated users update their information, then you don't need to set up anything else in Supabase.
You could simply create a user profile page in WeWeb and display their information when they sign in, based on the data you have in the `user` Variable:
![](https://weweb-changelog.ghost.io/content/images/2022/08/CleanShot-2022-08-19-at-19.16.17@2x.png)
### Display other users' information
In some use cases, you might want to display _other_ users' information.
For example, if you're building an HR portal in WeWeb, you might want HR employees to have access to a list of applicants and their user profiles.
You wouldn't be able to do that with the `users` table in the `auth` schema because each user's information is only available to them.
For such a use case, we recommend creating a `profiles` table in the `public` schema to store user data that you want to access via the API.
In WeWeb, you would then be able to create a Collection to get data from the `profiles` table.
Learn more about [managing user data in Supabase](https://supabase.com/docs/guides/auth/managing-user-data).
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,241 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'zuplo',
title: 'Zuplo',
description: 'Building a public API backed by Supabase.',
}
[Zuplo](https://zuplo.com) is a fully-managed API gateway that offers the easiest way to securely and safely share your API. In this guide we look at how you can combine Zuplo and Supabase to create a public API with rate-limiting, a self-serve developer portal, and API-key authentication. There is an [accompanying video for this article](https://www.youtube.com/watch?v=GJSkbxMnWxE).
![zuplo layout](/docs/img/guides/integrations/zuplo/arch.png)
In this example we're going to work with a simple table that allows people to read and write entries to a Supabase table that contains some reviews of skis. Because this is an API for developers, we have to assume that they may be calling it from another backend service and can't login as a user using the standard Supabase method. In this scenario, API keys are often a better choice - see [Wait, you're not using API keys?](https://zuplo.com/blog/2022/05/03/you-should-be-using-api-keys/).
We'll allow people, with a valid API key, to read data from the ski results table and to create new records. Hopefully it's obvious that there are many ways that you can extend this example to add more behavior like roles based access, with custom policies, custom handlers and more.
## Setting up Supabase
If you haven't already, create a new project in Supabase and create a table called ski-reviews with the following columns:
- id (int8)
- created_at (timestamptz)
- make (text)
- model (text)
- year (int8)
- rating (int2)
- author (text)
Manually enter a couple of rows of data, so that we have something to read from the DB.
## The `Get all` reviews route in Zuplo
Login to Zuplo at [portal.zuplo.com](https://portal.zuplo.com) and create a new project in Zuplo - I went with `supabase-ski-reviews`.
Select the **File** tab and choose **Routes**. Add your first route with the following settings:
- method: `GET`
- path: `/reviews`
- summary: `Get all reviews`
- version: `v1`
- CORS: `Anything goes`
And in the request handler section, paste the `READ ALL ROWS` URL of your Supabase backend (you can get to this in the **API docs** section of Supabase)
- URL Rewrite: `https://YOUR_SUPABASE_URL.supabase.co/rest/v1/ski-reviews?select=*`
- Forward Search: `unchecked`
In order to call the Supabase backend I need to add some authentication headers to the outgoing request.
Expand the **Policies** section of your route. Click **Add policy** on the **Request** pipeline.
We don't want to forward any headers that the client sends us to Supabase, so find the **Clear Headers Policy** and add that to your inbound pipeline. Note, that we will allow the `content-type` header to flow through, so this should be your policy config.
```json
{
"export": "ClearHeadersInboundPolicy",
"module": "$import(@zuplo/runtime)",
"options": {
"exclude": ["content-type"]
}
}
```
Next, we need to add the credentials to the outgoing request. We'll need to get the JWT token from supabase - you'll find it in **Settings** > **API** as shown below:
![secret_role jwt](/docs/img/guides/integrations/zuplo/secret-role.png)
Once you've got your service_role JWT, click **Add Policy** again on the **Request** pipeline and choose the **Add/Set Headers Policy** and configure it as follows:
```json
{
"export": "SetHeadersInboundPolicy",
"module": "$import(@zuplo/runtime)",
"options": {
"headers": [
{
"name": "apikey",
"value": "$env(SUPABASE_API_KEY)",
"overwrite": true
},
{
"name": "authorization",
"value": "$env(SUPABASE_AUTHZ_HEADER)",
"overwrite": true
}
]
}
}
```
Save your changes.
Next, create two secret [environment variables](https://zuplo.com/docs/deployments/environment-variables) as follows:
- SUPABASE_API_KEY: `"YOUR_SUPABASE_SECRET_ROLE_JWT"`
- SUPABASE_AUTHZ_HEADER: `"Bearer YOUR_SUPABASE_SECRET_ROLE_JWT"`
Obviously, in both instances replace `YOUR_SUPABASE_SECRET_ROLE_JWT` with your service_role JWT from Supabase.
You are now ready to invoke your API gateway and see data flow through from your Supabase backend!
Click on the **open in browser** button shown below and you should see the JSON, flowing from Supabase in your browser 👏.
![open in browser](/docs/img/guides/integrations/zuplo/open-in-browser.png)
## Adding authentication
At this point, that route is wide open to the world so we need to secure it. We'll do this using API keys. You can follow this guide [Add API key Authentication](https://zuplo.com/docs/quickstarts/add-api-key-auth). Be sure to drag the API Key authentication policy to the very top of your **Request** pipeline. Come back here when you're done.
Welcome back! You've now learned how to secure your API with API-Keys.
## Adding a Create route
Next we'll add a route that allows somebody to create a review. Add another route with the following settings
- method: `POST`
- path: `/reviews`
- summary: `Create a new review`
- version: `v1`
- CORS: `Anything goes`
And the request handler as follows:
- URL Rewrite: `https://YOUR_SUPABASE_URL.supabase.co/rest/v1/ski-reviews`
- Forward Search: `unchecked`
Expand the policies section and add the same policies (note you can reuse policies by picking from the existing policies at the top of the library)
![existing policies](/docs/img/guides/integrations/zuplo/existing-policies.png)
- api-key-auth-inbound
- clear-headers-inbound
- set-headers-inbound
Now your **create** route is secured and will automatically set the right headers before calling Supabase. That was easy.
You can test this out by using the **API Test Console** to invoke your new endpoint. Go to the **API Test Console** and create a new test called `create-review.json`.
- Method: `POST`
- Path: `/v1/reviews`
- Headers:
- `content-type`: `application/json`
- `authorization`: `Bearer YOUR_ZUPLO_API_KEY`
- Body:
```json
{
"make": "Rossignol",
"model": "Soul HD7",
"rating": 5,
"year": 2019
}
```
![Test console](/docs/img/guides/integrations/zuplo/test-console.png)
If you invoke your API by clicking `Test` you should see that you get a **201 Created** - congratulations!
## Add validation to your post
To make your API more usable and more secure it is good practice to validate incoming requests. In this case we will add a JSON Schema document and use it to validate the incoming body to our POST.
Create a new schema document called `new-review.json`.
![new schema](/docs/img/guides/integrations/zuplo/new-schema.png)
This example fits the ski-reviews table we described above
```json
{
"$id": "http://example.com/example.json",
"type": "object",
"default": {},
"title": "Root Schema",
"required": ["make", "model", "rating", "year"],
"additionalProperties": false,
"properties": {
"make": {
"type": "string",
"default": "",
"title": "The make Schema",
"examples": ["DPS"]
},
"model": {
"type": "string",
"default": "",
"title": "The model Schema",
"examples": ["Pagoda"]
},
"rating": {
"type": "integer",
"default": 0,
"title": "The rating Schema",
"examples": [5]
},
"year": {
"type": "integer",
"default": 0,
"title": "The year Schema",
"examples": [2018]
}
},
"examples": [
{
"make": "DPS",
"model": "Pagoda",
"rating": 5,
"year": 2018,
"author": "Josh"
}
]
}
```
Now add a new policy to **request** pipeline for your `Create new review` route. Choose the **JSON Body Validation** policy and configure it to use your newly created JSON schema document:
```json
{
"export": "ValidateJsonSchemaInbound",
"module": "$import(@zuplo/runtime)",
"options": {
"validator": "$import(./schemas/new-review.json)"
}
}
```
This policy can be dragged to the first position in your pipeline.
Now to test this is working, go back to your API test console and change the body of your `create-review.json` test to be invalid (add a new property for example). You should find that you get a `400 Bad Request` response.
![400 Bad Request](/docs/img/guides/integrations/zuplo/400-bad.png)
Finally, lean back and marvel at your beautiful Developer Portal that took almost zero effort to get this far, wow! Hopefully you already found the link for this when adding API key support :)
![Developer Portal](/docs/img/guides/integrations/zuplo/dev-portal.png)
Zuplo can also be used to handle Supabase JWT tokens for any API, learn more at [API Authentication with Supabase JWT Tokens](https://zuplo.com/blog/2022/11/15/api-authentication-with-supabase-jwt)
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
+1 -1
View File
@@ -1,5 +1,5 @@
import Layout from '~/layouts/DefaultGuideLayout'
import Admonition from '~/components/Admonition'
import { Admonition } from 'ui'
export const meta = {
title: 'Database Backups',
@@ -71,8 +71,16 @@ The final activation step reconfigures your project to start serving traffic on
The auth service, in particular, will no longer work with the original URL (`foobarbaz.supabase.co`).
As such, it is recommended that you schedule a downtime window of 20-30 minutes, depending on the complexity of your project, to update all the services that need to know about your custom domain:
- any client code (e.g., frontends, mobile apps)
- any OAuth providers (e.g., google, github)
- Any client code (e.g., frontends, mobile apps). You can initialize a new Supabase client with your custom domain, `api.example.com`. For example:
```js
import { createClient } from '@supabase/supabase-js'
// Use a custom domain as the supabase URL
const supabase = createClient('https://api.example.com', 'public-anon-key')
```
- Any OAuth providers (e.g., google, github)
Additionally, update the DNS configuration for `api.example.com` to once more use a CNAME record that resolves to `foobarbaz.supabase.co`.
Finally, you can use the `activate` subcommand to reconfigure your project:
@@ -21,7 +21,7 @@ After developing your project and deciding it's Production Ready, you should run
- Go to the Authentication > Policies page in the Supabase Dashboard to enable RLS and create security policies.
- Go to the Database > Replication page in the Supabase Dashboard to manage replication tables.
- Enable 2FA on GitHub. Since your GitHub account gives you administrative rights to your Supabase project, you should protect it with a strong password and 2FA using a U2F key or a TOTP app.
- Ensure email confirmations are enabled in the `Auth > Settings` page.
- Ensure email confirmations are enabled in the `Settings > Auth` page.
- Use a custom SMTP server for auth emails so that your users can see that the mails are coming from a trusted domain (preferably the same domain that your app is hosted on). Grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
- Think hard about how _you_ would abuse your service as an attacker, and mitigate.
- Review these [common cybersecurity threats](https://auth0.com/docs/security/prevent-threats).
@@ -39,9 +39,9 @@ After developing your project and deciding it's Production Ready, you should run
## Availability
- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Auth > Settings)
- you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc.
- The default rate limit for auth emails provided by Supabase is 30 new users per hour, if doing a major public announcement you will likely require more than this.
- Use your own SMTP credentials so that you have full control over the deliverability of your transactional auth emails (see Settings > Auth)
- you can grab SMTP credentials from any major email provider such as SendGrid, AWS SES, etc. You can refer to our [SMTP guide](/docs/guides/auth/auth-smtp) for more details.
- The default rate limit for auth emails when using a custom SMTP provider is 30 new users per hour, if doing a major public announcement you will likely require more than this.
- If your application is on the free plan and is **not** expected to be queried at least once every 7 days, then it may be paused by Supabase to save on server resources.
- You can restore paused projects from the Supabase dashboard.
- Upgrade to Pro to guarantee that your project will not be paused for inactivity.
@@ -85,6 +85,8 @@ After developing your project and deciding it's Production Ready, you should run
- 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.
- When using a custom SMTP service, some services might have link tracking enabled which may overwrite or malform the email confirmation links sent by Supabase Auth. To prevent this from happening, we recommend that you disable link tracking when using a custom SMTP service.
## 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).
@@ -7,7 +7,7 @@ export const meta = {
<Admonition type="note">
Postgres SSL Enforcement is currently in beta and is slowly being made available to all projects. [Contact support](https://supabase.com/dashboard/support/new) if you'd like to request early access.
Postgres SSL Enforcement is currently in beta. Some projects need to [upgrade](/docs/guides/platform/migrating-and-upgrading-projects#upgrade-your-project) to the latest version to use this feature.
</Admonition>
@@ -15,13 +15,19 @@ Your Supabase project supports connecting to the Postgres DB without SSL enabled
SSL enforcement only applies to connections to both Postgres and PgBouncer ("Connection Pooler"); all HTTP APIs offered by Supabase (e.g., PostgREST, Storage, Auth) automatically enforce SSL on all incoming connections.
## Manage SSL enforcement via the Dashboard
SSL enforcement can be configured via the "Enforce SSL on incoming connections" setting under the SSL Configuration section in [Database Settings page](https://supabase.com/dashboard/project/_/settings/database) of the dashboard.
## Manage SSL enforcement via the CLI
To get started:
1. [Install](/docs/guides/cli) the Supabase CLI 1.37.0+.
1. [Log in](/docs/guides/getting-started/local-development#log-in-to-the-supabase-cli) to your Supabase account using the CLI.
1. Ensure that you have [Owner or Admin permissions](/docs/guides/platform/access-control#manage-team-members) for the project that you are enabling SSL enforcement.
## Check enforcement status
### Check enforcement status
You can use the `get` subcommand of the CLI to check whether SSL is currently being enforced:
@@ -37,7 +43,7 @@ Or similarly, if SSL is not being enforced, you will see:
SSL is *NOT* being enforced.
```
## Update enforcement
### Update enforcement
The `update` subcommand is used to change the SSL enforcement status for your project:
+8 -24
View File
@@ -11,12 +11,7 @@ Looking for docs on how to add Single Sign-On support in your Supabase project?
</Admonition>
Supabase offers single sign-on (SSO) as a login option to provide additional
account security for your team. This allows company administrators to enforce
the use of an identity provider when logging into Supabase. SSO
improves the onboarding and offboarding experience of the company as the
employee only needs a single set of credentials to access third-party
applications or tools—which can also be revoked easily by an administrator.
Supabase offers single sign-on (SSO) as a login option to provide additional account security for your team. This allows company administrators to enforce the use of an identity provider when logging into Supabase. SSO improves the onboarding and offboarding experience of the company as the employee only needs a single set of credentials to access third-party applications or tools which can also be revoked easily by an administrator.
<Admonition type="note">
@@ -37,27 +32,16 @@ The following sections outline the limitations when SSO is enabled or disabled f
### Enable SSO for your team [#enable-sso]
- Organization invites are restricted to members of the company that belong to
the same identity provider.
- Every user has an organization created by default. They can create as many
projects as they want.
- An SSO user will not be able to update their password or reset their
password since their access is managed by the company administrator via the
identity provider.
- If an SSO user with the following email of `alice@foocorp.com` attempts to
sign-in with a GitHub account that uses the same email, a separate Supabase
account is created and will not be linked to the SSO user's account.
- An SSO user will not be able to see all organizations / projects created
under the same identity provider. They will need to be invited to the
Supabase organization first. Refer to [access control](/docs/guides/platform/access-control)
for more information.
- Organization invites are restricted to company members belonging to the same identity provider.
- Every user has an organization created by default. They can create as many projects as they want.
- An SSO user will not be able to update or reset their password since the company administrator manages their access via the identity provider.
- If an SSO user with the following email of `alice@foocorp.com` attempts to sign in with a GitHub account that uses the same email, a separate Supabase account is created and will not be linked to the SSO user's account.
- An SSO user will not be able to see all organizations/projects created under the same identity provider. They will need to be invited to the Supabase organization first. Refer to [access control](/docs/guides/platform/access-control) for more information.
### Disable SSO for your team [#disable-sso]
- You can prevent a user's account from further access to Supabase by removing
or disabling their account in your identity provider.
- You should also remove or downgrade their permissions from any organizations
inside Supabase.
- You can prevent a user's account from further access to Supabase by removing or disabling their account in your identity provider.
- You should also remove or downgrade their permissions from any organizations inside Supabase.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
+13 -5
View File
@@ -48,14 +48,10 @@ We can use your JWT Secret to generate new `anon` and `service` API keys using t
### Update API Keys
Replace the values in these files:
Replace the values the `.env` file:
- `.env`:
- `ANON_KEY` - replace with an `anon` key
- `SERVICE_ROLE_KEY` - replace with a `service` key
- `volumes/api/kong.yml`
- `anon` - replace with an `anon` key
- `service_role` - replace with a `service` key
### Update Secrets
@@ -66,6 +62,18 @@ Update the `.env` file with your own secrets. In particular, these are required:
- `SITE_URL`: the base URL of your site.
- `SMTP_*`: mail server credentials. You can use any SMTP server.
### Adding a firewall
We recommend using a firewall to restrict access to your Supabase setup (something like `ufw`). One thing to highlight is that Docker usually [makes changes to your iptables](https://askubuntu.com/questions/652556/uncomplicated-firewall-ufw-is-not-blocking-anything-when-using-docker).
To disable this behavior, you can add the following to your `/etc/docker/daemon.json` file and then run `sudo service docker restart`:
```json
{
"iptables": false
}
```
## Exposing services
The services running on the machine are not exposed to the internet by default. We recommend using a reverse proxy such as [NGINX](https://www.nginx.com/) or [Caddy](https://caddyserver.com/) if you want to expose the services publicly:
@@ -40,7 +40,7 @@ curl -X POST 'https://<project-ref>.supabase.co/auth/v1/magiclink' \
}'
```
Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Auth > Settings) and also transacting with third party OAuth providers to get basic user data.
Gotrue is responsible for issuing access tokens for your users, sends confirmation, magic-link, and password recovery emails (by default we send these from a Supabase SMTP server, but you can easily plug in your own inside the dashboard at Project Settings > Auth) and also transacting with third party OAuth providers to get basic user data.
The community even recently built in the functionality to request custom OAuth scopes, if your users need to interact more closely with the provider. See the scopes parameter here: [https://github.com/supabase/gotrue#get-authorize](https://github.com/supabase/gotrue#get-authorize).
Binary file not shown.

After

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 54 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 97 KiB

After

Width:  |  Height:  |  Size: 83 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 183 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 332 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 218 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 273 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 662 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.5 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 44 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 4.9 KiB

Loaded 100 of 553 files, more files were not shown because too many files have changed in this diff. Show more