<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:media="http://search.yahoo.com/mrss/"><channel><title><![CDATA[Robert Lee]]></title><description><![CDATA[Thoughts, stories and ideas.]]></description><link>https://robertlee.co/</link><image><url>https://robertlee.co/favicon.png</url><title>Robert Lee</title><link>https://robertlee.co/</link></image><generator>Ghost 5.13</generator><lastBuildDate>Thu, 16 Apr 2026 00:35:52 GMT</lastBuildDate><atom:link href="https://robertlee.co/rss/" rel="self" type="application/rss+xml"/><ttl>60</ttl><item><title><![CDATA[A simple solution to back pain]]></title><description><![CDATA[<p>I&apos;ve been spending a lot of time in the chair the past year working on various code and found myself experiencing some lower back pain for the first time in my life &#x2013; without some aggravating injury. </p><p>A simple solution that worked for me:</p><ol><li>Lay prone on the</li></ol>]]></description><link>https://robertlee.co/a-simple-solution-to-back-pain/</link><guid isPermaLink="false">64cfd4b50a6010047f7e0077</guid><dc:creator><![CDATA[Robert Lee]]></dc:creator><pubDate>Sun, 06 Aug 2023 17:16:06 GMT</pubDate><content:encoded><![CDATA[<p>I&apos;ve been spending a lot of time in the chair the past year working on various code and found myself experiencing some lower back pain for the first time in my life &#x2013; without some aggravating injury. </p><p>A simple solution that worked for me:</p><ol><li>Lay prone on the floor for 2 minutes</li><li>From the prone position, place your arms under your shoulders &amp; brace yourself up in a plank like position but your legs still remain on the floor. Do this for 2 minutes.</li><li>Finally, fully extend your arms, lined up under your shoulders, to push from the prone position to fully curve your back as far as possible for 2 minutes.</li></ol><p>Repeat this multiple times a day. Your back pain will localize then eventually dissipate within days or weeks. </p><p>Source:</p><figure class="kg-card kg-embed-card"><iframe width="200" height="113" src="https://www.youtube.com/embed/HoNW3TnAT3Y?feature=oembed" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen title="How To Fix Low Back Pain &amp; Sciatica"></iframe></figure>]]></content:encoded></item><item><title><![CDATA[Part 2: Unlocking User Management in SvelteKit+Supabase]]></title><description><![CDATA[<p>We&apos;ll cover how to handle user authentication in this part 2 series.</p><h2 id="how-supabase-simplifies-data-security-for-our-startup">How supabase simplifies data security for our startup</h2><p>With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...</p><pre><code class="language-js">const loggedInUserId = &apos;d0714948&apos;
let { data, error } = await supabase
  .from(&apos;</code></pre>]]></description><link>https://robertlee.co/part-2-users-and-organizations-supabase-sveltekit/</link><guid isPermaLink="false">6465426485c3f70486c2f6d0</guid><category><![CDATA[Sveltekit+Supabase Startup]]></category><dc:creator><![CDATA[Robert Lee]]></dc:creator><pubDate>Tue, 25 Jul 2023 12:59:13 GMT</pubDate><content:encoded><![CDATA[<p>We&apos;ll cover how to handle user authentication in this part 2 series.</p><h2 id="how-supabase-simplifies-data-security-for-our-startup">How supabase simplifies data security for our startup</h2><p>With policies, your database becomes the rules engine. Instead of repetitively filtering your queries, like this ...</p><pre><code class="language-js">const loggedInUserId = &apos;d0714948&apos;
let { data, error } = await supabase
  .from(&apos;users&apos;)
  .select(&apos;user_id, name&apos;)
  .eq(&apos;user_id&apos;, loggedInUserId)

// console.log(data)
// =&gt; { id: &apos;d0714948&apos;, name: &apos;Jane&apos; }</code></pre><p>... you can simply define a rule on your database table, <code>auth.uid() = user_id</code>, and your request will return the rows which pass the rule.</p><h3 id="how-it-works">How it works</h3><ol><li>A user signs up. Supabase creates a new user in the <code>auth.users</code> table.</li><li>Supabase returns a new JWT, which contains the user&apos;s <code>UUID</code>.</li><li>Every request to your database also sends the JWT.</li><li>Postgres inspects the JWT to determine the user making the request.</li><li>The user&apos;s UID can be used in policies to restrict access to rows.</li></ol><p>Supabase provides a special function in Postgres, <code>auth.uid()</code>, which extracts the user&apos;s UID from the JWT. This is especially useful when creating policies.</p><h2 id="user-authentication-in-the-app">User authentication in the app</h2><p>In our sample app, we are making authentication optional on the marketing pages &amp; mandatory on app pages. We&apos;ll work in that logic as we work through creating user accounts in this part 2.</p><p>First let&apos;s create our signup page. First we&apos;re going to create a folder within the <code>routes</code> directory that will wrap all of our authentication pages. This allows us to setup a custom <code>+layout.svelte</code> for all authentication pages to keep them looking similar. Let&apos;s create <code>src/routes/(auth)</code> which is the folder wrapper that contains all of our authentication pages.</p><p>Let&apos;s create the <code>src/routes/(auth)/+layout.svelte</code> page to serve as the wrapper for all of our authentication pages.</p><figure class="kg-card kg-code-card"><pre><code class="language-svelte">&lt;div class=&quot;bg-gray-50 min-h-screen&quot;&gt;
	&lt;div class=&quot;flex min-h-full flex-col justify-center py-12 sm:px-6 lg:px-8&quot;&gt;
		&lt;div class=&quot;mx-auto&quot;&gt;
			&lt;div class=&quot;font-serif text-4xl text-green-500&quot;&gt;deff&lt;/div&gt;
		&lt;/div&gt;
		&lt;slot /&gt;
	&lt;/div&gt;
&lt;/div&gt;
</code></pre><figcaption>src/routes/(auth)/+layout.svelte</figcaption></figure><p>Now we&apos;ll create the signup page at <code>src/routes/(auth)/signup/+page.svelte</code>: </p><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import { goto } from &apos;$app/navigation&apos;;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase } = data);

	let loading = false;
	let name: string;
	let email: string;
	let password: string;

	async function handleLogin() {
		try {
			loading = true;
			const { error } = await supabase.auth.signUp({
				email,
				password,
				options: {
					data: { name }
				}
			});
			if (error) throw error;
			goto(`/verify?email=${email}`);
		} catch (error) {
			if (error instanceof Error) {
				alert(error.message);
			}
		} finally {
			loading = false;
		}
	};
