mirror of
https://github.com/supabase/supabase.git
synced 2026-10-05 09:25:06 +03:00
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:
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">
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -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',
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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
|
||||
@@ -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,
|
||||
}
|
||||
}
|
||||
@@ -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 |
@@ -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
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user