Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit bdc4650

Browse files
authored
examples: Add with-supabase-auth-realtime-db example. (vercel#16016)
r? @lfades @timothyis cc @kiwicopple @awalias Adding a realtime chat example showing how to implement authentication and realtime data syncing with supabase.io :)
1 parent 604ca6c commit bdc4650

File tree

15 files changed

+732
-0
lines changed

15 files changed

+732
-0
lines changed
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
NEXT_PUBLIC_SUPABASE_URL=https://your-project.supabase.co
2+
NEXT_PUBLIC_SUPABASE_KEY=your-anon-key
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
2+
3+
# dependencies
4+
/node_modules
5+
/.pnp
6+
.pnp.js
7+
8+
# testing
9+
/coverage
10+
11+
# next.js
12+
/.next/
13+
/out/
14+
15+
# production
16+
/build
17+
18+
# misc
19+
.DS_Store
20+
*.pem
21+
22+
# debug
23+
npm-debug.log*
24+
yarn-debug.log*
25+
yarn-error.log*
26+
27+
# local env files
28+
.env.local
29+
.env.development.local
30+
.env.test.local
31+
.env.production.local
32+
33+
# vercel
34+
.vercel
Lines changed: 139 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,139 @@
1+
# Realtime chat example using Supabase
2+
3+
This is a full-stack Slack clone example using:
4+
5+
- Frontend:
6+
- Next.js.
7+
- [Supabase](https://supabase.io/docs/library/getting-started) for user management and realtime data syncing.
8+
- Backend:
9+
- [app.supabase.io](https://app.supabase.io/): hosted Postgres database with restful API for usage with Supabase.js.
10+
11+
![Demo animation gif](./docs/slack-clone-demo.gif)
12+
13+
This example is a clone of the [Slack Clone example](https://github.com/supabase/supabase/tree/master/examples/slack-clone) in the supabase repo, feel free to check it out!
14+
15+
## Deploy your own
16+
17+
Once you have access to [the environment variables you'll need](#step-3-set-up-environment-variables), deploy the example using [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example):
18+
19+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db&env=NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_KEY&envDescription=Required%20to%20connect%20the%20app%to%Supabase&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db%23step-3-set-up-environment-variables&project-name=supabase-slack-clone&repo-name=supabase-slack-clone)
20+
21+
## How to use
22+
23+
Execute [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app) with [npm](https://docs.npmjs.com/cli/init) or [Yarn](https://yarnpkg.com/lang/en/docs/cli/create/) to bootstrap the example:
24+
25+
```bash
26+
npx create-next-app --example with-supabase-auth-realtime-db realtime-chat-app
27+
# or
28+
yarn create next-app --example with-supabase-auth-realtime-db realtime-chat-app
29+
```
30+
31+
## Configuration
32+
33+
### Step 1. Create a new Supabase project
34+
35+
Sign up to Supabase - [https://app.supabase.io](https://app.supabase.io) and create a new project. Wait for your database to start.
36+
37+
### Step 2. Run the "Slack Clone" Quickstart
38+
39+
Once your database has started, run the "Slack Clone" quickstart.
40+
41+
![Slack Clone Quick Start](https://user-images.githubusercontent.com/10214025/88916135-1b1d7a00-d298-11ea-82e7-e2c18314e805.png)
42+
43+
### Step 3. Set up environment variables
44+
45+
In your Supabase project, go to Project Settings (the cog icon), open the API tab, and find your **API URL** and **anon** key, you'll need these in the next step.
46+
47+
![image](https://user-images.githubusercontent.com/10214025/88916245-528c2680-d298-11ea-8a71-708f93e1ce4f.png)
48+
49+
Next, copy the `.env.local.example` file in this directory to `.env.local` (which will be ignored by Git):
50+
51+
```bash
52+
cp .env.local.example .env.local
53+
```
54+
55+
Then set each variable on `.env.local`:
56+
57+
- `NEXT_PUBLIC_SUPABASE_URL` should be the **API URL**
58+
- `NEXT_PUBLIC_SUPABASE_KEY` should be the **anon** key
59+
60+
The **anon** key is your client-side API key. It allows "anonymous access" to your database, until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row level security for your data. Read more about this [below](#postgres-row-level-security).
61+
62+
> **_NOTE_**: The `service_role` key has full access to your data, bypassing any security policies. These keys have to be kept secret and are meant to be used in server environments and never on a client or browser.
63+
64+
### Step 4. Run Next.js in development mode
65+
66+
```bash
67+
npm install
68+
npm run dev
69+
70+
# or
71+
72+
yarn install
73+
yarn dev
74+
```
75+
76+
Visit [http://localhost:3000](http://localhost:3000) and start chatting! Open a channel across two browser tabs to see everything getting updated in realtime 🥳. If it doesn't work, post on [GitHub discussions](https://github.com/vercel/next.js/discussions).
77+
78+
### Step 5. Deploy on Vercel
79+
80+
You can deploy this app to the cloud with [Vercel](https://vercel.com?utm_source=github&utm_medium=readme&utm_campaign=next-example) ([Documentation](https://nextjs.org/docs/deployment)).
81+
82+
#### Deploy Your Local Project
83+
84+
To deploy your local project to Vercel, push it to GitHub/GitLab/Bitbucket and [import to Vercel](https://vercel.com/import/git?utm_source=github&utm_medium=readme&utm_campaign=next-example).
85+
86+
**Important**: When you import your project on Vercel, make sure to click on **Environment Variables** and set them to match your `.env.local` file.
87+
88+
#### Deploy from Our Template
89+
90+
Alternatively, you can deploy using our template by clicking on the Deploy button below.
91+
92+
[![Deploy with Vercel](https://vercel.com/button)](https://vercel.com/import/git?c=1&s=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db&env=NEXT_PUBLIC_SUPABASE_URL,NEXT_PUBLIC_SUPABASE_KEY&envDescription=Required%20to%20connect%20the%20app%to%Supabase&envLink=https://github.com/vercel/next.js/tree/canary/examples/with-supabase-auth-realtime-db%23step-3-set-up-environment-variables&project-name=supabase-slack-clone&repo-name=supabase-slack-clone)
93+
94+
## Supabase details
95+
96+
### Postgres Row level security
97+
98+
This project uses very high-level Authorization using Postgres' Role Level Security.
99+
When you start a Postgres database on Supabase, we populate it with an `auth` schema, and some helper functions.
100+
When a user logs in, they are issued a JWT with the role `authenticated` and thier UUID.
101+
We can use these details to provide fine-grained control over what each user can and cannot do.
102+
103+
This is a trimmed-down schema, with the policies:
104+
105+
```sql
106+
-- USER PROFILES
107+
CREATE TYPE public.user_status AS ENUM ('ONLINE', 'OFFLINE');
108+
CREATE TABLE public.users (
109+
id uuid NOT NULL PRIMARY KEY, -- UUID from auth.users (Supabase)
110+
username text,
111+
status user_status DEFAULT 'OFFLINE'::public.user_status
112+
);
113+
ALTER TABLE public.users ENABLE ROW LEVEL SECURITY;
114+
CREATE POLICY "Allow logged-in read access" on public.users FOR SELECT USING ( auth.role() = 'authenticated' );
115+
CREATE POLICY "Allow individual insert access" on public.users FOR INSERT WITH CHECK ( auth.uid() = id );
116+
CREATE POLICY "Allow individual update access" on public.users FOR UPDATE USING ( auth.uid() = id );
117+
118+
-- CHANNELS
119+
CREATE TABLE public.channels (
120+
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
121+
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
122+
slug text NOT NULL UNIQUE
123+
);
124+
ALTER TABLE public.channels ENABLE ROW LEVEL SECURITY;
125+
CREATE POLICY "Allow logged-in full access" on public.channels FOR ALL USING ( auth.role() = 'authenticated' );
126+
127+
-- MESSAGES
128+
CREATE TABLE public.messages (
129+
id bigint GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
130+
inserted_at timestamp with time zone DEFAULT timezone('utc'::text, now()) NOT NULL,
131+
message text,
132+
user_id uuid REFERENCES public.users NOT NULL,
133+
channel_id bigint REFERENCES public.channels NOT NULL
134+
);
135+
ALTER TABLE public.messages ENABLE ROW LEVEL SECURITY;
136+
CREATE POLICY "Allow logged-in read access" on public.messages USING ( auth.role() = 'authenticated' );
137+
CREATE POLICY "Allow individual insert access" on public.messages FOR INSERT WITH CHECK ( auth.uid() = user_id );
138+
CREATE POLICY "Allow individual update access" on public.messages FOR UPDATE USING ( auth.uid() = user_id );
139+
```
Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
import Link from 'next/link'
2+
import { useContext } from 'react'
3+
import UserContext from '~/lib/UserContext'
4+
import { addChannel } from '~/lib/Store'
5+
6+
export default function Layout(props) {
7+
const { signOut } = useContext(UserContext)
8+
9+
const slugify = (text) => {
10+
return text
11+
.toString()
12+
.toLowerCase()
13+
.replace(/\s+/g, '-') // Replace spaces with -
14+
.replace(/[^\w-]+/g, '') // Remove all non-word chars
15+
.replace(/--+/g, '-') // Replace multiple - with single -
16+
.replace(/^-+/, '') // Trim - from start of text
17+
.replace(/-+$/, '') // Trim - from end of text
18+
}
19+
20+
const newChannel = async () => {
21+
const slug = prompt('Please enter your name')
22+
if (slug) {
23+
addChannel(slugify(slug))
24+
}
25+
}
26+
27+
return (
28+
<main className="main flex h-screen w-screen absolute overflow-hidden">
29+
{/* Sidebar */}
30+
<nav
31+
className="w-64 bg-gray-900 text-gray-100 h-screen"
32+
style={{ maxWidth: '20%', minWidth: 150 }}
33+
>
34+
<div className="p-2">
35+
<h4 className="font-bold">Channels</h4>
36+
<ul className="channel-list">
37+
{props.channels.map((x) => (
38+
<SidebarItem
39+
channel={x}
40+
key={x.id}
41+
isActiveChannel={x.id === props.activeChannelId}
42+
/>
43+
))}
44+
</ul>
45+
</div>
46+
<hr className="m-2" />
47+
<div className="p-2">
48+
<button
49+
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
50+
onClick={() => newChannel()}
51+
>
52+
New Channel
53+
</button>
54+
</div>
55+
<hr className="m-2" />
56+
<div className="p-2">
57+
<button
58+
className="bg-blue-900 hover:bg-blue-800 text-white py-2 px-4 rounded w-full transition duration-150"
59+
onClick={() => signOut()}
60+
>
61+
Log out
62+
</button>
63+
</div>
64+
</nav>
65+
66+
{/* Messages */}
67+
<div className="flex-1 bg-gray-800 h-screen">{props.children}</div>
68+
</main>
69+
)
70+
}
71+
72+
const SidebarItem = ({ channel, isActiveChannel }) => (
73+
<>
74+
<li>
75+
<Link href="/channels/[id]" as={`/channels/${channel.id}`}>
76+
<a className={isActiveChannel ? 'font-bold' : ''}>{channel.slug}</a>
77+
</Link>
78+
</li>
79+
</>
80+
)
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
const Message = ({ message }) => (
2+
<>
3+
<div className="py-1">
4+
<p className="text-blue-700 font-bold">{message.author.username}</p>
5+
<p className="text-white">{message.message}</p>
6+
</div>
7+
</>
8+
)
9+
10+
export default Message
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import { useState } from 'react'
2+
3+
const MessageInput = ({ onSubmit }) => {
4+
const [messageText, setMessageText] = useState('')
5+
6+
const submitOnEnter = (event) => {
7+
// Watch for enter key
8+
if (event.keyCode === 13) {
9+
onSubmit(messageText)
10+
setMessageText('')
11+
}
12+
}
13+
14+
return (
15+
<>
16+
<input
17+
className="shadow appearance-none border rounded w-full py-2 px-3 text-gray-700 leading-tight focus:outline-none focus:shadow-outline"
18+
type="text"
19+
placeholder="Send a message"
20+
value={messageText}
21+
onChange={(e) => setMessageText(e.target.value)}
22+
onKeyDown={(e) => submitOnEnter(e)}
23+
/>
24+
</>
25+
)
26+
}
27+
28+
export default MessageInput
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
{
2+
"compilerOptions": {
3+
"baseUrl": ".",
4+
"paths": {
5+
"~/*": ["./*"]
6+
}
7+
}
8+
}

0 commit comments

Comments
 (0)