&lt;/script&gt;

&lt;div class=&quot;sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
	&lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;
		Create an account
	&lt;/h2&gt;
	&lt;div class=&quot;mt-8 sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
		&lt;div class=&quot;bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10&quot;&gt;
			&lt;form class=&quot;py-4 space-y-6&quot; on:submit|preventDefault={handleLogin}&gt;
				&lt;div&gt;
					&lt;label for=&quot;name&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Name&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;text&quot;
							autocomplete=&quot;name&quot;
							placeholder=&quot;Your name&quot;
							bind:value={name}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Email address&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;email&quot;
							placeholder=&quot;Your email&quot;
							bind:value={email}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;
				&lt;div&gt;
					&lt;label for=&quot;password&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Password&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;password&quot;
							placeholder=&quot;Your password&quot;
							bind:value={password}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div&gt;
					&lt;button
						disabled={loading}
						type=&quot;submit&quot;
						class=&quot;flex w-full justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2&quot;
					&gt;
						{#if loading}
							Creating account...
						{:else}
							Create account
						{/if}&lt;/button
					&gt;
				&lt;/div&gt;
			&lt;/form&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;
</code></pre><p>Ok now let&apos;s test it out by going to <code>localhost:5173/signup</code> and creating an account. After putting in your name, email, and a password, click sign in. Within the studio at <a href="http://localhost:54323/project/default/auth/users">http://localhost:54323/project/default/auth/users</a>, you should see your user account created. You should also see an email within the monitor at <a href="http://localhost:54324/monitor">http://localhost:54324/monitor</a> containing your verification email. This monitor is a mock emailer listener that handles email notifications for authentication testing locally. </p><p>When you click the link to verify your email, it&apos;ll take you to a <code>localhost:3000</code> callback link. We need to modify the <code>supabase/config.toml</code> file to reflect our specific local environment. Make the following changes:</p><pre><code class="language-toml">[auth]
# The base URL of your website. Used as an allow-list for redirects and for constructing URLs used
# in emails.
site_url = &quot;http://localhost:5173&quot;
# A list of *exact* URLs that auth providers are permitted to redirect to post authentication.
additional_redirect_urls = [&quot;https://localhost:5173&quot;]</code></pre><p>You&apos;ll need to restart your local dev instance in order to apply these changes: </p><pre><code class="language-bash">npx supabase stop &amp;&amp; npx supabase start</code></pre><p>Try creating a new account at http://localhost:5173/signup. Once you click on the email link you should be directed to the home page with a valid session. </p><p>For production purposes, we need to create a verify code page that the user is directed to after signing up &amp; when they click the link. Some email providers scan links which break the &quot;magic links&quot;/&quot;one time link&quot; functionality of our email links we send out. For those users, we&apos;ll have a verify link where they can manually enter the OTP code. </p><p>Create the verify page at <code>src/routes/(auth)/verify/+page.svelte</code> with the following code:</p><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import { goto } from &apos;$app/navigation&apos;;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase } = data);

	let loading = false;
	let token: string;

	const verify = async () =&gt; {
		try {
			loading = true;
			const { error } = await supabase.auth.verifyOtp({
				email: data.email!,
				token,
				type: &apos;signup&apos;
			});
			if (error) throw error;
			await goto(&apos;/&apos;);
		} catch (error) {
			return alert(&apos;Verification code is invalid. Try signing in again.&apos;);
		} finally {
			loading = false;
		}
	};

	if (data.token) {
		token = data.token;
	}
&lt;/script&gt;

&lt;div class=&quot;sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
	&lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;
		Verify your email address
	&lt;/h2&gt;
	&lt;p class=&quot;mt-4 text-center text-md text-gray-500&quot;&gt;
		Check your email for a magic link. If the link does not log you in automatically, enter the
		verification code below.
	&lt;/p&gt;
	&lt;div class=&quot;mt-8 sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
		&lt;div class=&quot;bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10&quot;&gt;
			&lt;form class=&quot;py-4 space-y-6&quot; on:submit|preventDefault={verify}&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;
						&gt;Verification code&lt;/label
					&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;text&quot;
							autocomplete=&quot;off&quot;
							placeholder=&quot;Code provided in email&quot;
							bind:value={token}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
					&lt;p class=&quot;mt-1 text-sm text-left text-gray-500&quot;&gt;
						You should receive an email containing your sign in code at {&apos; &apos;}
						&lt;span class=&quot;font-semibold&quot;&gt;{data.email}&lt;/span&gt;
					&lt;/p&gt;
				&lt;/div&gt;

				&lt;div&gt;
					&lt;button
						disabled={loading}
						type=&quot;submit&quot;
						class=&quot;flex w-full justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2&quot;
					&gt;
						{#if loading}
							Signing in...
						{:else}
							Verify
						{/if}
					&lt;/button&gt;
				&lt;/div&gt;
			&lt;/form&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p class=&quot;mt-10 text-center text-sm text-gray-500&quot;&gt;
	Didn&apos;t receive an email?
	&lt;a href=&quot;/login&quot; class=&quot;font-semibold leading-6 text-green-600 hover:text-green-500&quot;
		&gt;Try signing in again.&lt;/a
	&gt;
&lt;/p&gt;
</code></pre><p>In order to populate the <code>PageData</code> with the required <code>email</code> and <code>token</code> we need to perform a OTP login, we&apos;ll retrieve the parameters from the URL. Create a server load page at <code>src/routes/(auth)/verify/+page.server.ts</code>:</p><pre><code class="language-ts">// src/routes/+layout.server.ts
import { redirect } from &apos;@sveltejs/kit&apos;;
import type { PageServerLoad } from &apos;./$types&apos;;

export const load: PageServerLoad = async (event) =&gt; {
	const session = await event.locals.getSession();
	if (session) {
		throw redirect(303, &apos;/&apos;);
	}
	return {
		email: event.url.searchParams.get(&apos;email&apos;),
		token: event.url.searchParams.get(&apos;token&apos;),
		error: event.url.searchParams.get(&apos;error&apos;)
	};
};
</code></pre><p>Ok now create a new account via the signup flow. When checking the monitor for an email, instead of clicking the link, copy the code. Paste it into the verify box. You should now have a working verify page. We&apos;ll reuse this similar logic for a password reset as well. But first let&apos;s create a login page.</p><p>Next, we&apos;ll create the login page at <code>src/routes/(auth)/login/+page.svelte</code> with the following code:</p><figure class="kg-card kg-code-card"><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import { goto } from &apos;$app/navigation&apos;;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase } = data);

	let loading = false;
	let email: string;
	let password: string;

	const handleLogin = async () =&gt; {
		try {
			loading = true;
			const { error } = await supabase.auth.signInWithPassword({
				email,
				password
			});
			if (error) throw error;
			goto(`/verify?email=${email}`);
		} catch (error) {
			if (error instanceof Error) {
				alert(error.message);
			}
		} finally {
			loading = false;
		}
	};
&lt;/script&gt;

&lt;div class=&quot;sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
	&lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;
		Sign in to your account
	&lt;/h2&gt;
	&lt;div class=&quot;mt-8 sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
		&lt;div class=&quot;bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10&quot;&gt;
			&lt;form class=&quot;py-4 space-y-6&quot; on:submit|preventDefault={handleLogin}&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Email address&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;email&quot;
							placeholder=&quot;Your email&quot;
							bind:value={email}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Email address&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;password&quot;
							placeholder=&quot;Your password&quot;
							bind:value={password}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div class=&quot;flex items-center justify-between&quot;&gt;
					&lt;div class=&quot;flex items-center&quot;&gt;
						&lt;input
							id=&quot;remember-me&quot;
							name=&quot;remember-me&quot;
							type=&quot;checkbox&quot;
							class=&quot;h-4 w-4 rounded border-gray-300 text-green-600 focus:ring-green-600&quot;
						/&gt;
						&lt;label for=&quot;remember-me&quot; class=&quot;ml-3 block text-sm leading-6 text-gray-900&quot;
							&gt;Remember me&lt;/label
						&gt;
					&lt;/div&gt;

					&lt;div class=&quot;text-sm leading-6&quot;&gt;
						&lt;a
							href=&quot;/forgot-password&quot;
							class=&quot;font-semibold text-green-600 hover:text-green-500&quot;
						&gt;
							Forgot password?
						&lt;/a&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div&gt;
					&lt;button
						disabled={loading}
						type=&quot;submit&quot;
						class=&quot;flex w-full justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2&quot;
					&gt;
						{#if loading}
							Signing in...
						{:else}
							Sign in
						{/if}&lt;/button
					&gt;
				&lt;/div&gt;
			&lt;/form&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p class=&quot;mt-10 text-center text-sm text-gray-500&quot;&gt;
	Don&apos;t have an account?
	&lt;a href=&quot;/signup&quot; class=&quot;font-semibold leading-6 text-green-600 hover:text-green-500&quot;&gt;Sign up&lt;/a&gt;
&lt;/p&gt;
</code></pre><figcaption>src/routes/(auth)/login/+page.svelte</figcaption></figure><p>Try logging in with your created account credentials and you should be routed to the home page. Now let&apos;s add a check on both the <code>/login</code> and <code>/signup</code> pages to see if the user is logged in. We&apos;ll direct them to <code>/</code> if so. </p><p>Create a <code>src/routes/(auth)/login/+page.server.ts</code> and <code>src/routes/(auth)/signup/+page.server.ts</code> with the same following typescript code:</p><pre><code class="language-ts">import { redirect } from &apos;@sveltejs/kit&apos;;
import type { PageServerLoad } from &apos;./$types&apos;;

export const load: PageServerLoad = async (event) =&gt; {
	const session = await event.locals.getSession();
	if (session) {
		throw redirect(303, &apos;/&apos;);
	}
	return {};
};
</code></pre><p>If you now try to go to <a href="http://localhost:5173/login">http://localhost:5173/login</a> you&apos;ll notice you get redirected to <a href="http://localhost:5173/">http://localhost:5173/</a>. We can add a logout button to <code>src/routes/+page.svelte</code> by modifying it to:</p><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase, session } = data);

	async function logout() {
		await supabase.auth.signOut();
	}
&lt;/script&gt;

&lt;h1&gt;Welcome to SvelteKit&lt;/h1&gt;
&lt;p&gt;Visit &lt;a href=&quot;https://kit.svelte.dev&quot;&gt;kit.svelte.dev&lt;/a&gt; to read the documentation&lt;/p&gt;

&lt;button on:click={logout}&gt; logout&lt;/button&gt;
&lt;pre&gt;{JSON.stringify(session)}&lt;/pre&gt;
</code></pre><p>Clicking logout now clears the session. You can go to <a href="http://localhost:5173/login">http://localhost:5173/login</a> and re-login again. </p><h3 id="forgot-password-functionality">Forgot password functionality</h3><p>Now let&apos;s build out the reset password functionality. First let&apos;s build out a link in the <code>/login</code> page to allow users to go to the <code>/forgot-password</code> page. </p><p>Create the following files:</p><figure class="kg-card kg-code-card"><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import { goto } from &apos;$app/navigation&apos;;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase } = data);

	let loading = false;
	let email: string;

	async function forgot() {
		if (!email) {
			return alert(&apos;enter an email to reset password&apos;);
		}
		const { error } = await supabase.auth.resetPasswordForEmail(email, {
			redirectTo: `${location.origin}/reset-password`
		});
		if (error) console.error(error);
		goto(&apos;/reset-password&apos;);
	}
&lt;/script&gt;

&lt;div class=&quot;sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
	&lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;
		Reset your password
	&lt;/h2&gt;
	&lt;div class=&quot;mt-8 sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
		&lt;div class=&quot;bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10&quot;&gt;
			&lt;form class=&quot;py-4 space-y-6&quot; on:submit|preventDefault={forgot}&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Email address&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;email&quot;
							placeholder=&quot;Your email&quot;
							bind:value={email}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div&gt;
					&lt;button
						disabled={loading}
						type=&quot;submit&quot;
						class=&quot;flex w-full justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2&quot;
					&gt;
						{#if loading}
							Sending reset email...
						{:else}
							Reset
						{/if}
					&lt;/button&gt;
				&lt;/div&gt;
			&lt;/form&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;

&lt;p class=&quot;mt-10 text-center text-sm text-gray-500&quot;&gt;
	Remember your password?
	&lt;a href=&quot;/login&quot; class=&quot;font-semibold leading-6 text-green-600 hover:text-green-500&quot;&gt;Log in&lt;/a&gt;
&lt;/p&gt;
</code></pre><figcaption>src/routes/(auth)/forgot-password/+page.svelte</figcaption></figure><p>Add a redirect for logged in users so they aren&apos;t triggering password resets:</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { redirect } from &apos;@sveltejs/kit&apos;;
import type { PageServerLoad } from &apos;./$types&apos;;

export const load: PageServerLoad = async (event) =&gt; {
	const session = await event.locals.getSession();
	if (session) {
		throw redirect(303, &apos;/&apos;);
	}
	return {};
};
</code></pre><figcaption>src/routes/(auth)/forgot-password/+page.server.ts</figcaption></figure><p>And lastly, lets create the page to allow users to change their password upon redirect from the password reset email</p><figure class="kg-card kg-code-card"><pre><code class="language-svelte">&lt;script lang=&quot;ts&quot;&gt;
	import { goto } from &apos;$app/navigation&apos;;
	import type { PageData } from &apos;./$types&apos;;
	export let data: PageData;

	$: ({ supabase } = data);

	let loading = false;
	let password1: string;
	let password2: string;

	async function reset() {
		if (!password1 || !password2) {
			return alert(&apos;enter an email to reset password&apos;);
		}
		if (password1 !== password1) {
			return alert(&apos;passwords dont match&apos;);
		}
		const { error } = await supabase.auth.updateUser({
			password: password2
		});
		if (error) console.error(error);
		goto(&apos;/&apos;);
	}
&lt;/script&gt;

&lt;div class=&quot;sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
	&lt;h2 class=&quot;mt-6 text-center text-3xl font-bold tracking-tight text-gray-900&quot;&gt;
		Reset your password
	&lt;/h2&gt;
	&lt;div class=&quot;mt-8 sm:mx-auto sm:w-full sm:max-w-md&quot;&gt;
		&lt;div class=&quot;bg-white py-8 px-4 shadow sm:rounded-lg sm:px-10&quot;&gt;
			&lt;form class=&quot;py-4 space-y-6&quot; on:submit|preventDefault={reset}&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Password&lt;/label&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;password&quot;
							placeholder=&quot;Enter a password&quot;
							bind:value={password1}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;
				&lt;div&gt;
					&lt;label for=&quot;email&quot; class=&quot;block text-sm font-medium text-gray-700&quot;&gt;Confirm password&lt;/label
					&gt;
					&lt;div class=&quot;mt-1&quot;&gt;
						&lt;input
							type=&quot;password&quot;
							placeholder=&quot;Confirm your password&quot;
							bind:value={password2}
							class=&quot;block w-full appearance-none rounded-md border border-gray-300 px-3 py-2 placeholder-gray-400 shadow-sm focus:border-green-500 focus:outline-none focus:ring-green-500 sm:text-sm&quot;
						/&gt;
					&lt;/div&gt;
				&lt;/div&gt;

				&lt;div&gt;
					&lt;button
						disabled={loading}
						type=&quot;submit&quot;
						class=&quot;flex w-full justify-center rounded-md border border-transparent bg-green-600 py-2 px-4 text-sm font-medium text-white shadow-sm hover:bg-green-700 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2&quot;
					&gt;
						{#if loading}
							Sending reset password email
						{:else}
							Reset your password
						{/if}
					&lt;/button&gt;
				&lt;/div&gt;
			&lt;/form&gt;
		&lt;/div&gt;
	&lt;/div&gt;
&lt;/div&gt;
</code></pre><figcaption>src/routes/(auth)/reset-password/+page.svelte</figcaption></figure><h3 id="social-authentication-w-google-sign-in">Social authentication w/ Google sign in</h3><p>Let&apos;s add in google sign in to our application. To do that, first let&apos;s get an oauth credential. Create a new project via <a href="https://console.cloud.google.com/apis/dashboard">google&apos;s developer console</a> and then create an oauth 2.0 key.</p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/07/Screen-Shot-2023-07-12-at-9.03.41-AM.png" class="kg-image" alt loading="lazy" width="962" height="612" srcset="https://robertlee.co/content/images/size/w600/2023/07/Screen-Shot-2023-07-12-at-9.03.41-AM.png 600w, https://robertlee.co/content/images/2023/07/Screen-Shot-2023-07-12-at-9.03.41-AM.png 962w" sizes="(min-width: 720px) 720px"></figure><p>Be sure to add the following urls to the authorized redirect urls (replacing xxx with your supabase project id):</p><pre><code>https://xxxxxxxxx.supabase.co/auth/v1/callback
http://localhost:54321/auth/v1/callback</code></pre><p>Next, add the following configuration to to the <code>config.toml</code></p><pre><code># supabase/config.toml

[auth.external.google]
enabled = true
client_id = &quot;1058874065202-9dtif4drc6bkd35lobtoktvo5xxxxxx.apps.googleusercontent.com&quot;
secret = &quot;GOCSPX-YU8MZTCqnIK9y_xxxxxxxxx_O&quot;
# Overrides the default auth redirectUrl.
# redirect_uri = &quot;&quot;
</code></pre><p>After changing your configuration file, be sure to restart your supabase instance to take effect: <code>npx supabase stop &amp;&amp; npx supabase start</code></p><p>Go ahead and click sign in with google and you should be able to redirect to the logged in page.</p><h2 id="update-leveraging-server-side-pkce">UPDATE: Leveraging server side PKCE</h2><p>First let&apos;s upgrade the supabase sveltekit auth helper:</p><figure class="kg-card kg-code-card"><pre><code>&quot;@supabase/auth-helpers-sveltekit&quot;: &quot;^0.10.0&quot;,
</code></pre><figcaption>Change the version of the auth helpers in package.json</figcaption></figure><p>Next we&apos;ll create the authentication callback to exchange a code for a session by creating the callback endpoint</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">import { redirect } from &apos;@sveltejs/kit&apos;

export const GET = async ({ url, locals: { supabase } }) =&gt; {
  const code = url.searchParams.get(&apos;code&apos;)

  if (code) {
    await supabase.auth.exchangeCodeForSession(code)
  }

  throw redirect(303, &apos;/&apos;)
}</code></pre><figcaption>src/routes/(auth)/auth/callback/+server.ts</figcaption></figure><p>Lastly let&apos;s update each callback url for login/signup to redirect to the /auth/callback url.</p><figure class="kg-card kg-code-card"><pre><code class="language-ts">async function handleLogin() {
	try {
		loading = true;
		const { error } = await supabase.auth.signUp({
			email,
			password,
			options: {
				data: { name },
				emailRedirectTo: `${location.origin}/auth/callback`
			}
		});
		if (error) throw error;
		goto(`/verify?email=${email}`);
	} catch (error) {
		if (error instanceof Error) {
			alert(error.message);
		}
	} finally {
		loading = false;
	}
}</code></pre><figcaption>Update the src/routes/(auth)/signup/+page.svelte handleLogin function to include the email direct to the callback url.</figcaption></figure>]]></content:encoded></item><item><title><![CDATA[Supercharge Your Startup Journey with SvelteKit and Supabase]]></title><description><![CDATA[Build quicker. Learn quicker. Startups are all about how fast you can get your idea in front of potential customers that will pay for a solution. Sveltekit + Supabase is the best combination I've seen since I've started full stack development almost a decade ago. ]]></description><link>https://robertlee.co/supercharge-your-startup-journey-with-sveltekit-and-supabase/</link><guid isPermaLink="false">6464c83d85c3f70486c2f4fe</guid><category><![CDATA[Sveltekit+Supabase Startup]]></category><dc:creator><![CDATA[Robert Lee]]></dc:creator><pubDate>Wed, 17 May 2023 20:22:18 GMT</pubDate><content:encoded><![CDATA[<p>Build quicker. Learn quicker. Startups are all about how fast you can get your idea in front of potential customers that will pay for a solution. Sveltekit + Supabase is the best combination I&apos;ve seen since I&apos;ve started full stack development almost a decade ago. </p><p>SvelteKit, a cutting-edge JavaScript framework, is revolutionizing the way web applications are built. It offers a refreshing approach to frontend development by shifting the heavy lifting from the browser to the build step, resulting in leaner and faster applications. SvelteKit&apos;s key principle is compiling components at build time, enabling highly efficient and performant web experiences. With its intuitive syntax, reactive programming model, and seamless integration with popular frontend libraries and tools, SvelteKit provides developers with a powerful toolkit for creating dynamic and interactive user interfaces. In this article, we&apos;ll delve into the features and advantages of SvelteKit that make it a compelling choice for modern web development projects.</p><p>Supabase, an open-source alternative to Firebase, is revolutionizing the way startups handle their backend infrastructure. It serves as a powerful combination of a Postgres database and a set of authentication and real-time features, offering developers a full-stack platform that simplifies and streamlines the development process. With Supabase, startups can leverage the reliability and scalability of PostgreSQL, a battle-tested and widely adopted database technology. Supabase provides real-time capabilities, enabling instant updates and collaboration between users. The built-in authentication system ensures secure user management, allowing startups to focus on building their core product without compromising on security. Supabase gives you all the backend features you need to build a product: Database, Authentication, Storage, Realtime API, Edge Functions, Serverless APIs, and more.</p><p>In this series, we will walk you through the entire process of building a startup from scratch using these two technologies. The reader is expected to have a decent understanding of <strong>Javascript/Typescript, CSS, and HTML. Some SQL understanding can come in handy. </strong>Assuming you have those skills in hand, jump aboard. Let&apos;s build your next startup.</p><h2 id="create-the-app">Create the app</h2><pre><code class="language-bash">npm create svelte@latest deff-app</code></pre><p>Then <code>cd deff-app &amp;&amp; pnpm i &amp;&amp; npx svelte-add@latest tailwindcss</code> to install the dependencies &amp; tailwindcss.<br></p><h2 id="start-supabase-locally-for-development">Start Supabase locally for development</h2><p>Most of your development will happen locally. In order to test your app, you want to have a local supabase database + studio running to run tests rather than leveraging a remote supabase instance. We&apos;ll walk you through how to set one up for your local development needs.</p><p><code>npx supabase init &amp;&amp; npx supabase start</code></p><p>I then changed <code>.env</code> with my prod variables into <code>.env.preview</code> and added the following to <code>.env</code>: </p><pre><code>deff-app git:(main) &#x2717; npx supabase start
Seeding data supabase/seed.sql...
Started supabase local development setup.         
API URL: http://localhost:54321          
DB URL: postgresql://postgres:postgres@localhost:54322/postgres
Studio URL: http://localhost:54323    
Inbucket URL: http://localhost:54324
JWT secret: super-secret-jwt-token-with-at-least-32-characters-long        anon key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0
service_role key: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6InNlcnZpY2Vfcm9sZSIsImV4cCI6MTk4MzgxMjk5Nn0.EGIM96RAZx35lJzdJsyH-qQwv8Hdp7fsn3W0YpN81IU</code></pre><p><br>I took the output from my local supabase and filled into <code>.env</code> </p><pre><code class="language-bash">PUBLIC_SUPABASE_URL=http://localhost:54321
PUBLIC_SUPABASE_ANON_KEY=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0</code></pre><p>We&apos;re now all setup to run locally supabase. You can access the database via the studio at <a href="http://localhost:54323">http://localhost:54323</a></p><h2 id="connect-to-supabase-w-auth-helpers">Connect &#xA0;to Supabase w/ auth helpers</h2><p>Install supabase js &amp; the sveltekit auth package:</p><pre><code>npm install @supabase/supabase-js @supabase/auth-helpers-sveltekit</code></pre><p>Add the code below to your <code>src/hooks.server.ts</code> to initialize the client on the server:</p><pre><code class="language-js">// src/hooks.server.ts
import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from &apos;$env/static/public&apos;
import { createSupabaseServerClient } from &apos;@supabase/auth-helpers-sveltekit&apos;
import type { Handle } from &apos;@sveltejs/kit&apos;

export const handle: Handle = async ({ event, resolve }) =&gt; {
  event.locals.supabase = createSupabaseServerClient({
    supabaseUrl: PUBLIC_SUPABASE_URL,
    supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
    event,
  })

  /**
   * A convenience helper so we can just call await getSession() instead const { data: { session } } = await supabase.auth.getSession()
   */
  event.locals.getSession = async () =&gt; {
    const {
      data: { session },
    } = await event.locals.supabase.auth.getSession()
    return session
  }

  return resolve(event, {
    filterSerializedResponseHeaders(name) {
      return name === &apos;content-range&apos;
    },
  })
}</code></pre><p>For typescript, update your <code>src/app.d.ts</code> with the content below:</p><pre><code class="language-js">// src/app.d.ts

import { SupabaseClient, Session } from &apos;@supabase/supabase-js&apos;

declare global {
  namespace App {
    interface Locals {
      supabase: SupabaseClient
      getSession(): Promise&lt;Session | null&gt;
    }
    interface PageData {
      session: Session | null
    }
    // interface Error {}
    // interface Platform {}
  }
}</code></pre><p>Create a new <code>src/routes/+layout.server.ts</code> file to handle the session on the server-side.</p><pre><code class="language-js">// src/routes/+layout.server.ts
import type { LayoutServerLoad } from &apos;./$types&apos;

export const load: LayoutServerLoad = async ({ locals: { getSession } }) =&gt; {
  return {
    session: await getSession(),
  }
}</code></pre><blockquote>Start your dev server (<code>npm run dev</code>) in order to generate the <code>./$types</code> files we are referencing in our project.</blockquote><p>Create a new <code>src/routes/+layout.ts</code> file to handle the session and the supabase object on the client-side.</p><pre><code class="language-ts">// src/routes/+layout.ts
import { invalidate } from &apos;$app/navigation&apos;
import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from &apos;$env/static/public&apos;
import { createSupabaseLoadClient } from &apos;@supabase/auth-helpers-sveltekit&apos;
import type { LayoutLoad } from &apos;./$types&apos;

export const load: LayoutLoad = async ({ fetch, data, depends }) =&gt; {
  depends(&apos;supabase:auth&apos;)

  const supabase = createSupabaseLoadClient({
    supabaseUrl: PUBLIC_SUPABASE_URL,
    supabaseKey: PUBLIC_SUPABASE_ANON_KEY,
    event: { fetch },
    serverSession: data.session,
  })

  const {
    data: { session },
  } = await supabase.auth.getSession()

  return { supabase, session }
}</code></pre><p>Update your <code>src/routes/+layout.svelte</code>:</p><pre><code class="language-svelte">&lt;!-- src/routes/+layout.svelte --&gt;
&lt;script lang=&quot;ts&quot;&gt;
	import &apos;../app.postcss&apos;;
	import { invalidate } from &apos;$app/navigation&apos;;
	import { onMount } from &apos;svelte&apos;;

	export let data;

	$: ({ supabase, session } = data);

	onMount(() =&gt; {
		const { data } = supabase.auth.onAuthStateChange((event, _session) =&gt; {
			if (_session?.expires_at !== session?.expires_at) {
				invalidate(&apos;supabase:auth&apos;);
			}
		});

		return () =&gt; data.subscription.unsubscribe();
	});
&lt;/script&gt;

&lt;svelte:head&gt;
	&lt;title&gt;deff&lt;/title&gt;
&lt;/svelte:head&gt;

&lt;slot /&gt;</code></pre><p><br>Now all the pieces are in place for supabase. We&apos;ll come back to this later to implement user authentication in a subsequent article.</p><h2 id="add-svelte-inspector"><br>Add svelte inspector</h2><p>This allows me to shift-click within chome to go directly to vscode component.<br>Add the following to `svelte.config.js` </p><pre><code class="language-js">const config = {  
    // ...  
    vitePlugin: {    
       inspector: true    
    }
};</code></pre><h2 id="set-up-github-supabase-vercel"><br>Set up github &amp; supabase &amp; vercel</h2><p>In order to make our development supercharged, we want to leverage automated devops techniques so we&apos;re only focused on product + marketing. Github + Github Actions + Supabase + Vercel ensures we aren&apos;t worrying about any servers.</p><p>First let&apos;s get the code into a github repo. I will be sharing code snippets that work for my specific github repo so you&apos;ll need to replace the github repo link accordingly if you&apos;re copying &amp; pasting.</p><pre><code class="language-bash">git init
git add .
git commit -m &quot;first commit&quot;
git branch -M main
git remote add origin git@github.com:leerobert/sveltekit-supabase-startup.git
git push -u origin main</code></pre><p>The code is now live at <a href="https://github.com/leerobert/sveltekit-supabase-startup">https://github.com/leerobert/sveltekit-supabase-startup</a></p><p>Next I create a new project on supabase (https://app.supabase.com/new) and get the relevant credentials:</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-9.59.24-AM.png" class="kg-image" alt loading="lazy" width="1406" height="1324" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-9.59.24-AM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-9.59.24-AM.png 1000w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-9.59.24-AM.png 1406w" sizes="(min-width: 720px) 720px"><figcaption>Creating the production supabase account</figcaption></figure><p>Then I go to Project Settings &gt; API tab to view the API credentials. From there I retrieve the following two variables that are required to deploy the app within vercel:</p><pre><code class="language-bash">PUBLIC_SUPABASE_URL=https://your-project.supabase.co
PUBLIC_SUPABASE_ANON_KEY=ey-your-anon-key</code></pre><p><br>Then create a new project on <a href="vercel.com">Vercel</a> and connect it to the github repo. And I add the above supabase environment variables to the vercel environment variables.</p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.04.57-AM.png" class="kg-image" alt loading="lazy" width="1628" height="1318" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-10.04.57-AM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-10.04.57-AM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-10.04.57-AM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.04.57-AM.png 1628w" sizes="(min-width: 720px) 720px"></figure><p>It should look something like this within the environment variables component of creating the Vercel web app. Click deploy and you should have vercel linked to your github repo. This is crucial for the final step which is initializing a proper CI/CD workflow so you can optimally develop locally &amp; push up migrations / functions with any devops work. </p><h2 id="setup-cicd">Setup CI/CD</h2><p>&lt;paragraph about how to do ci/cd properly. local dev. make migrations. push code + migrations to a staging/develop branch. deploy check change in a staging env. then push to production&gt;</p><p>Let&apos;s create a second supabase instance for our staging environment. </p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.31.13-AM.png" class="kg-image" alt loading="lazy" width="1430" height="1336" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-10.31.13-AM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-10.31.13-AM.png 1000w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.31.13-AM.png 1430w" sizes="(min-width: 720px) 720px"><figcaption>Create the staging supabase instance</figcaption></figure><p>Next let&apos;s create a <code>staging</code> branch and connect that branch to Vercel. To do that, first we must create a staging branch and push it up to github:</p><pre><code class="language-bash">git checkout -b staging
git push -u origin staging</code></pre><p>Then I go to the domain settings for my project (the link looks like this for me <a href="https://vercel.com/leerobert/sveltekit-supabase-startup/settings/domains">https://vercel.com/leerobert/sveltekit-supabase-startup/settings/domains</a>). I enter a new domain with -staging appended to the name. Something like: <code>sveltekit-supabase-startup-staging.vercel.app</code></p><p>I click edit and change the branch to <code>staging</code> </p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.42.00-AM.png" class="kg-image" alt loading="lazy" width="1946" height="630" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-10.42.00-AM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-10.42.00-AM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-10.42.00-AM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.42.00-AM.png 1946w" sizes="(min-width: 720px) 720px"></figure><p>You should have two domains linked to two different branches now and your domain list should look like this:</p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.42.47-AM.png" class="kg-image" alt loading="lazy" width="1924" height="650" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-10.42.47-AM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-10.42.47-AM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-10.42.47-AM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-10.42.47-AM.png 1924w" sizes="(min-width: 720px) 720px"></figure><h3 id="database-migrations">Database Migrations</h3><p>Now let&apos;s create a migration and go through the deployment process. First I open up my local supabase studio at <a href="http://localhost:54323/project/default/editor">http://localhost:54323/project/default/editor</a>. Next I&apos;ll manually create a table called &quot;deffs&quot;. I turned off Row Level Security (RLS) for now. We&apos;ll get to that in a later article.</p><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-12.04.12-PM.png" class="kg-image" alt loading="lazy" width="1350" height="1540" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-12.04.12-PM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-12.04.12-PM.png 1000w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-12.04.12-PM.png 1350w" sizes="(min-width: 720px) 720px"><figcaption>Creating a table in supabase locally</figcaption></figure><p>Next, let&apos;s create a migration for the new table:</p><p><code>npx supabase db diff --use-migra -f initial_deffs_table</code> </p><p>This will create a file in your <code>supabase/migrations</code> folder that has the following contents:</p><pre><code class="language-sql">create table &quot;public&quot;.&quot;deffs&quot; (
    &quot;id&quot; bigint generated by default as identity not null,
    &quot;created_at&quot; timestamp with time zone default now(),
    &quot;name&quot; text not null
);

alter table &quot;public&quot;.&quot;deffs&quot; enable row level security;

CREATE UNIQUE INDEX deffs_pkey ON public.deffs USING btree (id);

alter table &quot;public&quot;.&quot;deffs&quot; add constraint &quot;deffs_pkey&quot; PRIMARY KEY using index &quot;deffs_pkey&quot;;
</code></pre><p>We then want to keep track of the types (for importing purposes) in a <code>supabase.schema.ts</code> file. You can generate that by running:</p><p><code>npx supabase gen types typescript --local &gt; src/supabase.schema.ts</code></p><p>Next, we want to create the Github CI deploy scripts necessary to run migrations in our staging and prod environments.</p><p>The first staging script will ensure that our <code>supabase.schema.ts</code> file is always in sync with our migrations. This ensure that our types used in our code stay in sync with the database state. Create a file in <code>.github/workflows/ci.yaml</code> with the following info:</p><pre><code class="language-yaml"># .github/workflows/ci.yaml
name: CI

on:
  pull_request:
  workflow_dispatch:

jobs:
  test:
    runs-on: ubuntu-22.04
    steps:
      - uses: actions/checkout@v3

      - uses: supabase/setup-cli@v1
        with:
          version: 1.61.1

      - name: Start Supabase local development setup
        run: supabase start

      - name: Verify generated types are up-to-date
        run: |
          supabase gen types typescript --local &gt; src/supabase.schema.ts
          if [ &quot;$(git diff --ignore-space-at-eol types.ts | wc -l)&quot; -gt &quot;0&quot; ]; then
            echo &quot;Detected uncommitted changes after build. See status below:&quot;
            git diff
            exit 1
          fi
</code></pre><p>Next create our staging deployment workflow in <code>.github/workflows/staging.yaml</code>:</p><pre><code class="language-yaml"># .github/workflows/staging.yaml
name: Deploy Migrations to Staging

on:
  push:
    branches:
      - staging
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-22.04

    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      SUPABASE_DB_PASSWORD: ${{ secrets.STAGING_DB_PASSWORD }}
      STAGING_PROJECT_ID: STAGING_PROJECT_ID

    steps:
      - uses: actions/checkout@v3

      - uses: supabase/setup-cli@v1
        with:
          version: 1.61.1

      - run: supabase link --project-ref $STAGING_PROJECT_ID
      - run: supabase db push
</code></pre><p>Note, in this file I am hardcoding my <code>STAGING_PROJECT_ID</code> and not using a Github secrets to ensure these fiels stay separate.</p><p>And lastly, the production workflow file <code>.github/workflows/production.yaml</code>:</p><pre><code class="language-yaml"># .github/workflows/production.yaml
name: Deploy Migrations to Production

on:
  push:
    branches:
      - main
  workflow_dispatch:

jobs:
  deploy:
    runs-on: ubuntu-22.04

    env:
      SUPABASE_ACCESS_TOKEN: ${{ secrets.SUPABASE_ACCESS_TOKEN }}
      SUPABASE_DB_PASSWORD: ${{ secrets.PRODUCTION_DB_PASSWORD }}
      PRODUCTION_PROJECT_ID: dfgfggoaaxmrmoggpdmi

    steps:
      - uses: actions/checkout@v3

      - uses: supabase/setup-cli@v1
        with:
          version: latest

      - run: supabase link --project-ref $PRODUCTION_PROJECT_ID
      - run: supabase db push
</code></pre><p>In order to have these properly deploy, we need to set up the github secrets. Go to the github repo &gt; settings &gt; and click secrets &amp; variables in the left sidebar &gt; actions. We&apos;re going to create three repository secrets for the actions. </p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-1.50.41-PM.png" class="kg-image" alt loading="lazy" width="1598" height="622" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-1.50.41-PM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-1.50.41-PM.png 1000w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-1.50.41-PM.png 1598w" sizes="(min-width: 720px) 720px"></figure><p>SUPABASE_ACCESS_TOKEN -&gt; create an access token at <a href="https://app.supabase.com/account/tokens">https://app.supabase.com/account/tokens</a></p><p>PRODUCTION_DB_PASSWORD -&gt; the password you provided when creating the production database in supabase</p><p>STAGING_DB_PASSWORD -&gt; the password you provided when creaitng the staging database in supabase</p><p>After saving these tokens, you should be able to push your commit up to the <code>staging</code> branch and you will see a Github Action run the supabase commit that pushes your migration changes to the staging supabase instance. If your action fails, make sure you have the right password / project id / access token variables all properly typed in.</p><p>As you can see in the pull request (<a href="https://github.com/leerobert/sveltekit-supabase-startup/pull/1">https://github.com/leerobert/sveltekit-supabase-startup/pull/1</a>), both the CI &amp; staging checks pass. </p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-1.58.01-PM.png" class="kg-image" alt loading="lazy" width="1852" height="1360" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-1.58.01-PM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-1.58.01-PM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-1.58.01-PM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-1.58.01-PM.png 1852w" sizes="(min-width: 720px) 720px"></figure><p>After merging the pull request into <code>main</code>, we see the <code>production</code> workflow action run at <a href="https://github.com/leerobert/sveltekit-supabase-startup/actions/runs/5006404642">https://github.com/leerobert/sveltekit-supabase-startup/actions/runs/5006404642</a> which then pushes the migration onto the production database.</p><p>The key to this flow is keeping staging &amp; production identical in workflows so as you work on code locally, you can push to staging for a &quot;sanity&quot; check before a production push.</p><p>Let&apos;s do this for a function next.</p><h3 id="function-deployments">Function deployments</h3><p>Let&apos;s create a basic hello world function:</p><p><code>npx supabase functions new hello_world</code></p><p>This will create a function within <code>supabase/functions</code>. The best way to do edits on functions is by opening up the <code>supabase</code> functions within a separate VSCode window and modifying the <code>.vscode</code> settings to handle deno functions. Otherwise, Sveltekit and Deno don&apos;t really get along. </p><p>You can open up VSCode manually within that folder or run <code>cd supabase &#xA0;&amp;&amp; code .</code> if you have <code>code</code> installed. Then create within the supabase folder window a file <code>.vscode/settings.json</code>. Put the following into the file:</p><pre><code class="language-json">{
	&quot;deno.enable&quot;: true,
	&quot;deno.unstable&quot;: true,
}
</code></pre><p>This should make all of your typescript errors go away for the deno code. </p><p>You can now run <code>npx supabase functions serve</code> to see the logging for the function. At the bottom of the <code>hello_world/index.ts</code> file exists a <code>curl</code> command you can run to test the function. You might need to add <code>hello_world</code> to the end of the url like: </p><pre><code class="language-bash">curl -i --location --request POST &apos;http://localhost:54321/functions/v1/hello_world&apos; \
  --header &apos;Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0&apos; \
  --header &apos;Content-Type: application/json&apos; \
  --data &apos;{&quot;name&quot;:&quot;Functions&quot;}&apos;</code></pre><figure class="kg-card kg-image-card kg-card-hascaption"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-4.14.08-PM.png" class="kg-image" alt loading="lazy" width="2000" height="1320" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-4.14.08-PM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-4.14.08-PM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-4.14.08-PM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-4.14.08-PM.png 2342w" sizes="(min-width: 720px) 720px"><figcaption>Running the function</figcaption></figure><p>Ok now let&apos;s add this function to our CI/CD flow. Modify <code>.github/workflows/staging.yaml</code> to add a new line at the end:</p><pre><code class="language-yaml">      - run: supabase functions deploy hello_world --project-ref $STAGING_PROJECT_ID
</code></pre><p>And also add to <code>.github/workflows/production.yaml</code>:</p><pre><code class="language-yaml">      - run: supabase functions deploy hello_world --project-ref $PRODUCTION_PROJECT_ID
</code></pre><p>After creating a PR (<a href="https://github.com/leerobert/sveltekit-supabase-startup/pull/2">https://github.com/leerobert/sveltekit-supabase-startup/pull/2</a>), you should see the function being deployed in the github action.</p><figure class="kg-card kg-image-card"><img src="https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-4.19.20-PM.png" class="kg-image" alt loading="lazy" width="2000" height="282" srcset="https://robertlee.co/content/images/size/w600/2023/05/Screen-Shot-2023-05-17-at-4.19.20-PM.png 600w, https://robertlee.co/content/images/size/w1000/2023/05/Screen-Shot-2023-05-17-at-4.19.20-PM.png 1000w, https://robertlee.co/content/images/size/w1600/2023/05/Screen-Shot-2023-05-17-at-4.19.20-PM.png 1600w, https://robertlee.co/content/images/2023/05/Screen-Shot-2023-05-17-at-4.19.20-PM.png 2142w" sizes="(min-width: 720px) 720px"></figure><p>Merging into main will also deploy the function to your production environment as well. You will find the edge function living within the functions tab of the supabase studio. </p><h2 id="next-steps">Next steps</h2><p>You have a full CI/CD process of being able to introduce code form a development environment (across multiple coders) into a PR which then can be merged into a staging environment for testing. The staging environment has the exact same setup as production so merges from staging to prod should be seemless. Database migrations &amp; function deployments are all handled for code changes to the functions as well. Devops is a minimum.</p><p>The next article will discuss how to do authentication and multi-tenancy within supabase. Stay tuned.</p>]]></content:encoded></item><item><title><![CDATA[Coming soon]]></title><description><![CDATA[<p>This is Robert Lee, a brand new site by Robert Lee that&apos;s just getting started. Things will be up and running here shortly, but you can <a href="#/portal/">subscribe</a> in the meantime if you&apos;d like to stay up to date and receive emails when new content is published!</p>]]></description><link>https://robertlee.co/coming-soon/</link><guid isPermaLink="false">631630ff2fd12315d12a3912</guid><category><![CDATA[News]]></category><dc:creator><![CDATA[Robert Lee]]></dc:creator><pubDate>Mon, 05 Sep 2022 17:25:19 GMT</pubDate><media:content url="https://static.ghost.org/v4.0.0/images/feature-image.jpg" medium="image"/><content:encoded><![CDATA[<img src="https://static.ghost.org/v4.0.0/images/feature-image.jpg" alt="Coming soon"><p>This is Robert Lee, a brand new site by Robert Lee that&apos;s just getting started. Things will be up and running here shortly, but you can <a href="#/portal/">subscribe</a> in the meantime if you&apos;d like to stay up to date and receive emails when new content is published!</p>]]></content:encoded></item></channel></rss>