Expand Serverless APIs to REST + GraphQL (#17654)

* graphql section

* fix(federation): markdown image links

* feat(federation): support mkdocs admonition title

* feat(federation): remark pymdown tab support

* fix codehike path

* graphql icon renders

* replace serverless-apis with REST

* run prettier

* update serverless apis ref

* remove GraphQL and Realtime references from REST docs

* move realtime example to realtime overview section

* new section for apis

* prettier

* product label

* move realtime back to products

* feat: graphql nav menu + simplified path

* chore: remove console log

---------

Co-authored-by: Greg Richardson <greg.nmr@gmail.com>
This commit is contained in:
Oliver RiceandGreg Richardson authored and GitHub committed 2023-09-25 15:17:31 -06:00
1 parent 21d7d6a21f
commit 3fe7613b42
19 files changed
+1367 -1016

No files matched your search

@@ -5,6 +5,7 @@ import {
IconMenuCli,
IconMenuCsharp,
IconMenuDatabase,
IconMenuGraphQL,
IconMenuEdgeFunctions,
IconMenuFlutter,
IconMenuGettingStarted,
@@ -16,7 +17,7 @@ import {
IconMenuRealtime,
IconMenuResources,
IconMenuSelfHosting,
IconMenuServerlessApis,
IconMenuRestApis,
IconMenuStorage,
IconMenuSwift,
IconMenuStatus,
@@ -32,8 +33,10 @@ function getMenuIcon(menuKey: string, width: number = 16, height: number = 16) {
return <IconMenuGettingStarted width={width} height={height} />
case 'database':
return <IconMenuDatabase width={width} height={height} />
case 'serverless-apis':
return <IconMenuServerlessApis width={width} height={height} />
case 'rest':
return <IconMenuRestApis width={width} height={height} />
case 'graphql':
return <IconMenuGraphQL width={width} height={height} />
case 'auth':
return <IconMenuAuth width={width} height={height} />
case 'edge-functions':
@@ -23,6 +23,25 @@ export function IconMenuHome({ width = 16, height = 16 }: HomeMenuIcon) {
)
}
export function IconMenuGraphQL({ width = 16, height = 16 }: HomeMenuIcon) {
return (
<svg
width={width}
height={height}
viewBox="0 0 24 24"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M12,5.37L11.56,5.31L6,14.9C6.24,15.11 6.4,15.38 6.47,15.68H17.53C17.6,15.38 17.76,15.11 18,14.9L12.44,5.31L12,5.37M6.6,16.53L10.88,19.06C11.17,18.79 11.57,18.63 12,18.63C12.43,18.63 12.83,18.79 13.12,19.06L17.4,16.53H6.6M12,22A1.68,1.68 0 0,1 10.32,20.32L10.41,19.76L6.11,17.21C5.8,17.57 5.35,17.79 4.84,17.79A1.68,1.68 0 0,1 3.16,16.11C3.16,15.32 3.69,14.66 4.42,14.47V9.36C3.59,9.25 2.95,8.54 2.95,7.68A1.68,1.68 0 0,1 4.63,6C5.18,6 5.66,6.26 5.97,6.66L10.38,4.13L10.32,3.68C10.32,2.75 11.07,2 12,2C12.93,2 13.68,2.75 13.68,3.68L13.62,4.13L18.03,6.66C18.34,6.26 18.82,6 19.37,6A1.68,1.68 0 0,1 21.05,7.68C21.05,8.54 20.41,9.25 19.58,9.36V14.47C20.31,14.66 20.84,15.32 20.84,16.11A1.68,1.68 0 0,1 19.16,17.79C18.65,17.79 18.2,17.57 17.89,17.21L13.59,19.76L13.68,20.32A1.68,1.68 0 0,1 12,22M10.8,4.86L6.3,7.44L6.32,7.68C6.32,8.39 5.88,9 5.26,9.25L5.29,14.5L10.8,4.86M13.2,4.86L18.71,14.5L18.74,9.25C18.12,9 17.68,8.39 17.68,7.68L17.7,7.44L13.2,4.86Z"
stroke="currentColor"
strokeMiterlimit="10"
strokeLinejoin="bevel"
/>
</svg>
)
}
export function IconMenuApi({ width = 16, height = 16 }: HomeMenuIcon) {
return (
<svg
@@ -118,7 +137,7 @@ export function IconMenuDatabase({ width = 16, height = 16 }: HomeMenuIcon) {
)
}
export function IconMenuServerlessApis({ width = 16, height = 16 }: HomeMenuIcon) {
export function IconMenuRestApis({ width = 16, height = 16 }: HomeMenuIcon) {
return (
<svg
viewBox="0 0 16 16"
@@ -16,24 +16,27 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
},
],
[
{
label: 'Product',
},
{
label: 'Database',
icon: 'database',
href: '/guides/database',
level: 'database',
},
{
label: 'Serverless APIs',
icon: 'serverless-apis',
href: '/guides/api',
level: 'api',
},
{
label: 'Auth',
icon: 'auth',
href: '/guides/auth',
level: 'auth',
},
{
label: 'Storage',
icon: 'storage',
href: '/guides/storage',
level: 'storage',
},
{
label: 'Edge Functions',
icon: 'edge-functions',
@@ -46,12 +49,6 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
href: '/guides/realtime',
level: 'realtime',
},
{
label: 'Storage',
icon: 'storage',
href: '/guides/storage',
level: 'storage',
},
{
label: 'AI & Vectors',
icon: 'ai',
@@ -60,6 +57,26 @@ export const HOMEPAGE_MENU_ITEMS: HomepageMenuItems = [
},
],
[
{
label: 'API',
},
{
label: 'REST',
icon: 'rest',
href: '/guides/api',
level: 'api',
},
{
label: 'GraphQL',
icon: 'graphql',
href: '/guides/graphql',
level: 'graphql',
},
],
[
{
label: 'Development Cycle',
},
{
label: 'Local Dev / CLI',
icon: 'reference-cli',
@@ -741,12 +758,15 @@ export const database: NavMenuConstant = {
}
export const api: NavMenuConstant = {
icon: 'serverless-apis',
title: 'Serverless APIs',
icon: 'rest',
title: 'REST API',
url: '/guides/api',
items: [
{ name: 'Overview', url: '/guides/api', items: [] },
{ name: 'Quickstart', url: '/guides/api/quickstart', items: [] },
{ name: 'Client Libraries', url: '/guides/api/rest/client-libs', items: [] },
{ name: 'Auto-generated Docs', url: '/guides/api/rest/auto-generated-docs', items: [] },
{ name: 'Generating Types', url: '/guides/api/rest/generating-types', items: [] },
{
name: 'Guides',
url: '/guides/api',
@@ -762,20 +782,16 @@ export const api: NavMenuConstant = {
{ name: 'Using custom schemas', url: '/guides/api/using-custom-schemas', items: [] },
],
},
{
name: 'REST & REALTIME',
url: undefined,
items: [
{ name: 'Auto-generated Docs', url: '/guides/api/rest/auto-generated-docs', items: [] },
{ name: 'Client Libraries', url: '/guides/api/rest/client-libs', items: [] },
{ name: 'Generating Types', url: '/guides/api/rest/generating-types', items: [] },
],
},
{
name: 'GRAPHQL',
url: undefined,
items: [{ name: 'GraphiQL Documentation', url: '/guides/api/graphql/graphiql', items: [] }],
},
],
}
export const graphql: NavMenuConstant = {
icon: 'graphql',
title: 'GraphQL',
url: '/guides/graphql',
items: [
{ name: 'Overview', url: '/guides/graphql', items: [] },
{ name: 'API', url: '/guides/graphql/api', items: [] },
],
}
@@ -48,6 +48,11 @@ const menus: Menu[] = [
path: '/guides/api',
type: 'guide',
},
{
id: 'graphql',
path: '/guides/graphql',
type: 'guide',
},
{
id: 'auth',
path: '/guides/auth',
@@ -19,7 +19,7 @@ const HeaderLink = React.memo(function HeaderLink(props: {
className={[
' ',
!props.title && 'capitalize',
props.url === router.pathname ? 'text-brand' : 'hover:text-brand text-scale-1200',
props.url === router.asPath ? 'text-brand' : 'hover:text-brand text-scale-1200',
].join(' ')}
>
{props.title ?? props.id}
@@ -30,7 +30,7 @@ const HeaderLink = React.memo(function HeaderLink(props: {
const ContentAccordionLink = React.memo(function ContentAccordionLink(props: any) {
const router = useRouter()
const { isDarkMode } = useTheme()
const activeItem = props.subItem.url === router.pathname
const activeItem = props.subItem.url === router.asPath
const activeItemRef = useRef(null)
const LinkContainer = (props) => {
@@ -96,7 +96,7 @@ const ContentAccordionLink = React.memo(function ContentAccordionLink(props: any
<a
className={[
'cursor-pointer transition text-sm',
subSubItem.url === router.pathname
subSubItem.url === router.asPath
? 'text-brand'
: 'hover:text-brand text-scale-1000',
].join(' ')}
@@ -123,7 +123,7 @@ const ContentLink = React.memo(function ContentLink(props: any) {
<a
className={[
'cursor-pointer transition text-sm',
props.url === router.pathname
props.url === router.asPath
? 'text-brand'
: 'hover:text-scale-1200 dark:hover:text-scale-1100 text-scale-1000',
].join(' ')}
@@ -12,7 +12,7 @@ const RefSwitcher = () => {
useEffect(() => {
setOpen(false)
}, [router.pathname])
}, [router.asPath])
return (
<div className="px-10 flex items-center -space-x-px">
+2 -2
View File
@@ -37,7 +37,7 @@ import {
IconMenuHome,
IconMenuGettingStarted,
IconMenuDatabase,
IconMenuServerlessApis,
IconMenuRestApis,
IconMenuAuth,
IconMenuEdgeFunctions,
IconMenuRealtime,
@@ -104,7 +104,7 @@ const components = {
IconMenuHome,
IconMenuGettingStarted,
IconMenuDatabase,
IconMenuServerlessApis,
IconMenuRestApis,
IconMenuAuth,
IconMenuEdgeFunctions,
IconMenuRealtime,
+6 -2
View File
@@ -23,8 +23,12 @@ const levelsData = {
name: 'Database',
},
api: {
icon: '/docs/img/icons/menu/database',
name: 'Serverless APIs',
icon: '/docs/img/icons/menu/rest',
name: 'REST API',
},
graphql: {
icon: '/docs/img/icons/menu/graphql',
name: 'GraphQL',
},
auth: {
icon: '/docs/img/icons/menu/auth',
+14 -3
View File
@@ -15,14 +15,17 @@ const remarkMkDocsAdmonition = function () {
const [firstChild] = paragraph.children
if (firstChild?.type === 'text') {
const match = firstChild.value.match(/^!!! ?(.*?)\n(.*)/s)
// Look for 3 '!', followed by an admonition type, followed by
// an optionally quoted title, followed by optional newlines of text
const match = firstChild.value.match(/^!!! ?("?)(.+)\1 ?\n?((?:.|\n)*)/)
if (!match) {
return
}
// Extract the admonition type along with the remaining text
const [, type, value] = match
// Extract the admonition type, title, and remaining text
const [, , typeTitle, value] = match
const [, type, title] = typeTitle.match(/^(.+?) ?(?:"(.*)")?$/)
// Rewrite the node's value to remove the admonition syntax
firstChild.value = value
@@ -46,6 +49,14 @@ const remarkMkDocsAdmonition = function () {
children,
}
if (title) {
admonitionElement.attributes.push({
type: 'mdxJsxAttribute',
name: 'label',
value: title,
})
}
// Overwrite original node with new element
parent.children.splice(index, 1, admonitionElement)
}
+1
View File
@@ -52,6 +52,7 @@ const nextConfig = {
domains: [
'avatars.githubusercontent.com',
'github.com',
'supabase.github.io',
'user-images.githubusercontent.com',
'raw.githubusercontent.com',
'weweb-changelog.ghost.io',
+12 -43
View File
@@ -2,23 +2,24 @@ import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'api',
title: 'Serverless APIs',
description: 'Auto-generating and Realtime APIs.',
title: 'REST API',
description: 'Auto-generating REST API.',
sidebar_label: 'Overview',
video: 'https://www.youtube.com/v/rPAJJFdtPw0',
}
Supabase auto-generates three types of API directly from your database schema.
Supabase auto-generates an API directly from your database schema allowing you to connect to your database through a restful interface, directly from the browser.
- REST - connect to your database through a restful interface, directly from the browser.
- GraphQL - manipulate your database using a graph-like query language.
- Realtime - listen to database changes.
All the APIs are auto-generated from your database and are designed to get you building as fast as possible, without writing a single line of code.
The API is auto-generated from your database and is designed to get you building as fast as possible, without writing a single line of code.
You can use them directly from the browser (two-tier architecture), or as a complement to your own API server (three-tier architecture).
## Features
## Features [#rest-api-overview]
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
It exposes everything you need from a CRUD API at the URL `https://<project_ref>.supabase.co/rest/v1/`.
The REST interface is automatically reflected from your database's schema and is:
- **Instant and auto-generated.** <br />As you update your database the changes are immediately accessible through your API.
- **Self documenting.** <br />Supabase generates documentation in the Dashboard which updates as you make database changes.
@@ -26,19 +27,14 @@ You can use them directly from the browser (two-tier architecture), or as a comp
- **Fast.** <br />Our benchmarks for basic reads are more than 300% faster than Firebase. The API is a very thin layer on top of Postgres, which does most of the heavy lifting.
- **Scalable.** <br />The API can serve thousands of simultaneous requests, and works well for Serverless workloads.
## REST API [#rest-api-overview]
Supabase provides a RESTful API using [PostgREST](https://postgrest.org/). This is a very thin API layer on top of Postgres.
It provides everything you need from a CRUD API at the URL `https://<project_ref>.supabase.co/rest/v1/`.
The REST interface is automatically reflected from your database's schema and supports:
The reflected API is designed to retain as much of Postgres' capability as possible including:
- Basic CRUD operations (Create/Read/Update/Delete)
- Arbitrarily deep relationships among tables/views, functions that return table types can also nest related tables/views.
- Works with Postgres Views, Materialized Views and Foreign Tables
- Works with Postgres Functions
- User defined computed columns and computed relationships
- Works with the Postgres security model - including Row Level Security, Roles, and Grants.
- The Postgres security model - including Row Level Security, Roles, and Grants.
The REST API resolves all requests to a single SQL statement leading to fast response times and high throughput.
@@ -47,33 +43,6 @@ Reference:
- [Docs](https://postgrest.org/)
- [Source Code](https://github.com/PostgREST/postgrest)
## GraphQL API [#graphql-api-overview]
Supabase uses [pg_graphql](https://supabase.github.io/pg_graphql/) to expose a GraphQL API endpoint at `https://<project_ref>.supabase.co/graphql/v1/`.
You can introspect and query the GraphQL API of an existing Supabase project within Studio [here](https://supabase.com/dashboard/project/_/api/graphiql),
or navigate there manually at `API Docs > GraphQL > GraphiQL`.
The GraphQL interface is automatically reflected from your database's schema and supports:
- Basic CRUD operations (Create/Read/Update/Delete)
- Support for Tables, Views, Materialized Views, and Foreign Tables
- Arbitrarily deep relationships among tables/views
- User defined computed fields
- The Postgres security model - including Row Level Security, Roles, and Grants.
The GraphQL API resolves all requests in a single round-trip leading to fast response times and high throughput.
Reference:
- [Docs](https://supabase.github.io/pg_graphql/)
- [Source Code](https://github.com/supabase/pg_graphql)
## Realtime API [#realtime-api-overview]
Supabase provides a Realtime API using [Realtime](https://github.com/supabase/realtime). You can use this to listen to database changes over websockets.
Realtime leverages PostgreSQL's built-in logical replication. You can manage your Realtime API simply by managing Postgres publications.
Go to your project's [Replication section](https://supabase.com/dashboard/project/_/database/replication) to get started.
## API URL and Keys
You can find the API URL and Keys in the [Dashboard](https://supabase.com/dashboard/project/_/settings/api).
+1 -131
View File
@@ -66,17 +66,12 @@ Every Supabase project has a unique API URL. Your API is secured behind an API g
/>
</video>
The REST API and the GraphQL API are both accessible through this URL:
- REST: `https://<project_ref>.supabase.co/rest/v1`
- GraphQL: `https://<project_ref>.supabase.co/graphql/v1`
The REST API is accessible through the URL `https://<project_ref>.supabase.co/rest/v1`
Both of these routes require the `anon` key to be passed through an `apikey` header.
## Using the API
### REST API
You can interact with your API directly via HTTP requests, or you can use the client libraries which we provide.
Let's see how to make a request to the `todos` table which we created in the first step,
@@ -120,131 +115,6 @@ JS Reference: [select()](/docs/reference/javascript/select),
[delete()](/docs/reference/javascript/delete),
[rpc()](/docs/reference/javascript/rpc) (call Postgres functions).
### GraphQL API
You can use any GraphQL client with the Supabase GraphQL API. For our GraphQL example we will use [urql](https://formidable.com/open-source/urql/docs/).
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="javascript"
queryGroup="language"
>
<TabPanel id="javascript" label="Javascript">
```javascript
import { createClient, useQuery } from 'urql'
// Prepare API key and Authorization header
const headers = {
apikey: <SUPABASE_ANON_KEY>,
authorization: `Bearer ${<SUPABASE_ANON_KEY>}`,
}
// Create GraphQL client
// See: https://formidable.com/open-source/urql/docs/basics/react-preact/#setting-up-the-client
const client = createClient({
url: '<SUPABASE_URL>/graphql/v1',
fetchOptions: function createFetchOptions() {
return { headers }
},
})
// Prepare our GraphQL query
const TodosQuery = `
query {
todosCollection {
edges {
node {
id
title
}
}
}
}
`
// Query for the data (React)
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
})
// Read the result
const { data, fetching, error } = result
```
</TabPanel>
<TabPanel id="curl" label="cURL">
```bash
# Append /graphql/v1/ to your URL, and then use the table name as the route
curl --request POST '<SUPABASE_URL>/graphql/v1' \
-H 'apikey: <SUPABASE_ANON_KEY>' \
-H 'Authorization: Bearer <SUPABASE_ANON_KEY>' \
-H 'Content-Type: application/json' \
-d '{ "query":"{ todos(first: 3) { edges { node { id } } } }" }'
```
</TabPanel>
</Tabs>
### Realtime API
By default Realtime is disabled on your database. Let's turn on Realtime for the `todos` table.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
queryGroup="database-method"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Replication** in the sidebar.
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
<video width="99%" muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter
publication supabase_realtime add table todos;
```
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Create a function to handle inserts
const handleInserts = (payload) => {
console.log('Change received!', payload)
}
// Listen to inserts
const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe()
```
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
@@ -1,22 +0,0 @@
import Layout from '~/layouts/DefaultGuideLayout'
export const meta = {
id: 'graphiql',
title: 'GraphiQL documentation',
description: 'Some docs on graphiql.',
video: 'https://www.youtube.com/v/7CqlTU9aOR4',
}
Every Supabase project has a GraphQL Endpoint: `https://<project_ref>.supabase.co/graphql/v1`.
This endpoint is compatible with any GraphiQL implementation that can pass an `apikey` header.
Some suggested applications:
- [paw.cloud](https://paw.cloud)
- [insomnia.rest](https://insomnia.rest)
- [postman.com/graphql](https://www.postman.com/graphql/)
- Self-hosted GraphiQL: GraphiQL can be served through a simple HTML file. See [this discussion](https://github.com/supabase/supabase/discussions/6144) for more details.
export const Page = ({ children }) => <Layout meta={meta} children={children} />
export default Page
-58
View File
@@ -174,64 +174,6 @@ response = supabase.table('todos').select("*").execute()
</TabPanel>
</Tabs>
### GraphQL
Every table can be accessed through the GraphQL API by switching `/rest/v1` with `/graphql/v1`.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="urql"
queryGroup="library"
>
<TabPanel id="urql" label="urql">
```javascript
import { createClient, useQuery } from 'urql'
const URL = '<SUPABASE_URL>/graphql/v1'
const ANON_KEY = '<SUPABASE_ANON_KEY>'
// Prepare API key and Authorization header
const headers = {
apikey: `${ANON_KEY}`,
authorization: `Bearer ${ANON_KEY}`,
}
const client = createClient({
url: URL,
fetchOptions: function createFetchOptions() {
return { headers }
},
})
// Prepare our GraphQL query
const TodosQuery = `
query {
todosCollection {
edges {
node {
id
task
}
}
}
}
`
// Query for the data (React)
const [result, reexecuteQuery] = useQuery({
query: TodosQuery,
})
// Read the result
const { data, fetching, error } = result
```
</TabPanel>
</Tabs>
export const Page = ({ children }) => <Layout meta={meta} children={children} hideToc={true} />
export default Page
@@ -0,0 +1,152 @@
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 { isAbsolute, 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'
import remarkPyMdownTabs from '~/lib/mdx/plugins/remarkTabs'
// We fetch these docs at build time from an external repo
const org = 'supabase'
const repo = 'pg_graphql'
const branch = 'master'
const docsDir = 'docs'
const externalSite = 'https://supabase.github.io/pg_graphql'
// Each external docs page is mapped to a local page
const pageMap = [
{
meta: {
id: 'graphql-overview',
title: 'GraphQL',
},
remoteFile: 'supabase.md',
},
{
slug: 'api',
meta: {
id: 'graphql-api',
title: 'GraphQL API',
},
remoteFile: 'api.md',
},
]
interface PGGraphQLDocsProps {
source: MDXRemoteSerializeResult
meta: {
title: string
description?: string
}
}
export default function PGGraphQLDocs({ source, meta }: PGGraphQLDocsProps) {
return (
<Layout meta={meta}>
<MDXRemote {...source} components={components} />
</Layout>
)
}
/**
* Fetch markdown from external repo and transform links
*/
export const getStaticProps: GetStaticProps<PGGraphQLDocsProps> = async ({ params }) => {
const [slug] = params.slug ?? []
const page = pageMap.find((page) => page.slug === slug)
if (!page) {
throw new Error(`No page mapping found for slug '${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 getRelativePath = () => {
if (pathname.endsWith('.md')) {
return pathname.replace(/\.md$/, '')
}
if (isAbsolute(url)) {
return relative(externalSiteUrl.pathname, pathname)
}
return pathname
}
const relativePath = getRelativePath().replace(/^\//, '')
const page = pageMap.find(({ remoteFile }) => `${relativePath}.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}/${relativePath}${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,
remarkPyMdownTabs,
[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: slug ? [slug] : [],
},
})),
fallback: false,
}
}
+57
View File
@@ -14,6 +14,63 @@ Supabase provides a globally distributed cluster of [Realtime](https://github.co
- [Presence](/docs/guides/realtime/presence): Track and synchronize shared state between clients.
- [Postgres Changes](/docs/guides/realtime/postgres-changes): Listen to Postgres database changes and send them to authorized clients.
### Realtime API
By default Realtime is disabled on your database. Let's turn on Realtime for a `todos` table.
<Tabs
scrollable
size="small"
type="underlined"
defaultActiveId="dashboard"
>
<TabPanel id="dashboard" label="Dashboard">
1. Go to the [Database](https://supabase.com/dashboard/project/_/database/tables) page in the Dashboard.
2. Click on **Replication** in the sidebar.
3. Control which database events are sent by toggling **Insert**, **Update**, and **Delete**.
4. Control which tables broadcast changes by selecting **Source** and toggling each table.
{' '}
<video width="99%" muted playsInline controls={true}>
<source
src="https://xguihxuzqibwxjnimxev.supabase.co/storage/v1/object/public/videos/docs/api/api-realtime.mp4"
type="video/mp4"
/>
</video>
</TabPanel>
<TabPanel id="sql" label="SQL">
```sql
alter
publication supabase_realtime add table todos;
```
</TabPanel>
</Tabs>
From the client, we can listen to any new data that is inserted into the `todos` table:
```javascript
// Initialize the JS client
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)
// Create a function to handle inserts
const handleInserts = (payload) => {
console.log('Change received!', payload)
}
// Listen to inserts
const { data: todos, error } = await supabase.from('todos').on('INSERT', handleInserts).subscribe()
```
Use [subscribe()](/docs/reference/javascript/subscribe) to listen to database changes.
The Realtime API works through PostgreSQL's replication functionality. Postgres sends database changes to a [publication](/docs/guides/database/replication#publications)
called `supabase_realtime`, and by managing this publication you can control which data is broadcast.
## Examples
<div className="grid md:grid-cols-12 gap-4 not-prose">
@@ -0,0 +1,5 @@
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M12,5.37L11.56,5.31L6,14.9C6.24,15.11 6.4,15.38 6.47,15.68H17.53C17.6,15.38 17.76,15.11 18,14.9L12.44,5.31L12,5.37M6.6,16.53L10.88,19.06C11.17,18.79 11.57,18.63 12,18.63C12.43,18.63 12.83,18.79 13.12,19.06L17.4,16.53H6.6M12,22A1.68,1.68 0 0,1 10.32,20.32L10.41,19.76L6.11,17.21C5.8,17.57 5.35,17.79 4.84,17.79A1.68,1.68 0 0,1 3.16,16.11C3.16,15.32 3.69,14.66 4.42,14.47V9.36C3.59,9.25 2.95,8.54 2.95,7.68A1.68,1.68 0 0,1 4.63,6C5.18,6 5.66,6.26 5.97,6.66L10.38,4.13L10.32,3.68C10.32,2.75 11.07,2 12,2C12.93,2 13.68,2.75 13.68,3.68L13.62,4.13L18.03,6.66C18.34,6.26 18.82,6 19.37,6A1.68,1.68 0 0,1 21.05,7.68C21.05,8.54 20.41,9.25 19.58,9.36V14.47C20.31,14.66 20.84,15.32 20.84,16.11A1.68,1.68 0 0,1 19.16,17.79C18.65,17.79 18.2,17.57 17.89,17.21L13.59,19.76L13.68,20.32A1.68,1.68 0 0,1 12,22M10.8,4.86L6.3,7.44L6.32,7.68C6.32,8.39 5.88,9 5.26,9.25L5.29,14.5L10.8,4.86M13.2,4.86L18.71,14.5L18.74,9.25C18.12,9 17.68,8.39 17.68,7.68L17.7,7.44L13.2,4.86Z"
stroke="#4CC38A" strokeMiterlimit="10" strokeLinejoin="bevel" />
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

+5
View File
@@ -0,0 +1,5 @@
<svg viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M1.923 9.927A5.833 5.833 0 0 1 1.14 7m2.877-5.046A5.833 5.833 0 0 1 7 1.14m5.13 3.025c.465.84.73 1.807.73 2.835m-2.883 5.049A5.832 5.832 0 0 1 7 12.859m6.172-10.027a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM4.81 11.148a2 2 0 1 1-4 0 2 2 0 0 1 4 0Zm8.362 0a2 2 0 1 1-4 0 2 2 0 0 1 4 0ZM4.81 2.832a2 2 0 1 1-4 0 2 2 0 0 1 4 0Z"
stroke="#4CC38A" strokeMiterlimit="10" strokeLinejoin="bevel" />
</svg>

After

Width:  |  Height:  |  Size: 476 B

+1032 -718
View File
File diff suppressed because it is too large. Load diff