Merge pull request #9925 from supabase/thor/caching-examples

Thor/caching examples
This commit is contained in:
Thor 雷神 Schaeff authored and GitHub committed 2022-10-29 08:15:07 +08:00
commit 419a3865e0
24 files changed
+8606

No files matched your search

@@ -0,0 +1,171 @@
# Logs
logs
_.log
npm-debug.log_
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
.pnpm-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]_.[0-9]_.[0-9]_.[0-9]_.json
# Runtime data
pids
_.pid
_.seed
\*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
\*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# Snowpack dependency directory (https://snowpack.dev/)
web_modules/
# TypeScript cache
\*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional stylelint cache
.stylelintcache
# Microbundle cache
.rpt2_cache/
.rts2_cache_cjs/
.rts2_cache_es/
.rts2_cache_umd/
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
\*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# parcel-bundler cache (https://parceljs.org/)
.cache
.parcel-cache
# Next.js build output
.next
out
# Nuxt.js build / generate output
.nuxt
dist
# Gatsby files
.cache/
# Comment in the public line in if your project uses Gatsby and not Next.js
# https://nextjs.org/blog/next-9-1#public-directory-support
# public
# vuepress build output
.vuepress/dist
# vuepress v2.x temp and cache directory
.temp
.cache
# Docusaurus cache and generated files
.docusaurus
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
# TernJS port file
.tern-port
# Stores VSCode versions used for testing VSCode extensions
.vscode-test
# yarn v2
.yarn/cache
.yarn/unplugged
.yarn/build-state.yml
.yarn/install-state.gz
.pnp.\*
# wrangler project
.dev.vars
@@ -0,0 +1,62 @@
# Use waitUntil to perform work after Cloudflare Worker returns response
**[📹 Video](https://egghead.io/lessons/supabase-use-waituntil-to-perform-work-after-cloudflare-worker-returns-response?af=9qsk0a)**
The `waitUntil` function allows us to continue performing work in our Cloudflare Worker, after a response has been sent back to the client.
In this lesson, we modify the `revalidate` route to send a response immediately, and then continue on to fetch new data and refresh our KV store.
Finally, we use the Thunder Client extension to simulate a POST request to the `revalidate` route, and confirm that this receives a response before the cache is updated.
## Code Snippets
**Update Revalidate route to respond immediately**
```javascript
router.post(
"/revalidate",
withContent,
async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }, context) => {
const updateCache = async () => {
const { type, record, old_record } = request.content;
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
if (type === "INSERT" || type === "UPDATE") {
await writeTo(ARTICLES, `/articles/${record.id}`, record);
}
if (type === "DELETE") {
await ARTICLES.delete(`/articles/${old_record.id}`);
}
const { data: articles } = await supabase.from("articles").select("*");
await writeTo(ARTICLES, "/articles", articles);
console.log("updated cache");
};
context.waitUntil(updateCache());
console.log("sending response");
return json({ received: true });
}
);
```
**Run wrangler development server**
```bash
npx wrangler dev
```
## Resources
- [Cloudflare waitUntil docs](https://developers.cloudflare.com/workers/runtime-apis/scheduled-event/)
- [Supabase.js docs](https://github.com/supabase/supabase-js)
- [Wrangler CLI docs](https://developers.cloudflare.com/workers/wrangler/commands/)
- [KV Storage docs](https://developers.cloudflare.com/workers/runtime-apis/kv/)
- [Thunder Client VS Code extension](https://marketplace.visualstudio.com/items?itemName=rangav.vscode-thunder-client)
---
Enjoying the course? Follow Jon Meyers on [Twitter](https://twitter.com/jonmeyers_io) and subscribe to the [YouTube channel](https://www.youtube.com/c/jonmeyers).
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,17 @@
{
"name": "supabase-at-the-edge",
"version": "0.0.0",
"devDependencies": {
"wrangler": "2.0.16"
},
"private": true,
"scripts": {
"start": "wrangler dev",
"deploy": "wrangler publish"
},
"dependencies": {
"@supabase/supabase-js": "^1.35.4",
"itty-router": "^2.6.1",
"itty-router-extras": "^0.4.2"
}
}
@@ -0,0 +1,100 @@
import { createClient } from "@supabase/supabase-js";
import { Router } from "itty-router";
import { json, status, withContent } from "itty-router-extras";
import { readFrom, writeTo } from "./utils/cache";
const router = new Router();
router.get("/read-kv", async (request, { ARTICLES }) => {
const articles = await readFrom(ARTICLES, "/articles");
return json(articles);
});
router.get("/write-kv", async (request, { ARTICLES }) => {
const articles = [{ title: "test3" }, { title: "test4" }];
await writeTo(ARTICLES, "/articles", articles);
return json(articles);
});
router.get(
"/articles",
async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }) => {
const cachedArticles = await readFrom(ARTICLES, "/articles");
if (cachedArticles) {
console.log("sending the cache");
return json(cachedArticles);
}
console.log("fetching fresh articles");
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { data } = await supabase.from("articles").select("*");
await writeTo(ARTICLES, "/articles", data);
return json(data);
}
);
router.get(
"/articles/:id",
async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }) => {
const { id } = request.params;
const cachedArticle = await readFrom(ARTICLES, `/articles/${id}`);
if (cachedArticle) {
console.log("sending the cache");
return json(cachedArticle);
}
console.log("fetching fresh article");
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
const { data } = await supabase
.from("articles")
.select("*")
.match({ id })
.single();
await writeTo(ARTICLES, `/articles/${id}`, data);
return json(data);
}
);
router.post(
"/revalidate",
withContent,
async (request, { SUPABASE_URL, SUPABASE_ANON_KEY, ARTICLES }, context) => {
const updateCache = async () => {
const { type, record, old_record } = request.content;
const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY);
if (type === "INSERT" || type === "UPDATE") {
await writeTo(ARTICLES, `/articles/${record.id}`, record);
}
if (type === "DELETE") {
await ARTICLES.delete(`/articles/${old_record.id}`);
}
const { data: articles } = await supabase.from("articles").select("*");
await writeTo(ARTICLES, "/articles", articles);
console.log("updated cache");
};
context.waitUntil(updateCache());
console.log("sending response");
return json({ received: true });
}
);
router.all("*", () => status(404, "Not found"));
export default {
fetch: router.handle,
};
@@ -0,0 +1,8 @@
export const readFrom = async (cache, path) => {
const data = await cache.get(path);
return JSON.parse(data);
};
export const writeTo = async (cache, path, data) => {
await cache.put(path, JSON.stringify(data));
};
@@ -0,0 +1,7 @@
name = "supabase-at-the-edge"
main = "src/index.js"
compatibility_date = "2022-07-07"
kv_namespaces = [
{ binding = "replace-with-your-kv-name", id = "replace-with-your-kv-production-id", preview_id = "replace-with-your-kv-preview-id" }
]
@@ -0,0 +1,2 @@
NEXT_PUBLIC_SUPABASE_URL=
SUPABASE_SERVICE_ROLE_KEY=
@@ -0,0 +1,3 @@
{
"extends": "next/core-web-vitals"
}
@@ -0,0 +1,38 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.js
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# local env files
.env*.local
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts
.vscode
+44
View File
@@ -0,0 +1,44 @@
# Caching data at the edge with Next.js 13 and Supabase
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).
It showcases how to [fetch and cache data](// See the docs: https://beta.nextjs.org/docs/data-fetching/caching) at the Edge with Next.js and Supabase.
## Getting Started
First, start Supabase locally and seed your local development database with the [Supabase CLI](https://supabase.com/docs/reference/cli):
```bash
supabase start
```
Next, run the development server:
```bash
npm run dev
# or
yarn dev
```
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.
[API routes](https://nextjs.org/docs/api-routes/introduction) can be accessed on [http://localhost:3000/api/hello](http://localhost:3000/api/hello). This endpoint can be edited in `pages/api/hello.ts`.
The `pages/api` directory is mapped to `/api/*`. Files in this directory are treated as [API routes](https://nextjs.org/docs/api-routes/introduction) instead of React pages.
## Learn More
To learn more about Next.js, take a look at the following resources:
- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.
You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!
## Deploy on Vercel
The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.
Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
@@ -0,0 +1,26 @@
html,
body {
padding: 0;
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, Segoe UI, Roboto, Oxygen,
Ubuntu, Cantarell, Fira Sans, Droid Sans, Helvetica Neue, sans-serif;
}
a {
color: inherit;
text-decoration: none;
}
* {
box-sizing: border-box;
}
@media (prefers-color-scheme: dark) {
html {
color-scheme: dark;
}
body {
color: white;
background: black;
}
}
@@ -0,0 +1,18 @@
import './globals.css'
export default function RootLayout({
children,
}: {
children: React.ReactNode
}) {
return (
<html lang="en">
<head>
<title>Create Next App</title>
<meta name="description" content="Generated by create next app" />
<link rel="icon" href="/favicon.ico" />
</head>
<body>{children}</body>
</html>
)
}
@@ -0,0 +1,21 @@
// Caching data at the edge with Next.js 13 and Supabase
// See the docs: https://beta.nextjs.org/docs/data-fetching/caching
import 'server-only'
import { createClient } from '@supabase/supabase-js'
const supabase = createClient(
process.env.NEXT_PUBLIC_SUPABASE_URL ?? 'http://localhost:54321',
process.env.SUPABASE_SERVICE_ROLE_KEY ??
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSJ9.vI9obAHOGyVVKa3pD--kJlyxp-Z2zV9UUMAhKpNLAcU'
)
export const config = {
runtime: 'experimental-edge',
}
export const revalidate = 60 // revalidate this page at most every 60 seconds
export default async function PostList() {
const { data, error } = await supabase.from('articles').select('*')
return <pre>{JSON.stringify({ data, error }, null, 2)}</pre>
}
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
experimental: {
appDir: true,
},
}
module.exports = nextConfig
File diff suppressed because it is too large. Load diff
@@ -0,0 +1,26 @@
{
"name": "with-nextjs-13",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"@supabase/supabase-js": "^2.0.4",
"next": "13.0.0",
"react": "18.2.0",
"react-dom": "18.2.0",
"server-only": "^0.0.1"
},
"devDependencies": {
"@types/node": "18.11.7",
"@types/react": "18.0.24",
"@types/react-dom": "18.0.8",
"eslint": "8.26.0",
"eslint-config-next": "13.0.0",
"typescript": "4.8.4"
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

@@ -0,0 +1,4 @@
<svg width="283" height="64" viewBox="0 0 283 64" fill="none"
xmlns="http://www.w3.org/2000/svg">
<path d="M141.04 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.46 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM248.72 16c-11.04 0-19 7.2-19 18s8.96 18 20 18c6.67 0 12.55-2.64 16.19-7.09l-7.65-4.42c-2.02 2.21-5.09 3.5-8.54 3.5-4.79 0-8.86-2.5-10.37-6.5h28.02c.22-1.12.35-2.28.35-3.5 0-10.79-7.96-17.99-19-17.99zm-9.45 14.5c1.25-3.99 4.67-6.5 9.45-6.5 4.79 0 8.21 2.51 9.45 6.5h-18.9zM200.24 34c0 6 3.92 10 10 10 4.12 0 7.21-1.87 8.8-4.92l7.68 4.43c-3.18 5.3-9.14 8.49-16.48 8.49-11.05 0-19-7.2-19-18s7.96-18 19-18c7.34 0 13.29 3.19 16.48 8.49l-7.68 4.43c-1.59-3.05-4.68-4.92-8.8-4.92-6.07 0-10 4-10 10zm82.48-29v46h-9V5h9zM36.95 0L73.9 64H0L36.95 0zm92.38 5l-27.71 48L73.91 5H84.3l17.32 30 17.32-30h10.39zm58.91 12v9.69c-1-.29-2.06-.49-3.2-.49-5.81 0-10 4-10 10V51h-9V17h9v9.2c0-5.08 5.91-9.2 13.2-9.2z" fill="#000"/>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

@@ -0,0 +1,3 @@
# Supabase
.branches
.temp
@@ -0,0 +1,71 @@
# A string used to distinguish different Supabase projects on the same host. Defaults to the working
# directory name when running `supabase init`.
project_id = "with-nextjs-13"
[api]
# Port to use for the API URL.
port = 54321
# Schemas to expose in your API. Tables, views and stored procedures in this schema will get API
# endpoints. public and storage are always included.
schemas = []
# Extra schemas to add to the search_path of every request.
extra_search_path = ["extensions"]
# The maximum number of rows returns from a view, table, or stored procedure. Limits payload size
# for accidental or malicious requests.
max_rows = 1000
[db]
# Port to use for the local database URL.
port = 54322
# The database major version to use. This has to be the same as your remote database's. Run `SHOW
# server_version;` on the remote database to check.
major_version = 14
[studio]
# Port to use for Supabase Studio.
port = 54323
# Email testing server. Emails sent with the local dev setup are not actually sent - rather, they
# are monitored, and you can view the emails that would have been sent from the web interface.
[inbucket]
# Port to use for the email testing server web interface.
port = 54324
smtp_port = 54325
pop3_port = 54326
[storage]
# The maximum file size allowed (e.g. "5MB", "500KB").
file_size_limit = "50MiB"
[auth]
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = "http://localhost:3000"
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = ["https://localhost:3000"]
# How long tokens are valid for, in seconds. Defaults to 3600 (1 hour), maximum 604,800 seconds (one
# week).
jwt_expiry = 3600
# Allow/disallow new user signups to your project.
enable_signup = true
[auth.email]
# Allow/disallow new user signups via email to your project.
enable_signup = true
# If enabled, a user will be required to confirm any email change on both the old, and new email
# addresses. If disabled, only the new email is required to confirm.
double_confirm_changes = true
# If enabled, users need to confirm their email address before signing in.
enable_confirmations = false
# Use an external OAuth provider. The full list of providers are: `apple`, `azure`, `bitbucket`,
# `discord`, `facebook`, `github`, `gitlab`, `google`, `twitch`, `twitter`, `slack`, `spotify`.
[auth.external.apple]
enabled = false
client_id = ""
secret = ""
# Overrides the default auth redirectUrl.
redirect_uri = ""
# Overrides the default auth provider URL. Used to support self-hosted gitlab, single-tenant Azure,
# or any other third-party OIDC providers.
url = ""
@@ -0,0 +1,7 @@
create table if not exists articles (
id uuid default uuid_generate_v4() primary key,
created_at timestamp with time zone default timezone('utc'::text, now()) not null,
title text not null,
content text not null,
is_published bool default false not null
);
@@ -0,0 +1,5 @@
insert into articles(title, content)
values
('First blog', 'This is my very first blog'),
('Second blog', 'I am really enjoying writing blogs'),
('Third blog', 'Okay, this is getting hard to maintain');
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es5",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"forceConsistentCasingInFileNames": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "node",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}