Building a Realtime App with Remix and Supabase

Rate this content

Supabase and Remix make building fullstack apps easy. In this workshop, we are going to learn how to use Supabase to implement authentication and authorization into a realtime Remix application. Join Jon Meyers as he steps through building this app from scratch and demonstrating how you can harness the power of relational databases!

156 min
23 Nov, 2022

AI Generated Video Summary

Today's Workshop focused on building a real-time chat application with Superbase and Remix. The workshop covered various topics such as creating Superbase tables and data, fetching data from Superbase, enabling row-level security, adding Superbase authentication with GitHub, and troubleshooting authentication and data access. The workshop also touched on mutating data in Superbase, implementing real-time subscriptions, and deploying the application. Additional resources and courses were mentioned for further learning. Overall, the Workshop provided a comprehensive overview of building a real-time chat application using Superbase and Remix.

1. Introduction to Real-time Chat Application

Short description:

Hello, everyone. Today, we are going to be building a real-time chat application with Superbase and Remix. We'll cover hosting a database, authentication, and authorization.

Hello, everyone. My name is John Myers. I am a developer advocate from Superbase. And today, we are going to be building a real-time chat application. And it's going to be all have authentication and client side stuff going on and service side stuff going on. And, yes, it's going to be a lot of fun.

So, yeah, if you haven't joined yet in the chat to the Zoom, Lira has posted a link to the Discord. If you join that, you can jump into the Discord for this building a real-time app with Remix and Superbase channel. And that's what we can use to kind of chat about things throughout the workshop. And so, I might share my screen. And I guess we'll just jump into it, because we have way too much to cover in a not very long amount of time. So, we'll see how much we actually get through. Hopefully we'll get through all of it. If not, this repo will be available ongoing after the workshop. can come back any time and go through this awesome repo that I put together. I've been spending way too much time on this today. I hope you appreciate it. If you join the Discord and scroll up, there will be a link to this repo. So, DijonMasters slash remix WordPress. I recommend you go and open that up, because it will basically have the steps for everything that we're going to go through.

So, firstly, I guess, what are we doing here? So, what are we actually going to build? So, we're building an app with Superbase and Remix. It's a collection of opensource tools that wrap around a Postgres database. Handles hosting file storage, realtime functions, web hooks. There are basically just lots of ways to build applications and also, automate all of those backend things that you want to keep in sync. So, we'll only be like very much scratching the surface during this workshop. And we'll more be looking at how do we click these two pieces together. So, from this list, we'll probably, well, we'll be definitely hosting a database. We'll be doing lots of Orth. So, we'll be doing authentication and authorization. So, we'll be doing authentication using Superbase and we'll be signing in using third party OAuth authentication.

2. Building Superbase and Remix App

Short description:

Using GitHub as an OAuth provider for sign-in. We won't touch file storage but focus on real-time chat. Remix is a web framework that provides server functionality when needed. The workshop is divided into 12 modules covering Superbase and Remix app creation. Caveats: new features may break as Remix changes. Start by creating a Superbase project at or Choose a project name, set a secure database password, and select a region close to your users.

So, using GitHub as an OAuth provider. Just so people don't need to remember another email and password, they can just sign in with their already existing GitHub account. We won't be touching file storage. We will be touching real time, if we get time, hopefully. The whole point of picking, like, a real time chat application is that you get to like actually, like, engage with people, like, from the workshop. So, hopefully, by the end we have real time enabled. But I guess we always have Discord if we want a much more, you know, much more feature rich real time chat application. But, yes, we won't be touching file storage, edge functions, database functions, triggers, or web hooks. So there are lots of things for you to go and learn after this as well. And I'll have some resources at the end for places you can get started. We will also be using Remix. So I'm assuming you've at this point heard of Remix at least, Remix. I've kind of just done a very short, short sentence here because I'm assuming you probably have some experience. But Remix is a web framework that, I've said, gives you sprinkles of server where you need it, which might not make any sense to a lot of people. But basically just like it's a web framework where you can think about building UIs. But, you know, when you hit the point where you need a little bit of server stuff, so you need to do something that you can't trust the client with, like authentication, user names and passwords or database access, things like that that you traditionally can't trust the client to do, Remix gives you those little server bits. Yeah, when you need them.

So, the structure of this workshop. This is all broken up into 12 different modules so they cover creating a Superbase and Remix app from scratch. We'll start with raw commands to build these things up from nothing. As I said before, we'll spend time on authentication, authorization, and in real time and we'll hopefully deploy it by the end of the workshop. We'll see how far we get in two hours. Thumbs to ask questions. I think you can raise your hand. I don't know if I'll see it with the view that I have currently, but feel free to just like unmute your mic and interrupt me. It's fine to interrupt me at any point. Yeah. Don't feel like you can't do that. If that feels like something you don't feel comfortable doing, feel free to post questions in the Discord and I'll try to remember to answer them in between each of the modules. And if forget, again, someone feel free to unmute the mic and say, hey, there's this question in the Discord that you haven't got to.

Caveats. So, this is the first time that I've taught this workshop. I wrote this workshop, like, over the last week, because a lot of the stuff that's in this workshop only existed like a week ago. So, we've been doing a lot of work at Superbase to make the experience building an app with Remix and Superbase really nice and so, well, hopefully, hopefully we've made it very nice. So, yeah, there's a lot of new stuff in here that is very fun to learn, because it's, you know, new, fancy things, but is likely to break as Remix changes and, you know, they're moving at breakneck speed implementing new things and tweaking APIs and things like that. So, yeah, it may break as Remix changes, especially if there are any big I don't think there'll be any big changes, but any big immediate changes with the whole Shopify acquisition. It's also, it's not, like, super late right now, it's only 9.15, but it will get later for me, and so my brain may just, you know, slow down a bit. So, be kind. And I spent way too long building- Sure. Excuse me, building this awesome repo. I spent a lot of today building this awesome repo. I separated it all out into modules, and then you can go to, like, each next lesson from each lesson that you're on, and then this beautiful progress bar, it actually fills up with the different lessons. So, you can jump to, like, any lesson, like, let's go to the eleventh lesson, which is a really silly way to do this, where these are all individual images. But not what we are going to be dealing with today. What we're going to be dealing with today is building a Superbase and Remix app. But I encourage you to go and visit this repo and fork it, clone it, do whatever you want. But yeah, go and check out all of the cool little progress bar-y things that are in there. All right, so let's start off by creating a Superbase project. So, Superbase is cool enough to have got the snagged the domain So, if you go to, you will get redirected to Superbase. You'll be asked to sign in, and it will start creating, like, jump straight into creating a project for you. So, that's the fastest way you can get started with Superbase, is just going to Or you can go to and click new project. But that's the boring way. So much cooler. So, yeah, this organization will just be the default organization that you that is the account that you signed in with with GitHub. The name is going to be the project name. So, this one, I'm going to say, oh, maybe we need a maybe we need a cool name for our chat app. But that's beyond me. That's not happening at 9 20 PM. So, we're going to say remixconf europe workshop example. That's a super hip name. And then the database password, you can set this using you know, like one pass or last pass or make sure it's a secure password. But if you don't want to generate it yourself, you can click this generate password button and it will generate a secure password. So, yeah, there are some things you might want to do later, like at super base we give you direct access to the underlying database. So, if you wanted to do something like connect to this with Prisma or you wanted to take, dump your database and then go and host it somewhere else, all of those things will require you to have your database password. So, make sure you copy that and put it somewhere safe and then your region, since we're just building like a fun example, it's probably best for you to choose a region that's like closest to your physical location. But the idea is that you would pick somewhere that is close to the majority of your users.

3. Creating Superbase Table and Data

Short description:

Pick a region close to your users. I chose Oceana. We'll create a table called messages with row level security. We'll talk more about authorization later. We'll enable real-time later. Change the id type to UUID. Set default value for id generation. Change created at to timestamp. Untick is nullable for created at. Add a column for content of type text.

But the idea is that you would pick somewhere that is close to the majority of your users. So, if when you're starting your application, you preemptively know exactly where the majority of your users are going to be, then definitely select that region. I'm going to select Oceana, which is closest to where I am in Australia. And I'm going to stick this on the free plan. So, I'm going to click create new project and this will take us to a screen that says it will take a little bit of time to actually scale up all this infrastructure, so it's setting up my project. But it's got to go and create, you know, like file hosting stuff and realtime stuff and database storage and all of that. And so, we will give it a little bit of time to do that. And I was going to ask in the chat, in the Discord chat, how about we have a quick little vote on whether we do this in JavaScript or TypeScript. Because I feel like, I usually lean towards, like, doing most examples in JavaScript, just because it's more accessible. And, yeah, I want, I don't want there to be an additional hurdle of needing to learn Type stuff on top of what you're learning about Superbase or Remix for the first time. But I feel like the Remix crowd is also quite heavily on the TypeScript side. So, feel free to vote in Discord, and we'll see what the majority is. So far, TypeScript is winning. So, if you want to be kind to me at 920 PM, we've got two TypeScripts. This is totally going to be in TypeScript. All right, I can do it. You're going to need to be slightly kinder and more accepting. There may be a couple of any's scattered throughout. We'll see. We'll see how we go. But, yes, we don't need to make the decision just yet. So, anyone else that wants to weigh in may be able to tip the scales towards JavaScript. We'll see how it goes. All right. So, I've floundered long enough that this has created our project. And it's ready to go. So, we can head on over to the table editor, and we can go and create ourselves a table and some data. So, we don't have any tables at the moment. So, we're gonna create a new table. This one is going to be messages. And by default, row level security is enabled. Row level security, we will talk a little bit more about this later. But, basically, it is how you do authorization or how we encourage that you do authorization in Superbase. It's something that exists in PostgreSQL itself and it's a good way to lock down access to your data at the database layer and enable access as you need it. But, yeah. We can talk more about that soon. Enable real time. So, we are going to be doing some real time stuff. I'm gonna leave this unticked though so we can see how you go about enabling it if you haven't turned it on yet. So, yeah. I'll leave that one unticked. We'll take it later. The columns so the auto generated stuff auto generated columns are id and created at. So, I'm gonna change the type of this of the id just to UUID. Just because I prefer the look of it is literally the only reason. You might have a more technical reason you want to do and then when you choose UUID, if you click these little three dots, we have this function generate V4 function. If you select this as the default value, then this will just do what, you know, an id column should do and it will just generate them for you. So you don't ever need to think about them again. Created at, we're going to this is defaulting to a timestamp. It's also calling a function called now. So, again, this is another function that exists in Postgres that you don't need to worry about. It's just going to tell you what the current date is. When you create a new record, you don't need to tell it when you created it. It's going to take a timestamp from whatever now is at that point and put it in this column. Something that I will do is click this little cog, which gives me some more options. I'm going to untick is nullable. It doesn't make sense for this date column to be null. It's got a default value of null and it's never going to change because it's created at. So, it's one of my pet peeves about superbase. It's not just unticked by default. I feel like this should always be not nullable. Now we can add our own columns. I'm going to add my own column for content. These are going to be just chat messages. So, they're just going to have a content field, which is going to be what you actually see, and we are going to make the type of that text. So, we can use text. Any time we have string data or any kind of just characters. And, yeah, there are lots of different other types. Speaking of types, just glancing over at the chat and seeing that we're still not winning on JavaScript. We're still heavily leaning on the TypeScript side.

4. Creating Superbase Table and Data

Short description:

To create the table, choose the text type for the message column. Set it as non-nullable. After saving, add some example messages. If you get stuck, I'll provide solutions. You can run SQL queries directly in the dashboard. We'll insert two messages as an example. If you're comfortable with SQL, you have direct access to the database. After running the queries, check the table editor to see the messages. That's it for creating a Superbase project. Next, we'll create a Remix application using the 'mpx create remix' command.

Oh, we've got another JavaScript. All right. Let's see if we can do the scales. Maybe I can vote as well. Anyway, because you love types so much, we have all these different types that you can set your columns to. I'm going to choose text. And, obviously, I don't want a default value for this. This is the message that the user's actually going to write. But I don't want that to be nullable, either. It doesn't make sense for us to have a message that doesn't have any content. I think that's it for now. So, I'm going to create this table by clicking Save. It's going to create the table. It's going to create those columns. And then we can add some data.

Something while that's creating, I have, like, kind of documented all of these steps in the in this repo. So, you'll see that it says go over to, create a project, and then create a new table. So, this is the name of our table. And these are those three columns that we were just talking about. And then we're going to add some example messages. So, if you do get stuck at any point, I am going to leave a little bit of time at least at the start for people to catch up to that point because it's probably a good thing for you to get it to at least a point where you have a working application. But then due to the time limits, I might, like, you know, kind of just step through the other stuff a lot faster. But I'd like to get everyone up to a point where they have a working application. So, if you're following along with me, that's great. If you're not, I'll probably add five or ten minutes for people to get up to that point. And then just kind of help people, yeah. Until everyone has at least a working application, a super base instance, a remaxed application that's connecting to a super base instance. And then we'll just, you know, jump into the fast, fast forward mode. But I also have these awesome solutions here, which I've got closed in case you want to, like, challenge yourself and actually try it out. But if you do get frustrated, or just you want to see it, you can open up the solution. And I've tried to, like, provide because some of it's in, like, super base database land or in the dashboard of super base. So, I've added some, like, some copy and pasteable snippets. So, these ones, like, create table if not exist messages, this is creating a message table. Again, ID, UUID, calling that function, created that content, blah, blah, blah. And then it's just inserting in some messages and it's setting row level security to be on. Which is just what we did in the dashboard. So, yeah. If you haven't used super base before and you're looking where you can actually, like, run those, if you have a look at your dashboard and come across to SQL editor, this gives you just basically unrestricted access to your super base database, your Postgres database. So, if we were to, I guess, we could do part of this here. We could insert these two messages. So, we are inserting into messages the content of hello and does this work. So, we're just creating two new rows. One says hello and one says does this work. And then if I click run down here, it's going to execute this against my database. Don't be worried and think that this is all going to be like SQL and we're gonna learn a whole bunch of that. Not the case. I just want to show you that, like, if you are comfortable with SQL, you have direct access to the underlying database. So, you can do most things either from writing raw SQL or you can use the nice UI in this dashboard. So, we've run this. Now. And it says success, no rows returned, which means everything went well. So, if I come back to the table editor and click on messages, and give it a sec to load, we'll see that we have those two messages there. So, we can run raw SQL or we can want to insert a new row. And the content is going to be from the dashboard. Save. And then we'll see that row in there. So, there are different, yeah. Different ways to do the same thing. So, I think that's it for creating a super based project. Yeah. Yeah, yeah.

So, hopefully, yeah, if you can follow along while I'm doing it, then do that. But I will leave a little bit of time after we create a remix application and, like, connect up the pieces for you to catch up to that point. But yeah, let's have a look at the next lesson or the next module. So, the next one is going to be creating a remix application. So, you're probably familiar with this command. You can do mpx create remix and that's going to use the create remix package to create a new remix application. And then, the name of that application here is RemixConf example. So, I'm going to copy this. I'm going to head over to my terminal.

5. Creating Basic App and Selecting Hosting Platform

Short description:

We'll start by creating a basic app from scratch. You can use the Superfly stack for a premade solution. We'll host the app on Vercel for quick deployment. Feel free to choose a different hosting platform if you prefer.

I'm going to say mpx-create-remix and RemixConf Europe example, just because I just realized that the other one is probably called RemixConf example. All right. Where did we land on this? We have two Java scripts. And we have two TypeScripts. Oh, no. We got three TypeScripts. Damn it. I'm joking. I love TypeScript.

All right. So, what kind of app do we want to create? I'm going to say just the basics. If you do want to use I forget your first name, but if you want to use someone in the chat's awesome premade stack, then you can use the Superfly stack, which is an awesome stack that's been built for building a Superbase app that's deployed to fly. You can get up and running quickly with that. We're going to go just the basics. We're going to start from scratch.

Where do we want to host this? Again, we could take a vote. I don't know. It's not really worth it. You can deploy it wherever you want. I'm just going to select Vercel because it will make the end of this workshop very fast to deploy it up and get something working that we can all interact with. If you have a preference, feel free to select a different one. You need to follow different deployment steps, which I think are auto generated into the readme when you create the Remix project. If you're comfortable with that, do that. If you're not comfortable with that, go with Vercel because that's what I'm doing here.

6. Installing Superbase and Creating Superbase Client

Short description:

We're going to install the Superbase library and create a Superbase client util function. The Superbase URL and anon key are added to the .env file. We'll create a new file called superbase.ts in the utils folder to house the Superbase client util function.

TypeScript or JavaScript. The final question. No, we're definitely going Typescript. They've won. And we're going to say that we do want to run Npm install. So this is going to create a Remix application. And then, yeah, we'll be able to there is an optional step here. If you want to configure Tailwind. I don't think I'm going to just because we won't have time to probably get to styling. So, I probably won't step through this, but maybe I'll add an additional module that adds some styling. And I'll just use Tailwind because it seems to be fairly industry standard at the moment, I guess, like TypeScript. So, yeah, feel free to step through. I've just linked to the documentation for installing Tailwind from the Tailwind docs, so feel free to set that up if that's something you would like. So, this is still going. Maybe we should, I guess, we can't configure Tailwind until it's finished. Maybe we'll just start talking about what we're going to do next. So, next we're going to query some Superbase startup from Remix. We're going to start off by installing the superbase library. This is what we're going to use to connect to our Superbase instance, and we have an application up and running. So, we can do that properly now. So, this is called RemixConf Europe Example. I'm going to open it up in VS Code and make this full screen and I assume that you have, you know, seen a Remix application before, but I can take you through a quick high-level runthrough of what's been created for us. So, the app directory is probably where you'll spend the majority of time. In there you've got routes, and then index.tsx is the route that gets loaded when we visit the home URL or the landing page. So, just slash in our Remix application. So, if we want to, actually, I might do this here and say npm run dev which is how we run a development environment for Remix. And then I'm going to move these windows around so it doesn't drive us insane swiping more times than we need to. So, local hosts, welcome to Remix. And then a link to a bunch of the docs. So, if we have a look, that is exactly what is in our index.tsx file. Welcome to Remix and a collection of links. And so, yeah, this is anything that you put in the routes folder will be a route in your application and all of that is route, is wrapped by this root.tsx. So, yeah. Yeah. We'll go more into that in a second. But let's start with installing our – this one. MPM install at super base slash super base JS. I'm going to copy this one. I'm going to run it down here and install the super base JS library. I then need to add a dot EMV file and the super base URL are nonkey. We need to make sure that's at the root level of our application. I like to just collapse all these folders and click outside of here to make sure I'm on the outermost point and then create my dot EMV. You'll see that's automatically ignored by our git ignore file somewhere in there. So we don't need to worry about these values getting committed to GitHub. But in here we need a Superbase URL and a Superbase nonkey and you can find these. If you have the repo open, you can click on this handy, dandy link right here which is going to take you to this thing where you select a project. I have way too many projects. And so, oh, but I created it in my DJONmasters one, so there it is, easy. You select the project and then it will take you directly to this page that has your URL and your anon key. If you do want to find that outside of clicking that link, go to settings, and then API, and then we have our URL and anon key. I'm going to copy our URL from here, paste it over here, and then I'm going to copy my public anon key. And so, this key is actually fine to be exposed. Like, technically we could put the strings of both of these directly in our code if we wanted to. Because we have road level security enabled, it means that this anon key can't do anything, well, everything is shut down by default. Because we've turned on RLS, which just denies all actions by default. And then, if you want to enable those actions, you need to write a policy. We'll look more at that in a second. But it's good to know that it doesn't matter if this key is exposed. This is why it's a public key. But, yeah, it tends to make people a little bit uncomfortable when they're first using SuperBase because they're used to not being allowed to do that. But it is safe if you have row-level security enabled. Cool. So, I've got my SuperBase URL and my non-key in my.env file. So, I can probably just close those now. And we've installed our SuperBase JS library. So, now we want to create a SuperBase JS client. and I've somehow got rid of my RemixConf repo. And we're up to querying data from Remix. So, we've installed our SuperBase JS library, a.env file, so then we need to create a SuperBase client util function. So, we're gonna create a new file, utils slash superbase.ts, and we're gonna put this in there.

7. Generating TypeScript Types for SuperBase Client

Short description:

To generate TypeScript types for the SuperBase client, install the SuperBase CLI and run 'SuperBase gen types TypeScript --project ID' command. This will generate a 'dbtypes.ts' file containing the database information. Import the database from the generated file and pass it to the create client function. Now, when you create a client, it will have all the types of your database. You can query tables and select fields using dot notation. The returned data will have the specified types. Using these tools and TypeScript provides benefits even if you're not comfortable with TypeScript.

So, create this at the root most level again. So, utils slash superbase.ts. And we're going to put this in here. And since we are doing TypeScript, we should probably do this properly, which means I should probably... Oh no, I did add a step. Look at that. What am I doing there? Optional, add types I did add a step. All right, since we're going TypeScript down the TypeScript path, SuperBase has recently done a whole bunch of work to add TypeScript support to the SuperBase client itself. So, we can actually generate those types using the SuperBase CLI, which is so cool. It just makes like working with TypeScript so much easier. Like, it's just, it makes a lot more sense. So, you might not have the SuperBase CLI installed. So, if you go to this link, it'll show you how to install it. I think you can also use MPX and just say, MPX SuperBase generate types. But, yeah, maybe just install the tool using brew or whatever for your system. And then, once you have that installed in your terminal, you can run SuperBase gen types TypeScript and then dash dash project ID, putting your project ID. And then, this is just like dumping that output into a new file called dbtypes.ts. So, if I run that here, I just need to get my project ref, to actually just copied into my.amv file, because it's this part of your URL. So, after the HTTPS colon slash slash, but So, I'm gonna copy that one and I'm going to paste it in here. And then when I run that, it will look like nothing's happened. But if we now open up our dbtypes file that we just generated, we will see that we have basically, like taken all of the database information and written it to this ts file. So, our list of tables. So, we've got a messages table and then that's got rows in it that have these fields. And yeah, it's a really cool way to have all of your types generated for you. Just keep in mind that as you change the structure of your database, you'll need to rerun this. So yeah, anytime you make changes, like you create a new table, not adding data to your database, but changing the structure of your database or adding Postgres functions or things like that, you'll need to rerun this type generation. Yeah, this command. All right. But then we can do this cool thing where we can import our database from that file that we just created. And then when we're calling create client, we can use these little alligator brackets and pass it in our database. And now when we create a client, you'll see that it has all of that information. So we've given it all of the types of our database. So we've got messages and blah, blah, blah, blah, blah. And so now, well, I guess if we were to say like, just to prove typing stuff's working if we said that we wanted to create a new client, that was all of this stuff. And then we wanted to see like what we could do. If we do dot, we'll see, obviously, all of this stuff which we always had. But if we do dot from, we can actually see the different tables that we're actually able to query. So if we wanted to query messages and we wanted to select all fields, then if we were to say, I don't know if it's gonna let me do this, but let's say data equals, oh wait, this stuff. It's not gonna let me do that. Const get data equals async function. And then we put this in here. Now, if we highlight over data, you'll see that data has to be one of these messages with an ID created at content. So it's an array of all of those things, or we have null. So we get all of this typing for free because we have pasted in this database with all those generated types. So if you're one of the people that said do it in JavaScript, I'm sorry that we just had to go through all of that. But I think this is one of the benefits of having this tooling that exists in Superbase and then obviously all of this, you know, Remix doing all this awesome work to encourage people to use TypeScript and make it easy to use TypeScript and make all their examples in TypeScript. It makes it much easier to kinda just wire up these things, even if you're not that comfortable with TypeScript, and then you get all the benefits of TypeScript with someone else doing some of the hard lifting. So.

8. Fetching Data from Superbase

Short description:

We have created a utility file called superbase.ts in the utils folder. It imports createClient from superbase.js and our database, and exports a client for querying data. We can check the auto-generated docs in the Superbase dashboard for more information on what we can do with our Superbase instance. We can query data from the messages table using the select function, and the documentation provides JavaScript snippets for various operations. To fetch data in a Remix application, we export a loader function that runs server-side and fetches the data. We can fetch data from Superbase using the from function, and select the columns we need. We can handle errors and return the fetched data. By using the useLoaderData hook, we can access the fetched data in our component. We can render the data using JSON.stringify to display it in a formatted way. However, we may encounter an issue of getting an empty array as a response, which is due to the absence of a row-level security policy.

All right, getting rid of all of that. So we have this utility file. So I created a new folder called utils and a superbase.ts file. And in there, I'm importing createClient from superbase.js, importing our database, and then exporting out a client that we can use throughout our application. So, we now have a client that we can go and query data with.

And so let's query some data. I've got a link here to say, check out the auto-generated docs in your super base projects dashboard. So if you do go to, like, if you're wondering what you can actually do in your superbase instance from this dashboard here, you can go to API Docs. And this is, yeah, a super, super cool thing. If you're like new to superbase, this is auto-generated documentation that is changing as your database changes. It's being updated as your database is being updated. So like, for example, we created messages a second ago, like this didn't exist in our database, but we created that table and now it's generated documentation to say, this is how you interface with your messages table. So we have like JavaScript snippets that you can use with superbase.JS. So you can await superbase.from messages and just select the ID. It shows you how to insert rows, shows you how to filter things, how to insert new rows, how to update rows, delete rows, how to do real time subscriptions. So yeah, this is an awesome resource for when you're first learning how to use superbase, all of this documentation is generated for you. I mean, if we have a look, the introduction has just gone through, like this is what your API URL is, this is how you install superbase.js. Like these are the steps we just went through. Authentication, which we'll look at soon, just shows you how you create a client. That's not authentication. It's all lies. User management has how you actually sign up users, sign in with passwords. Awesome resource, go there if you're stuck or if you wanted to see what you can actually do, because yeah, it will like morph as your database morphs and give you the little useful snippets to do the things that you can do that are related to your database.

So we want to query some data from this messages table. So if we have a look at, not this, on the messages where we were before, if we want to select all rows, read all rows from our messages table, we can just copy this here and head over to our application. And then in our app routes index.tsx file. So this is our route route, or the route that's loaded when we navigate to just slash. So this page here, if our project was running, so npm run dev. So this page here. So how do we get data into our component in a remix application? I'm assuming you've all got some, maybe you don't, if you don't have any experience with that, the way that you do that in remix is by exporting out a loader. So a loader function, which we can say export const loader equals an async function, and then in here, this is where we can fetch data, and whatever we return from here, we can access in our component. And so this loader is a special function, it has to be called a loader, it has to be exported from the component, but this is a special function that will run server side. So this is that server side magic dust that I was talking about before. Remix gives you the ability to just kind of like, you know, define a React component here, which is all about, you know, UI front end stuff, but then when you wanna fetch data, it gives you a nice little server side method for fetching data. And if you're familiar with Next.js, then this would be similar to get server side props. Or, I guess, yeah, some of the ASYNC server component and stuff that they've just launched, but this loader will run on the server and then you can access that data in your component. So here is where we would want to fetch some data from super base. So I don't know why these are let. I'm gonna make those const. And yeah, I might update the generator that generates a whole bunch of let statements. Although at the start of Remix, I'm pretty sure Ryan was very into using let instead of const, but he seems to have lost that battle. So anyway, data is gonna be our messages and then we have an error and we haven't actually imported super base. So we want to import that from our utils slash super base file that we created a moment ago. We're saying.from, so this is the table that we want to select them from, which is messages and and star means all columns, but you can also just omit that. And this will also select all columns. If you only cared about like the content, for example, then you can say, just select the content column, which maybe we'll do later. But for now, we are just going to get everything so we can see it. So now we would have messages and an error. I guess we should do error handling again. We're building a Remix app, let's do it properly. We could actually, we can just throw an error and then it will... Nah, we don't need to do error handling. We can just get our messages and then we can return our messages and we can import a JSON helper here, which comes in from at Remix Run slash node. We don't actually need this at the moment because this is just a JSON object, but we'll need it later. So we may as well just bring it in now, but this is just a helper that's provided to create a response out of a JSON object. So we have our messages, they're being returned from our loader and then whatever we return from our loader we can access in our component by calling useLoaderData. So here we could say const messages is equal to useLoaderData, which comes in from at Remix Run slash react. And then that is going to give us those messages, which we can then print out. So I guess rather than rendering any of this stuff, we can just render a pre tag that's called JSON.stringify and passes in our messages and then the value null and two. If you haven't seen this before, this basically just allows us to like pretty print a JSON object onto the screen. And so, yeah, rather than just being like a dump of text, it'll be nicely formatted so we can see the different fields. Which I guess is easier for me to show you rather than tell you. And if I refresh this page, you'll see an empty object there, or an empty array. So, why are we getting an empty array? Can anyone, anyone think of a reason why we'd be getting an empty array? Because if we open up the network here and refresh, it looks like when we go off to... Oh yeah, I guess we can't see it here. But it's a successful response. We're not getting an error, but we are getting no results when in our database we have a few results. We have three results. I'm gonna spoil the surprise for you and say it's because we haven't written a row-level security policy yet. So we haven't really talked about row-level security.

9. Enabling Row-level Security and Loading Data

Short description:

Row-level security, or RLS, is a way to do authorization in Postgres databases. Enabling RLS denies all access to a table, and specific actions need to be enabled through policies. By writing access policies in the database itself, data leakage is minimized. We want to enable read access for all users by creating a policy that allows the select action. We use an expression that evaluates to true to enable read access, without enabling other actions. The SQL generated behind the scenes shows how the policy is created. Once the policy is saved, data is loaded into the Remix application from Superbase using SuperbaseJS.

We've started talking about it a little bit, but not properly. So row-level security, or RLS, is a way to do authorization in Postgres databases. And when you enable row-level security, it will just deny all access to that table. And then the only way that you can enable access, so basically like every insert, update, delete, or select statement, will just get denied. So you won't be able to select, insert, update, or delete any rows in that table.

And then if you want to enable that, you need to write a policy to enable that specific action. So this is a great way to write those access policies in the database itself, which means that it's much harder for data to actually leak out of your database. Like you can't just write some bad API logic that exposes a whole bunch of data that shouldn't be exposed. You can't, like, leave off a where clause somewhere in your application and get access to sensitive information because all of that, all of those rules are written in the database itself.

So, we don't have any active row-level security policies. So all of our actions are being denied. So here we're trying to select all of the rows from that messages table, and we're just not getting anything back because every single row is saying, no, you can't select this, no, you can't select this, no, you can't select this. So, we want to write a policy to say, well, we want to be able to actually select those rows.

So there are some templates that you can use to get started, which give you access to some common use cases. But for this, we're gonna say, full customization, we need to give our policy a name. So what we're actually gonna do is just enable read access for all, yeah, let's just say for all users. So we need to tell it which actions we want to enable. So either select, insert, update, delete, or all. So this is gonna be a select action. So if you're not familiar with SQL or that kind of language or syntax around these kinds of database terms, selecting is just reading data. Inserting is adding new data. So inserting it into the table. Updating is updating a particular value or a particular row or collection of rows. And then delete is deleting it from the database. All will enable all of these actions, but it's usually best practice when you're building this kind of authorization system to enable as little as possible. So just like when you hit the problem where you realize, oh, I need to enable that action to be able to build this function. That's when you would want to write a rule to just enable that one action. So we're going to enable select. We're going to leave target roles for now. And then we have this using expression. And so this is just an expression that evaluates to either true or false. So we could say like, one plus one equals three. So one plus one is never going to equal three, so this is also going to deny all attempts to select. If we were to say one plus one equals two, then this would enable all actions. And so, yeah, any kind of expression that evaluates to either true or false, which we'll look at writing like a slightly more complex one soon, but right now we just want to enable read access for all users. And so, we just want to return true, because true means sure. They can read this data, but it doesn't enable anything else. So it doesn't allow them to insert or update or delete or distract anything. It just allows them to now select. So it's basically just opening up read access to this table. And then, when you say create policy, this will show you the SQL that's actually generated behind the scenes. So this is something that's like pretty consistent with Superbase, is that we try to not just abstract away everything and be like, you don't need to worry about the SQL. We'll do everything. You just click some buttons and we'll make it work. You can still do that, that's fine. But we like to look for those opportunities where we can help educate you about what you're actually doing. So you don't need to use Superbase in the future if you don't want to. Like all we're doing here, you don't need to use Superbase to enable row-level security, that's just a Postgres thing. And so, this is showing you that like all we're doing is taking those fields that you just entered values for and just turning it into SQL syntax. So create policy, this is the name that we gave it, on public, so this is just a schema for all the tables that we'll create in Superbase. They're all on the public schema. And then, there are other schemas that contain other tables for like auth and things like that. But we don't need to worry about schemas too much. That's more advanced than we need to go today. But messages is the name of the table that we wanted to enable select on. And then, it's just saying as permissive. So essentially allow this action for select. So that's the action that we told it we wanted to enable. And we wanna enable it to public. So this is just everyone using true. So, yeah. Enable read access for everyone. And then, if we click Save Policy, there's nothing magical going on in the background. Literally, all we're doing is just running that SQL against your SQL database. And so, now we have a row level security policy that is enabling read access. So now, if we go back to our application and refresh, boom, we have data being loaded into our remixed application from Superbase, using SuperbaseJS. And so, I might pause it there, maybe just for a couple of minutes, while people get up to this point. Sorry, I've just got controls and weird things in the way. Yeah, so we're up to the end of this module, and this is a good point to get everyone up to. Just so that we have, yeah, everyone with a Superbase instance connected to a remix application.

10. Enabling Row-level Security and Taking a Break

Short description:

You've enabled row-level security, added a messages table, and a policy to read from it. We're halfway through, so take a short break, ask questions, and get your application up to this point. Let me know if you need help.

You've enabled row-level security, you've added a messages table, you've added a policy to be able to read from it. And then, yeah. And then we can continue on, and I might just like, yeah. We're already halfway through, so I might need to just quickly go through the rest, but this is a good point to get up to. So I might leave it for like, let's say till, well, yeah, let's leave it for seven minutes. So go grab a sip of water, go to the toilet if you need to, get your application up to this point if that's something you wanna do, and definitely ask me some questions, if you have anything. So yeah, like related to this, you can just ask me like, how my day's going, or like what the weather is like in Melbourne, but even better, ask me about SuperBass or about Remix, or if you get stuck in any of these steps up to this point, then let me know, and I can help get you unstuck now before rushing even further ahead.

11. SuperBass Memes and TypeScript Support

Short description:

If you're not following SuperBass on Twitter, then you are missing out on all kinds of fantastic memes. RLS protects data at the row level in the database. Superbase now supports TypeScript, making me happier to write it every day.

And just looking through the comments, I've got some SuperBass memes posted about, I can actually bring this over here, so you can see, even though you're probably in the Discord, if you don't follow the SuperBass YouTube account, then you are making a mistake with your life. Sorry, not YouTube account, Twitter account. I mean, follow the YouTube as well, but if you're not following SuperBass on Twitter, then you are missing out on all kinds of fantastic memes, like this one. And so this is from the SuperBass account, when the hacker comes for your data, but you're using RLS. This is actually, yeah, I think one from like today or a couple of days, yeah, today, we can play it quickly. Well, we've got audio. You can't do it. You can't do it. You got nothing, because RLS is protecting that data at the row level in the database. So you're just not gonna get through and we've got, I forget his name, but saying noise. This guy is an amazing poet that my daughter who is three years old really, really enjoys. And so I have watched an incredibly large amount of this guy's poems. He's great. I love that now super based gen types, TypeScript project ID can work without setting up local dev Superbase. So yeah, there's been a lot of work that's gone into making this work yet in a very, very nice way. And it's making me slightly happier to write TypeScript every day. So I'm sure that comes across. I actually do really like TypeScript. I feel like there's just a running joke. If you've watched any of my live streams or watched any of my videos, I feel like I have to remain consistent about making jokes about TypeScript, but actually, I really do like TypeScript more and more every day, especially when tooling supports it like Superbase is doing now. If you want typing on your React const messages.

12. Type-Safe Loader Function and Fetching Data

Short description:

In order to make everything type safe, we need to give a type to use loaded data. We can use the type of loader to ensure messages have the correct types. Remix provides a new loader/action signature that allows us to specify the type of the return value. By using this signature for the loader function, we can pass the correct types through the entire application. This ensures that our messages object only contains messages with the specified properties. With these changes in place, we can now fetch and display data from Superbase with all the benefits of TypeScript.

Oh yeah, good call. Yes, yes, very good call. In order to take, to make, ah, this was something I ran into literally today when I was putting this example together because I always go with, I'll just show you what I'm talking about. I always go with loader function. And I wonder whether that's, oops, not that one. I wonder whether that is the problem. Because yeah, we select our messages, but then we made a big deal about making everything type safe, but then I've just ignored that entirely here and now messages is just implicitly any. So we can give a type to use loaded data. Very excellent call out. And I think maybe if you say type of loader here, this doesn't give us, yeah, this is still any, but I wonder if we say, this is loader args L3. Is that what we were just doing? Loader, async, loader args. And then, oops, parentheses, my bad. Wait, async, blah, so yeah, it should be loader args here, right? Doesn't like it. Input, the return type of async function method, oh yeah, because it's promise. Promise? I promise. I'll give you loader args. And now, oh God. It's not gonna let me do this either, is it? This is so bad. All right, we've still got a couple of minutes if people want to get up to this point. If everyone's up to this point, maybe if you are up to this point, add a message to the discord just saying move on and I will move on. But otherwise I'll give it a couple more minutes. And yeah, if anyone's stuck at any point or something doesn't make sense, please mention something or check a message in the discord and I can help you out. In the meantime, I'm gonna try and sort out this TypeScript thing that I definitely should have already sorted out. So constantly a type of loader. So this part is right. And then in order to make type of loader work, Remix provides a new loader slash action signature. So if I say all of this for a loader and we move this in here and get rid of this second one. Oh yeah, right. And we need one of these ones. Ah, right, okay. I didn't have these. Oh, I need it to be inside. Right, so this object which has a request and params and all of the other stuff, our loader args and then messages works. That's so cool. That is amazing. Very cool. Thank you so much. Rifflema. That is awesome. That is very cool. And thanks to Colin from Zod for giving us that one. Should be in print that, yeah right. You were pointing out exactly what I had done wrong, my bad. Very cool. Okay, so I might move on unless anyone has any quick questions, anything before I continue on at breakneck speed and try and get through whoops, try and get through this example before the end. All right. We're doing it. We're doing it, let's do it. Okay. What is the next step? So, sorry, if anyone was following along with the changes there. So yeah, if you use this signature for the loader function, so if you say export consulate equals async and then destructure the request, I guess we could just do this if we're not using the request and then say that that is loader, args, then all of this will now work where we can say it's a type of loader, which means that our messages get all of that beautiful TypeScripty goodness that's piping all the way through from like super based client. So this is coming from all the way up here in our utils files. So us pulling in this database file that we generated, giving that to createClient, then gives us a like typed super based client. And then in here, we're pulling in that typed client. We then And so this knows that all of the things that exist in super base. So it then knows that this messages object can only be an array of messages that have an ID are created at and a content, or it's gonna be null. So then we can pass that through as our response. And then by saying type of loader here, it's looking at our loader function, and it's saying, well, what is the type of the like return value of this? Once we like resolved that promise and we have the value returned, what is that? And so that is an object that has this messages, adjacent objects that has messages. So then we can pull messages out of there and then messages has those types. That is awesome. So if we go back to our application, this should be the same. If we refresh, cool. We're getting our data. Hopefully everyone's up to that point. If you're not, the video of this will be available. I think available to you immediately, maybe, but then available in a month or so for everyone. But if you, yeah, if you run into anything, feel free to ping me on Twitter. I have a long list of DMs that I haven't got back to but I promise I'll get back to you. Feel free to DM me and ask questions or actually ask questions in this Discord because there'll be a whole bunch of people that also went through this same workshop that will probably be able to jump in and give you help much more quickly than I can.

13. Adding Superbase Auth with GitHub

Short description:

We're adding Superbase Auth, which needs to happen client-side. Real-time and authentication should also be triggered client-side. We'll create an OAuth app with GitHub and configure the authorization callback URL. Then, we'll enable GitHub as a provider in Superbase authentication and provide the client ID and secret. After saving, users can sign in with GitHub. We'll create a login component with handleLogin and handleLogout functions, using Superbase's auth.signin with OAuth to sign in with GitHub.

But I will also make sure that I check back here and give anyone any help that I can. All right, so we're getting our data into our application and so we want to go on with our next step which is the next lesson where we are gonna add Superbase Auth. So Superbase Auth needs to happen client side and then later in this workshop we'll look at wiring up some server stuff. But yeah, anything to do with like Superbase authentication or Superbase, so like signing users in, taking their username and email and password or triggering the OAuth flow with GitHub or whatever, all of that needs to happen client side, it needs to be triggered client side. And then anything realtime also needs to be triggered client side. So any like data fetching stuff we can do that on the server once we wire it up, but yeah, Auth and realtime will need to happen client side. It's an unnecessary distinction to make at this point. So we'll circle back to that. But I'm gonna open up this resource which is a super base docs, which shows you how to login with GitHub. So it shows you how to create and configure an OAuth app. So I'm gonna open this one up and then if we go, I think we drop down here and go to settings and then scroll down to the bottom and go to developer settings and then OAuth apps and then new OAuth app. And then we wanna create a new OAuth app called RemixConf Europe, Europa workshop. We then need a homepage URL. So what is a homepage URL? Well, if we come back to this documentation, it will tell us that a a callback URL looks like this. So it'll have your slash auth slash V1 slash callback. Actually, I think there's even, yeah, anyway. This, we'll copy this and we'll go across to our OAuth application. So this is gonna be our authorization callback URL. So we then need to go and find our projectref. And so we can either go to our.env file or we can just go to our project in Superbase and grab this value. So between or after project, that is our projectref. We can copy it from here. We can go to our project settings and grab it from our URL on our API settings or probably under general, there's a reference ID. And then we also copied this into our.env file down here. So lots of places we can find our Superbase ref, but we wanna copy that one. We wanna put it into our our authorization callback URL. So this is the URL that that GitHub is going to send the access token and refresh token. And like this user is signed in and here's all the things that they have access to or whatever. And so, yeah, we need to provide that at this And then if we copy that same URL and come up to home page URL and just get rid of all of that slash or slash V1 slash callback stuff, this is the home page URL of our application. Cool, and that's all we need. We can then register our application. And then this is going to give us a client ID which we can copy here. And then we need somewhere to put that. So if we go back to our SuperBase project and come across to authentication and then providers, we want to enable GitHub as a provider. So at the moment, email is enabled by default, so people can use a username, an email address and password to create an account with SuperBase. And then we have all of these different third-party providers, which you can use. I mean we could use Zoom and just use our credentials for this chat. But if we want to use GitHub, we drop this down, we say enable GitHub OAuth. And then this is going to ask us for our client ID. So we can paste that in here and our client secret. And then you'll see that we do have that redirect URL there, which we can just copy, which does actually have our... That's what I was half-started thinking about, that I was pretty sure there was some way we could just copy the whole URL inside our dashboard. And so that's here, we can copy that, and it contains our project, ref. So we need a client secret, which I'm not meant to show you on the screen, am I? I do trust you, I probably shouldn't reveal it well, if this is gonna go out to the greater webs, sorry, I'm just clicking generate a new client secret, and then I'm copying that value and then I should be able to share my screen again. And I'm pretty sure it obfuscates it when I paste it into Superbase, let's find out. The best way to find out is to do it live on stream again. So if we go back to our authentication here, I'm pretty sure when we paste this, yes, obfuscated, beautiful. So we've got our client ID and our client secret, and then we click save, and that's it. We can now sign in our users with GitHub, which is pretty awesome. That was something that blew my mind when I first started using Superbase. Yeah, I really just hate either generating a password for whatever application I'm using or remembering a password or reusing the same password, which is what usually happens. And so I love being able to authenticate with GitHub and then be able to have accounts with other things. So what were we doing? We're doing a workshop. So we've created our GitHub OAuth app, and now we just need to create a login component. And so I've actually got this one that I prepared earlier just because this part isn't super fun to watch. So I'm going to again, come out here and say, I want to create a new folder called Components. And in there, I want to create a login.tsx file. And then I'll paste this in here. This is saying that I can't import that from utils. Oh yeah,.ts. That doesn't make sense. Yes, I should update that. That should not have a.ts on the end. But anyway, we're creating a login component. Then we have a handleLogin function and a handleLogout function. So handleLogin function just uses SuperBase, it calls.auth.signin with OAuth. So that's the method that we use to sign in using SuperBase auth. And then we tell it which provider we'd like to use. So here we'd like to use GitHub.

14. Passing Environment Variables to Client Side

Short description:

We need to pass the environment variables from the server side to the client side. Rename Superbase.server.ts to indicate it should never be used on the client. Export the loader function in root.tsx and return the environment variables. Use the loader data to access the environment variables. Create a Superbase client using the environment variables.

But if we wanted to use Google or Apple or Zoom or whatever, there's a whole bunch of providers. But yeah, we're going to use GitHub because we just went through setting up that application. If there's an error, we're console logging it out. And then handleLogout is very similar. We're saying SuperBase.auth.signout. And then if we get an error, we're console logging that out. And then we just have a button to trigger either of those. So we want to either handleLogout when we click the Log Out button or handleLogin when we click Log In. So nice, simple component. We want to import that in our application. So here under routes.index.tsx, rather than... We'll keep our dump of JSON here, but we will also have our login button. And then we'll have our dump of JSON. Cool. And then on our page, we now have a log out and a login button, and we have a semicolon that made its way through over here. And so now, if we were to... Ah, yes, yes, we have a problem here. So if we click on the log in button, and if we have a look at the network request, it doesn't make a network requests, which is what we would expect for it to go off to Superbase. And if we have a look in the console, we'll see this error. So reference error process is not defined. And so I've added that here. So if we try to render this, we'll notice this big error. So check the browser console, and you'll see that process is not defined. And then if we try to render this, you'll see that process is not defined error. So the problem here is that when we created our Superbase client on the server side, so this server side one that we're using here, that's fine, because if we have a look at this utils file, this is using process.env.superbase.url, which is in our.amv file. So that's who base URL environment variable exists. And so does our non-key. But when we're on the client side or when we try and do this in the browser, those environment variables don't exist there because like that's how environment variables work. You declare them for the server. Some frameworks give you like an escape patch to make it available throughout your entire application. So for example, in next JS, you can like prepend the environment variable with next underscore public underscore, and then that will make it available both server and client side. But in Remix, we don't have any concept of that. And so our.env file variables are only available in the server. So if we wanna make it available in our client, we need to do a little bit more wiring. We need to pass that through from our server side to our client side. So the first thing I'm gonna do is because this will not ever work client side, I'm gonna rename this Superbase.server.ts to remind myself that this should never, ever be used on the client. And I believe someone who is very experienced with Remix can correct me here if I'm wrong, but I'm pretty sure this is excluded from the client bundle by Remix by default. So anytime you have.server, it's actually like protecting that from ever ending up in a client bundle and then trying to access that process.env that will not exist. Alright. So, but we still have this login component that doesn't work. And yeah. So we need to pipe in these values from a server, from our loader function. So up in our root.tsx file, we can export our loader function. And we're gonna give it the right signature. Yeah, so that we can use that magic again. We're gonna say this is loader args, and then inside the body of our loader, we are going to return a new object. And we want to return some env variables. So we're gonna say const env is equal to a new object where our Superbase URL is set to that. And our, oh, so close. GitHub copilot nearly had it. A non key. Alright, so on our loader, these environment variables exist. So we can create a new object called env, which has our Superbase URL set to process.env.superbaseURL. And our super base and non-keys set to process.env.superbase.andnonkey. So we're only like allowing these two values to pipe through, we're not just like exposing all of the, all of the dot env, our entire environment that would be a bad idea to expose that to the client. But we're just like basically allowing these two values to be piped through. So we're returning that from our loader. So then we can say const env is equal to use loader data. And again, we can type this by saying type of loader. And now we have this env, which tells us that we have a super base URL and a super base and non-key. It does tell us that they're either a string or undefined. So I'm gonna put a little exclamation mark here to say that, well, we have declared these, so these will always be available. Cause if they aren't actually declared in the environment we're running in, like if we push this up to Vercel and they're not there, we probably do wanna get an error from super base, like trying to create a client and then saying you don't have these values. We want everything to blow up. So if we have a look at our env now, then we have a super base URL, which is a string and a super base a non-key, that's a string. Cool. So now we want to create a super base client to be able to run that login and log out function. So we can say const super base equals create client. And then we can pass this our env dot super base URL and our env dot super base a non-key. So this is like what we're doing in our super base dot server dot TS file.

15. Passing Super Base Client to Login Component

Short description:

We create a super base client in the browser or client-side land. We can use the outlet context to pass the super base client to other components. The outlet context acts as a global context that can be subscribed to in other components. By using the outlet context, we can access the super base client in our login component. We can create a typed super base client by providing the database to the create client function. We also need to create types for the outlet context to ensure type safety. With the super base client available, we can now use the .auth and .signinwithauth functions to handle the login process. We can authorize users through GitHub and store the super base key in local storage. To restrict access to authenticated users, we can modify the policy in the super base database. By setting the policy to the authenticated role, we enable read access only for signed-in users. Refreshing the application will show the list of messages only for signed-in users.

In fact, we could modify this so that we could pass these in. But anyway, we are now creating this client. So now we have a super base client that exists in the browser or in client side land in our component.

So we need to get this into our login component. So this component here, and so one way we can do this is we can use our outlet context and we can give it our super base client. And so now any component from our outlet onwards.

So if we were to have like, hello above this outlet, and then we go to our application and refresh. We'll see that we have hello above. And if we were to move that down below our outlet, then you'll see that it's down the bottom. So this outlet is being rendered on the page and then anything else in this component we can put around it. But yeah, it gives us access to something called outlet context, which is basically like global context, like react context that we can then like, subscribe to those values in other components.

So if we want to use that context value down in our logging component, we can say, rather than using this one which comes in from the server, which we can't use on the client. We want to say const, super base equals use outlet context. And again, this is something that we can type and we probably should pipe through all of our types, since we're using TypeScript.

So back up in our root right here, we're creating a client, but really we should be creating a typed client. So if we come back here, we can import our database like we did here. Well, not here, up in our root, and then we can give our not super base, database to our create client function, and so then this is now a typed super base client, but then we also need to create types for our outlet context, I believe, because in here we do not know that this is a typed super base client because we haven't told this use outlet context what any types are. So back up in our roots, let's create a... Up here, we can create a type for super base outlet context, and this can be equal to an object where we have super base, which can be a super base client, which we can pull in from super base JS. And then we can also give this a type. So we can say that we want it to be a typed one of database. And now we have this super base outlet context. We probably want to export this so we can use it elsewhere because where we really want to use it is in our logging component here. So we want to say super base outlet context. And so we can pull that in from our root and we need another one of those. And now we have a type super base client. So we've got our types pieping their way through like multiple layers of all kinds of amazing stuff. So now when we actually use it, we can see that.auth and.signinwithauth takes a thing and gets a promise back and everything's beautifully typed, which is very nice, but not even the problem we're trying to solve.

What we're trying to solve is that we couldn't use super base on the client side. So when the user clicks a button, we couldn't use that server side super base client cause it was relying on those environment variables that weren't there. But now if we get rid of this, give it a refresh just for good measure and then click login, boom. We are taken through the login process through GitHub.

So here we're gonna say we want to authorize Dijon Mustard and you can see what it's actually getting access to just email addresses read only, but yeah, pretty limited scope. We want to authorize that. And so that's gonna go through and now if I have a look in my application local sites, storage somewhere, storage, storage in my local storage. I now have a super base key maybe. Let's, oh God, how do I get rid of all this stuff? This is the problem with using local host for all of your stuff. I'm gonna say this is my super base key because it has SP in front of it, super base, and it says auth token. So cool. We are signed in, but you wouldn't know that from what we're looking at here because we're still, if we log out and then refresh the page, we're still seeing all of these rows when really we probably don't want people to see rows from our messages if they're not signed in. That's probably something that we want to enforce is that like we can enable read access, but we probably wanna only enable read access for people who have at least logged in. So we can fix that in our super base database by going over to our policy, and this is a little too permissive at the moment where it's just returning true. So we can actually scope this to a role, and so when a user is signed in, their role is set to authenticate it. So we can just say, we wanna set this to the authenticated role and you can do a bunch of other stuff if you want, if you understand what those roles are. But yeah, authenticated is set for anyone who's signed in using super base auth. And we can just keep the expression as true. So it's still gonna enable read access, but only if they are authenticated. So we can say review, so we're altering our policy and just basically setting it instead of public, it's now set to authenticated. So we say, save policy. And now if we refresh our application, hopefully we won't see any results cause we're not signed in, which is good. And now if we log in, we'll go through that auth flow and you'll see there's a little bug there cause we're not actually seeing the results even though we're logged in now. But if we refresh, nope, maybe we've got other bugs. Log in. What does this say? All right, we passed through our environment variables. This is, yeah, this is not right yet. We haven't used. Yeah, that's from auth helper stuff which we'll get to in a second. And we don't need to pass through this session yet. What has happened? Something weird is going on. It sure is, but it's not the something weird that I think is going on. So, oh, wait, of course it is. What am I talking about? This is exactly the weird that... Yeah, okay, all right. I haven't run this workshop before. It's okay, we got there. My brain's working again. So we would expect that this is going to, going to show us a list of all of our messages because we're signed in, and if we were to, well, if we were to get the session on the server side, so up here where we're in our loader, where we're creating this environment object, if we were to create a server side super-base client here, so if we were to say, well, yeah, we can just, all right, let's call this again, and say, const super-base equals this, make sure we're not importing super-base up here, and then we wanna go and get the session for our currently signed in user.

16. Server-side and Client-side Session

Short description:

We're trying to get the session for the currently signed-in user on the server-side and the client-side. We expect the sessions to match since we're using the same environment variables. However, when we refresh the application, we're not getting the expected results. The server is saying that there is no session, but the client is showing the session as null. We're not sure why this is happening and have tried different approaches, including using the auth helpers package for Remix. We're still investigating the issue.

It's okay, we got there. My brain's working again. So we would expect that this is going to, going to show us a list of all of our messages because we're signed in, and if we were to, well, if we were to get the session on the server side, so up here where we're in our loader, where we're creating this environment object, if we were to create a server side super-base client here, so if we were to say, well, yeah, we can just, all right, let's call this again, and say, const super-base equals this, make sure we're not importing super-base up here, and then we wanna go and get the session for our currently signed in user. So we can do that. I actually have a copy and pasteable snippet just here, where we can go and get a session by calling super-base.auth.getSession. So if I put this here, this is not an async function. So we say async here, and now we have a session, so we can pass that through as well to our front end, and then we could get our session out of here, which is just magically, is it magically? Yeah, it is, it gives you a session, that's awesome. All right, so I'm going to console.log our, I'm gonna say this is our server session, and then in our login component, or actually it doesn't really matter where I do it, does it? I'm gonna copy and paste this little snippet down here to kind of make more clear what is going on. So, use effect runs in the client, and then so once we're like client-side, we call use effect, then we're saying super base, which is using this like this client-side super base client, this browser super base client, we're then saying auth.getUser, we could actually just make this the same as our one above and say get session, which gives us a session. And then we can say, this is the client-side understanding of what our session is. All right, so we have, we're just console logging out our service session and our client session. So basically the session that we got when we created a Superbase client server-side in our loader, and then the session that we got when we created a Superbase client in the browser or client-side. And so we would expect that these match because we're using the same environment variables, we're using the same everything. But if we go back to our application and we refresh, see that we're not getting our results. And if we open up the console, our server is saying that we don't actually have a session but our client is saying, what are you talking about? This session is right here and it is also null. Okay, maybe we don't have a session. Login. Refresh. Yes. No, session is null. What is happening here? Our login component. Who is Auth, sign in with OAuth, provider blah. Let's check out our login. So this should be logging in. We log out. And if I just go to storage, local storage, and get rid of anything in here, delete all, refresh, completely fresh everything, and then log in. No. What is happening here? Maybe I will just quickly put this back to get user just in case. And then when we have our user, once we are logging that out, that should work. Log in, refresh, unauthorized. Interesting. Invalid claim, missing sub claim. Hmm. Create client env.superbase url and if we console log out, ow. Env here do we have those environment variables piped through superbase url, superbase and nomkey? Interesting. Hmm. So that's piping through to there. We're creating a super base client within using it say auth.getUser, that's a promise. So we're saying then giving it a user and console logging out that. Hmm. I'm not sure why this is not working. So we're passing through that super base client which is getting a super base client, which has all of that stuff on it. Then we're saying sign in with auth and telling it, we want to sign in with GitHub. OK. Maybe let's try and copy and paste. So that's what you use to create browser client. Is it a different function to create the client on the browser and in the server? It is when we start using the remix auth helpers, but I realized that was a typo from copying and pasting different versions of stuff. But we can just, yeah we can just install the auth helpers and do it properly. I was just trying to demonstrate that it should still, like when we pass it through to the outlet and then get the reference, it should still be on the server. So we're going to go to the outlet and then yeah, see, this is a function that we get from the auth helpers. But let's just jump to the auth helpers and pretend that we had a different session on the client versus the server and we'll see if we run into the same problem when we try and use the auth helpers. So let's go. Next lesson, server-side auth. We're going to install the auth helpers. The reason that this should be creating a different, yeah, the reason that this should be creating a different session on the client and on the server is that by default, Superbase Auth uses local storage. And so it stores your access token in local storage which obviously isn't available server-side. So when we go and call a loader that goes off to the server and runs server-side, and then that local storage value doesn't exist. And so the way that we can provide access to that server-side is with a cookie. And so for a while you needed to roll your own cookie in when you were using Remix and pass that across to the server-side. But obviously you would then need to implement your own refreshing and your own everything. Basically, just all of your own Auth, making it useless using Superbase. And so we've built these Auth helpers that are specific to different frameworks. So we have Auth helpers for Next.js, Remix and SvelteKit. And so yeah, you can basically follow the same or similar pattern in any of those frameworks to be able to get all of that server-side cookie magic wired up for you, so you don't need to think about it. So let's install the Auth helpers package for Remix. So we're going to say, npm install at Superbase slash auth helpers dash Remix. And then we're going to run our development server again. And then over here, we're going to refactor this to use this create server client. We're going to use create server client from the Auth helpers. And we will refactor our export default, rather than just exporting out the client.

17. Creating Server Client and Refactoring Logic

Short description:

We're creating a server client based on the request and response from Remix. This server client handles refreshing logic and token access. We also need to pass the response headers when returning JSON. We refactor the logic to use create server client instead of a regular client. We create a browser client using the create browser client function. We use the outlet context to access the super base client. We use Superbase.server to create a server client in a server environment. We use the outlet context to create a Superbase client client-side. We encountered authorization issues and are investigating the cause.

We're going to export out a function, which takes a request and a response. And this is because we now need to use headers and cookies and things that exist at the time that we're actually making that request. So we need to pass in the request and the response from Remix. And then we can create a server client based on that request and response. So yeah. So now, rather than just using that client directly, we need to call this function and give it a request and a response. So back up in our index, we can't just say import Superbase, we have to say import, create server client. Create server client. Create Superbase server... Let's go client. Cool. So create server client is now a function. So here we need to say const superbase equals calling that function and then we need to pass in a request and a response. So the request we can get from this loader. So Remix parses us a request object, so we can pass that through to when we create our server client, but then we also need a response. We need to create an empty response and then we need to pass that through to where we're creating our super base server client, because that is going to handle all of that, like, refreshing logic, refreshing access tokens and all of that stuff, refreshing our auth session and then attaching new headers so that's also accessible in the client. So yeah, we need to pass a request and a response and then annoyingly, we need to, because this response now has those headers to do with refreshing our token and we might need to send those back to the client to set a new cookie. When we return JSON, we also need to tell it that we would like to set headers to be a response.headers. So I'll add a little bit of space here. So here we're creating an empty response. We're passing that through to this create server client function. We then get a super base client which we can go and make authenticated requests to Superbase to go and get all of our messages. And then when we return our JSON, we just need to make sure that we pass those headers from the response because our Superbase client may have modified them. This is just the annoying bit that you need to remember to do. But it's a much smaller annoying bit than writing all of your own cookie refreshing logic and all of that stuff that used to be a massive problem. So, okay, cool. So now we have a server client. So we probably need to refactor a bit of our logic up here. So rather than using, rather than creating a regular client here in our loader, we want to call our create... I'll just copy and paste it from here. Oop, it's not in here. We go like this. All right, request... So we have our loader, we're getting our request, we're creating a response, we're sending that across to create server client which hasn't been imported yet and it doesn't know how to import it because we didn't give it a name. So input create server client from utils slash Superbase Server and then we need to remember that when we return we need to use the JSON helper. We need to give the bits of data that we actually want to return and then we also need to tell it those response headers and then down here where we're creating a client using Superbase JS this is where we want to import the create browser client. Wondering why I'm not getting auto-complete... maybe something is wrong. Create browser client And then, down here where we are creating a browser client, we want to call that function, give it our database and these environment variables. And because this is the only place we're creating a browser client and then we're sharing it everywhere else through this outlet context we shouldn't actually need to do this. So we might as well make this a simple outlet context. We shouldn't actually need to do that again. So now, any time we want to create a Superbase client in a server environment, so any time we want to create it in a loader or in an action, we want to use this Superbase.server function and then any time we want to use it client side we want to use this outlet context and pull out our super base client. So let's see if that has magically fixed our problems. I'm going to run our Dev server again just in case because this is not going to be a great end to our workshop if it doesn't work. So this is saying unauthorized, which is interesting. So let's log in and refresh. And our client side is still saying that we don't have a user and our server side is saying that we don't have a user, but we are in fact logged in. What has happened? Hmm, so jumping back to when we added client auth, we created this which we copied and pasted, we then passed through our environment variables which we've confirmed are working, passed through superbase to our outlet, which is definitely happening there. And passing through our session, so we have our environment and our session. But that shouldn't matter because up here it's not actually set to anything. We then creating a browser client based on those variables and then we're using and then we're using Outlet-Context inside our logging component from Remix-Run-React. We are getting, we're not actually using the session yet, but we can get out our session. Ah yeah, which we'd need to add up here. Our session is a session. Hmm, very strange. And let's see. Login component, maybe I'll just, I don't know why it would make a difference, but let's quickly refactor this to say we're going to bring in our login component up here and we are going to pass it directly a superbase client. And then in our log in component, we're going to accept a superbase client. And then get rid of that call to this comment, tell this, and our super base can be just a non type super base client. Disgusting. A non type super base client. Disgusting. And then when we click one of these buttons, we're using that untyped super base client to do these things. We're signing in with Oauth, passing it on to provided GitHub. Let's see. And up to structure property in use, blah, blah, blah. All right, let's say, log in refresh. No API error is very weird. Orb API, error, invalid claim, missing sub claim.

18. Troubleshooting Superbase Provider Setup

Short description:

Our provider setup seems incorrect. Let's double-check and generate a new one. We're encountering issues with setting up the login. Any Superbase experts who can help?

This is suggesting that our provider is not set up correctly. That looks right, let's make sure that is. Whoops, exposed it. Is this one. And update just in case. And that should be all good. Let's just delete this just in case, generate a new one. Stop sharing for a second and copy it across. I can share again. And let's try to paste this one in here and then grab our client ID from here. It's definitely enabled. Okay. And that one is definitely enabled. It looks all good, and we don't have it. See that's weird, I actually have a user. So the login stuff has worked. So I'm going to delete my user, I'm gonna refresh this application, I'm gonna clear out my cookies. Delete all. I'm gonna clear out my local storage. All right, I'm gonna refresh, I'm gonna click login again, And then that is not setting anything. Hmm. Anyone see what's going on? Anyone more familiar with Superbase than me?

19. Adding User ID for Messages

Short description:

We fixed the issue by adding the OAuth helpers, which synchronized the server-side and client-side sessions. Now we can continue with the workshop. We're going to add a user ID for each message by creating a new column and establishing a foreign key relationship with the auth.users table. This will ensure that only authenticated users can send messages. However, we can't enforce this rule yet because some columns don't have a user assigned. We'll save the changes and manually handle the database migration. After adding the user, we can check if the relationship is correctly set by viewing the data in the table editor. This table stores authentication-related data managed by Superbase.

I think. Yeah. Okay. So it's creating our GitHub user. And then, if we have a look at logs, would that make anything more clear? Auth logs. Invalid claim. Missing sub claim. Very strange. So this says we just need that star, we need to register a new blah blah. And that should be all good. Sign in with OAuth provider GitHub. So there's one thing it says there, which was like insert the final URL of the hosted application, which I don't know if we've done properly like that, enter the final hosted URL of your app, and the site URL. This is important. Yes. I don't think that could be an issue. It does sound important. This one is, if we go to settings and then auth settings, this is is it there? Somewhere. Yeah. I believe it is set to localhost over port 3,000. URL configuration. Yeah, right. Site URL. So this is set to localhost- But we didn't do that in GitHub. If you go to the OAuth application, we set it to the super base URL, I think. This one? Yeah. Does that need to be localhost as well? Maybe. Let's have a look. HTTP, colon, slash slash, localhost over port 3,000. Ah, actually that's a very good point. Of course it does. All right. You're a genius. Because it said it's on the wrong domain, right? Yeah, genius. All right, it's all okay. All right, let's click log in. Let's go through that flow. Then we get redirected back to here. All right, and then we refresh and we see everything. Thank you. Thank you very much. All right, cool. So we're back to the point where we can now, well I guess we can't demonstrate the problem now because we fixed it by adding the OAuth helpers. But before this server side session here and the user associated with this server side session was different to this client session. But now that we have wired up our super base OAuth helpers, it is now, yeah, we're now handling all of that properly. Because this is now the Create Server client is now using cookie storage instead of using local host. And then our front end is also using cookie storage to use our client, our Create Browser client. So, yeah, they're both using the same storage now, which means that we have a synchronized session across our server and our browser. So, awesome, thank you for that. That would have been a terrible just halting point for the workshop where we wouldn't really be able to get anywhere. And now maybe we can finish it off. So we've done all of our server side stuff. We have done, yeah, so I guess we've done authorization. No, we're gonna add a user ID for each message. So which user actually sent each message. So if we come back to our table editor here and look at messages, we have our ID created at and content. So we're going to create another column for user underscore ID We're gonna add a foreign key relationship. So this creates a relationship between this table and another table. The magical secret table that a super base users for managing all of the authenticated user stuff is the auth dot users table. So we want to select that here as the table that we want to refer to, and then we want to create a relationship with the ID column. So now we will only be able to like this, this column will only be able to store user IDs that exist in the auth dot users table. So essentially only users who have signed into our, signed into our application. So cool, and we would tick, we would untick allow nullable and say that each message has to have a user that sent it, but we can't actually do that because we have all of these columns that don't have a user yet. So if we save this, leaving it as allow nullable, this is gonna show you how you manually do like database migrations, where you're trying to add something that contradicts a rule in the database structure. We create that new column and we don't have it set and we have not null, we allow no values in there. Then we go and get our user. So we should only have one user, which is me. I can copy my user ID here. Then we can come back to the table editor and we can add this user and we can check this is like, wired up to the correct place by clicking view data here. And this will show us like, all of the different bits of data that exist in the auth.users table. So this is all of the bits of like, stuff to do with authentication that Superbase stores.

20. Troubleshooting Authentication and Data Access

Short description:

So we have added a user ID column and modified the RLS policy to allow only signed-in users to read messages. However, we are experiencing issues with logging in and seeing the data. We have tried clearing cookies, turning Remix off and on again, and deleting the user, but the problem persists. The error 'invalid claim missing sub' is still occurring. We are unsure of the cause and are currently investigating the issue.

So this is all of the bits of like, stuff to do with authentication that Superbase stores. And so that looks good. That looks like it's wired up to the correct user. So then I'll say, save and then I'll do that for these other two as well. Sick. And now we want to check our application is still working, which it is. So we now have this user ID that's being piped through, which is good. We've added a column with a foreign key relationship to the ID column of auth.users. And then we want to modify our RLS policy to only allow signed-in users to read messages. So we actually already did that before, which if we have a look at our policies, that was this one here, where we made it so that only authenticated users can see this data. So if we refresh and we log out, we've logged out, but we still see the data, right? And it kind of like, it requires us to refresh to be able to see that we don't actually have access to that data anymore. And then if we click log in again, gone through that login process, but we're still seeing an empty array. And then we have to refresh to be able to see that we have broken everything again. We actually have broken everything. What's going on? So log in. What? Localhost. Let's try logging out. I shouldn't have signed out. That was my mistake. Hm. What have I just broken? That's all on. All we did was add a user ID column. This is still fine. Let's try another tab. Clear cookies maybe? Sorry? Clear cookies maybe? Good idea. Storage... cookies... don't have any cookies. Delete all. Delete everything. Delete all. This one is haunted. Have you tried turning Remix off and on again? That's a... good call. Let's try turning Remix off and on again. And... refresh. I don't like this error. This makes me concerned. Login, refresh. No. We're still getting invalid claim. And this is definitely local host, we definitely updated it because we got... Our session. Uhh... Okay. Authorize. Okay. Not sure what's going on there. Let's try taunting it once again. And say log out. And then refresh and we'll see that we have an empty array. And go login. And refresh. No. Whoops. Uhh,whoops. Very weird. Let's try...... It's very strange because here we have......that's all set up and we have our user. All right, let's just clear out these. And then let's delete our user. And clear out cookies. Refresh. Lug in...! No. Hm. Very, very strange. Invalid claim missing sub. When in doubt, do exactly the same thing again let's put a slash on the end that'll help. Update application.

21. Troubleshooting OAuth App and Adding Auth Listener

Short description:

We encountered issues with the steps for creating the OAuth app. The workshop steps are broken, and the speaker needs to investigate and update the steps. They will provide a working example and notify the audience. The next topic is adding an auth listener to handle changes in the user's state. The listener compares the access token from the server and client and triggers a reload of the loader if they differ. This ensures that the data is fetched correctly when the user's session changes. A Superbase auth listener component is created using the authentication state change hook. The listener uses a Fetcher to reload the active loaders when an empty action is submitted. The listener is implemented in the superbase auth listener component and rendered in the root.tsx file.

Login. Refresh. No, really doesn't like it. Alright, I'm going to copy this one and I'm just going to create this one more time. We're going to delete it somewhere. Advanced. Delete. Delete. Alright, we're going to try creating our OAuth app again. For this one. Home page can be localhost. Authorization callback can be this one. Maybe we'll just quickly confirm that. Here, under. Here, copy this one. Register. Copy. Paste it in here. Save. Apply the next line. Here. And now, we can answer some of these questions. Can you copy some messages with Seek and See? And let's try logging in one more time. 404, what are you talking about? That doesn't make any sense. This is what happened to me when I tried to follow along. Okay. So the steps are broken. Hmm. I definitely had it working. Uh, that is very strange. Okay. I might need to work out what's going on and then maybe update the, uh, the steps for the workshop cause this doesn't make any sense. Uh, well, I guess. When we. Yeah, maybe I'll just need to do that and then put up a different example and then maybe ping the, uh, this discord and let you know that there is a working example up there, uh, but it was definitely working earlier today. Uh, so what else are we going to talk, talk about? We were going to talk about adding an auth listener. So yeah. Um, uh, when the, what I was trying to demonstrate is when the state of the user changes, um, we are manually needing to refresh to be able to, um, to be able to, to basically like re fetch our loader because our loader ran when our component first loaded. Um, and at that point we didn't have a user. We hadn't authenticated with GitHub yet. And then GitHub sends us the access token and blah, blah, blah. Um, but then there's nothing that tells. Uh, remix that it needs to reload that loader again. It needs to go and fetch the data for our messages again. So we need to create a Superbase, uh, auth listener. And so this is just a component that uses super base dot auth dot authentication state change, which is a, um, a hook that gets called anytime anything happens on your Superbase session, um, it then passes in the event and the session and then automatically loads that server. Um, so that session has an access token if it's signing in or it doesn't have an access token, if it's signing out. Um, and so what, uh, what we set up here is basically pass through the access token from the service side. So from that session that we got in the loader, and then we compare that to the one that we're getting from the client. And then if they differ, it means that basically the client and the server are out of sync. Um, and so the, the state of the auth of our users session has changed, but. The, the state of the loader, like when we fetched, uh, data in the loader, we didn't have that current state. So either the user was logged in and they saw the results and now they're logged out, so we need to refetch that data to get rid of the results or they fetch the results. There was no results and then, um, the state of the, uh, their session has changed and so we need to go and fetch those fresh results. So that's what this does. This is a little trick where, uh, we can, uh, create a Fetcher by calling use Fetcher, which comes in from, uh, Remix. And then, um, the reason that this is a little bit of a trick is that anytime you submit, uh, a, uh, an action, anytime you, uh, you run an action, so send a put or post or patch request, uh, to an action, um, Remix will automatically recall all of the active loaders. So we can make a basically just an empty, um, submit, uh, like, uh, we can emulate submitting a form to an action with no data, uh, and we can create an empty action that just basically returns null, but what this will trigger is a reloading of all of the active loaders. So let's just implement it anyway. And then we'll see if everything just magically works. Uh, Superbase. So we're going to create a new component called superbase auth listener. So we're going to create that in components, Superbase, listener.tsx, and I dumped this in here. So this takes in an access token and Superbase. And so we want to render this from our root.tsx file. So up here we want our superbase auth listener, superbase auth listener, which comes in from components, and then that takes in an access token and a superbase client. So we can set the access token to be, well up here, we have grabbed a session from the server side. So in our loader, we've grabbed a session, piped it through to our component here. And so here we can say our access token is equal to session, and then if we have a session and have an access token, then we'll pass that through. And we also want to pass through our superbase client, which is equal to superbase.

22. Mutating Data in Superbase

Short description:

We were going to create an action at app routes and handle auth.tsx to trigger the re-fetching of loaders. We would render it above the outlet. We encountered issues with GitHub's redirect URL, resulting in a 404 error. We planned to mutate data in Superbase, add an RLS policy for inserts, implement real-time updates, and deploy to Vercel. However, we need to investigate the issues further. The example repo seems to be working fine. We will post updates in the Discord chat.

And yeah, that should be all good. Then the next step was to create this empty action. So we're going to create an action at app routes and then handle auth.tsx. So if we have routes and then handle auth.tsx and we do this, this will trigger that re-fetching of all of our loaders, and then we want to render that directly above our outlet, which we've done up in route.tsx. So we've got this one here. And if our application was working, then when we log in, log in slash authorize. I mean, I don't think it's super likely, but maybe GitHub's broken right now. Let's put it on GitHub. It's not my fault. It's not Superbase's fault. Something weird's going on with GitHub. So it looks like the redirect, you are, is that and yeah. Okay. I'll work out what's going on there. But yes, if I could demonstrate it, what would happen is that, this would just dynamically swap out for the, like when we click log in, it would be like, cool, something has changed so I'm going to re-fetch this. And then we would see our data. And then when we click log out, same thing. It would log out our user and then it would detect that something has changed and it would recall our loader to, to refresh those results. So.

Then we were just going to look at mutating data from Superbase. So creating a form that then actually like inserts data into our Superbase database we were going to add an RLS policy to enable inserts. And then, uh, we were going to implement real time, uh, where we're going to implement a listener that, uh, checked whether, um, yeah, any time any changes happened in the database, we can then call this function, which could update like our client side state, um, to show that there's a new message, uh, and then we were going to deploy it to Vercel and we're all going to have fun and chat lots. Um, but. Uh, yeah, I think something weird is going on. So I'll, I'll have a look at this tomorrow and then I'll post in the discord, um, if there are any changes that need to happen. Um. Yeah, I see, someone said they've gone through the whole workshop and the example repo and it works. Fine. Um. Very strange. Hmm. Um. Should we just hack at things until it works? Maybe I'll move my login, oops. Back to here. Get this from the outlet. Unhack all of the things that I hacked. Uh, don't need that. That should be coming in from the outlet context. Uh, the only other thing I was going to do in this login component was conditionally render this button based on that session. Um, so if I just paste this, um, then yeah, checking if the session has a user and then if it does doing log out, otherwise doing login, um, which we will be able to see. Cause this will, So login 404. Okay. It seems like a very weird thing. That GitHub is 404-ing. Cause this is, I don't think there will be any. There will be. Um, if there will be any, PIP. Because I don't know. So I you're doing PIPs, which is why I, you know, there's like a lot of bitmap stuff, which is, yeah. Maybe GitHub actually is having a weird issue. Uh, but all right. Anyway, I will work out what's going on and then I'll post in this discord, uh, chat. The thing is though, add a 404 from the start, I'm wondering if it's configuration somewhere, as far as I could see the redirect URL is not a local host yet, that's what I was thinking when we actually, like when we log in, this is saying that the redirect URL is still I know, but that's the callback. Hmm. Not all such reasons, such called back. Very strange the repo example works fine apparently, sending it to there. Hmm. Very weird. Well, yeah, if you want to, if anyone else wants to pull down like this repo, should be pretty much the example that we've gone through, but with some extra real timey stuff, yeah, maybe try pulling that down and seeing if it works for you. But it is very weird. Then we're getting a 404. Hmm. What news get her looked into make life is it? Doesn't yet. That is exactly the case, Chris. I feel like I was thinking, let's just use this because it will abstract away all of the, like, complexity of needing to, like, sign up a user and then go through the like the flow of verifying your account and blah, blah, blah, blah. But this is the simplest way to get a login session up and running. And apparently it's just not the case. That is interesting to know that it's working. The example repo is working. All right.

23. Local Email and Password Authentication

Short description:

We sidestepped the GitHub issue and now we're using local email and password for authentication. The data is stored in our Superbase database. We have a new user ID and can create a new row. The listener for logging in and out is also working correctly. Now we'll implement the last part, which involves mutating data in our Superbase database. We'll render a form on our index page and submit it to an action function. The action function creates a Superbase server client, gets the message from the form data, and inserts it into Superbase. However, when we refresh the application and submit a new message, nothing appears. Something seems to be broken.

Maybe I should check that out. So actually, I even still have it running. So I'm gonna stop this one and I'm gonna go cd remixconf example npm ransomware. Run dev. So this should be the example that I made earlier today. Am I logged in? Hello? No, it would appear not. Okay, so this one seems to not be working as well. I guess we could quickly rip out the Github part. Does anyone need to go to bed? I need to go to bed because it's 11.23, but I can quickly do that. Let's go, let's get rid of Github in case Github is the problem. And let's say handle local or just email login, and we'll say superbase.auth.signinwithpassword. So this then takes an email, which we're going to say John at It takes a password, which we're going to say password and we will need to handle email sign up as well to actually create the account. Let's click that. And then we need some extra buttons. So if we do, we want to say handle email login, handle email sign up. And now we run this one again. All right, we now have login and sign up. So if we click sign up, this will go and create an account in the background. I will need to verify on my email, confirm my sign up. Copy link pasted in here. Refresh. OK, cool, log out. Logged in. Oh, we don't actually have any data in the database. I hope this wasn't the problem all along. I'm sure it's not. All right. So for anyone playing at home, we've just side stepped GitHub altogether. So now we have just we are just using local e-mail and password. So this is being stored in our local directory. Local email and password. So this is being stored in our Superbase database, rather than having anything to do with GitHub. So you'll see that we now have a new user ID. So if I copy that one and come across here and I will create a new row, for. This is working, and paste in this user ID, and now if we refresh here, we'll see this. So that was. Yeah, maybe GitHub is actually having weird issues. Maybe Superbase have maybe pushed something that broke GitHub for a second. But OK, so this is working. So now if I log out, we'll see that that listener is now working as well. So when I log in, it goes and refetches the loader, when I log out, it goes and refetches the loader. So that is all working correctly. So I guess we can. All right, let's implement the last little bit. So we have something that is working. We'll just copy and paste code snippets. It will be fine. But it looks like all of the rest of the remixey stuff and all of the rest of the auth stuff is working just for some reason the GitHub config is not. So where were we up to? We had added superbase-auth. We had added our authorization. We had added a superbase-auth-listener. So now we want to mutate data from our superbase database. So we're going to copy our form here and on our index page, we are going to render out a form underneath all of our stuff. So this is a form that comes in from remix. We need to set the method to either post, put or patch or something other than get. So we have an input for our message and we have a submit button. And so, this is going to submit to an action. So we can declare an action in the same file. So, this is an action function which has action args. And we create a Superbase server client. We then get the message from our form data. Sorry, my voice is starting to die if you can't hear. We then call Superbase.FromMessages.Insert. So this is how we insert a new value into Superbase. If we have an error we'll log it out. Otherwise we'll just return null. And so now if we go back to our application and we refresh, we'll see this, we'll say this is a new message and we click send and we don't see anything. And we think something has broken, something is wrong. Well that is actually the case.

24. Adding User ID for Messages

Short description:

We fixed the issue by adding the OAuth helpers, which synchronized the server-side and client-side sessions. Now we can continue with the workshop. We're going to add a user ID for each message by creating a new column and establishing a foreign key relationship with the auth.users table. This will ensure that only authenticated users can send messages. However, we can't enforce this rule yet because some columns don't have a user assigned. We'll save the changes and manually handle the database migration. After adding the user, we can check if the relationship is correctly set by viewing the data in the table editor. This table stores authentication-related data managed by Superbase.

If we have a look at the network, if we click send again, this post request is getting 200 ok, null, and if we refresh, we see that nothing has actually changed. So it should be giving us an error. Somewhere should be giving us an error. This one, this is working. No, it thinks it's getting back. Confused.

So, we have inserted form post. It doesn't automatically attach the user ID to the message? It doesn't. So if we say user ID, and let's just set that to... Let's just set that to the string of my user for a second. And, send. Yeah, you've got to set the column back to nullable, right? Or to not null. So, we're creating them and not associating them with a user. Well, it shouldn't be creating them at all, because we don't have a row level security policy to enable inserting. But, I'm trying to demonstrate that but it doesn't... Nothing is working consistently like it should.

So, one reason that we can't actually insert this value. You point out a good point though. We should edit this column now and we should set this to not allow nullable now that we have a user ID for each of our messages. Because we don't ever want that to not be associated with the user. Another cool thing we can do while we're here actually is this column, we can set a default value to be AUTH.UID. So, we can actually call that function as the default value. Which means that it is automatically associated with the user who inserts that row. And so, now if we come across to our policies, we've only written a policy for selecting. And so, we want to write a new policy for insert. We want to say users can insert their own messages. We want this to only be for authenticated users. And the expression that we want is UserID, so this is the column in the messages table. We want to make sure that UserID is set to AUTH.UID. So, essentially, this is the currently signed in user, and we're making sure that that's the value that they're trying to set to the column. So, they can't insert a value for someone else. They can't insert a message on behalf of someone else. So, now that we have an insert policy, we should actually be able to insert into our superbase database. So, if we send this, we see there it is. This is a new message. And this is another message. And we can get rid of that bit that we added here, so we're manually passing along a user ID. Because by default, any time we try to insert, it's going to use that default value of the currently signed-in user. So, that will happen automatically now. And we can say, automatically send. And it's working. And so, now the last part that we wanted to do is, right now this is working, because when we are inserting into superbase, that's running in an action. And as I said before, any time that you complete an action, it will go and re-run all of the loaders that are active on the page. And so, this is automatically going and fetching that data that's changed, but that's only going to work for my user. If one of you was— if this was hosted on Versel or something, and one of you was to try to type in a message, that's going to refresh on your side because you are submitting an action for your session. But that doesn't tell my browser anything, that doesn't tell me that I need to go and re-run that loader. So, that's when we want to use real-time subscriptions in Superbase. So, we did all of that stuff. We want to implement real-time. So, we need to make sure that replication is enabled for the messages table. So, if we come back to our Superbase database, go to database, replication, and then click zero tables over here, we want to make sure that this is turned on for messages. So, now we will receive those messages, otherwise Superbase doesn't send out those real-time stuff. So, now, in our client, we can subscribe to any of those changes that are happening in Postgres. So, we can specify that the event we want to listen to is insert, the schema that we want to listen to is public, and the table is messages, and then that's going to call this function every time a new value is inserted. So, whether it's inserted by me or any of you, then we're going to call this function, which is just going to update our current client-side state with our new messages. And you'll see I'm using awful annies all over the place, which I don't think I'm actually using in the example for this repo, so maybe I'll... yeah, I'll use that example instead. All right. So, we want to create a new component called real-time messages. And I'm just going to dump this in here. So, we can actually pull out the type of an individual message by using our database public tables messages row. So that's like digging into this object that we got back from our generated types, which are wrong now. For anyone playing at home, we did change the structure of our database. So we actually do need to run this generate again. So we have the right types. But yeah, now we can dig into our messages and now we have a message that shows that it has an ID created at and content, which isn't right. Because it should now have a user ID. Let's worry about it another time. So now we have this real time messages component. So what we're going to do is we are going to, in our index.tsx page, we are going and fetching our...

25. Real-time Subscription and Wrapping Up

Short description:

In our loader, we fetch the initial state of the messages server-side and display them. We also set up a real-time subscription to listen for changes in Postgres. We update the client state and render the messages. Pushing the project to Vercel is the next step, but it's not working properly. Superbase has many other features like File Storage, Edge Functions, GraphQL, Database Functions, Triggers, and Webhooks. Follow Superbase on YouTube and Twitter for more information and join the community Discord. Check out the Superbase blog and documentation for in-depth technical content. Follow the speaker on Twitter for updates.

Not there. In our loader, we're going and fetching all of the messages server side. So the initial state of this page, when we load this page, we go and fetch the state of the messages server side. And then that's what we want to display. But then, when our real-time subscription that's listening to changes in the database, when that tells us that there's a new record, we want to also display that one. So we want to start with the server version of those messages and then we want to update them anytime anything changes client side. So we're going to say, real-time messages, and then we're going to parse it the server messages, which in this case is just called messages. Which it doesn't like because we're finally typing things properly. And so we can say it's either going to be messages or it's going to be an empty array. Cool, so then real-time messages is going to get that as the initial state of our server messages. So we're setting up, we're using new state to create a new variable for messages. We're initializing that with the server messages, so the server state of that. And then this is just to synchronize if that loader does get recalled and we have a new state of our server messages, then we want to update this variable. So we call setPosts. setMessages. Yeah, and then we're setting up a subscription that's listening to any changes in Postgres, any inserts. We then have... messages. It needs to be setMessages again here. It's setPosts. It's too late for me. There we go. All the red's gone. Thank you. Yeah, so we set up a real-time subscription for inserts. We then update the client state, and then this is now the component that's responsible for just rendering out those messages. So now, we won't actually see anything change because I'm the only user, but if I send this again, I'll see this. But if we all go to... The next step was going to be to push this to Vercel, but since it's not really working properly, I won't bother, but you can just create a GitHub repo, commit the code, push it to GitHub, create a Vercel project. Make sure you add the environment very first for your SuperBase URL on SuperBase and on key, otherwise it won't work. And that's when you update the auth settings in the SuperBase dashboard to point to your Vercel instance instead of this one, which I think I did in my other project. So if we go to... Remixconf... example. And we check authentication. Not there. Settings. Or, general. Where did we find it? API. It's in the OAuth application at least, right? In GitHub. Yes. I think so. Where did we just find this before? URL configuration. This one. This one should be Vercell. So let's have a look at Vercell, because I pushed up this one earlier today. So if we copy this URL... and update this one to point to that URL, and then make sure that our OAuth application for the RemixConf example is pointing to this one... now... does this work?......... Nah...okay, everything's broken. This was the example that I've put together earlier today that was working with... with GitHub Authentication, but that looks like it's also not happy. Let's not spend any more time wasting time debugging things. It will work. I'll fix it up tomorrow. So, wrapping up, we have only scratched the surface of what Superbase can do, because it can even do the thing that we're trying to do that it's not doing. Which I will work out what's going on. But we didn't even touch File Storage, Edge Functions, GraphQL, Database Functions, Triggers, Webhooks, there's a whole bunch of cool stuff in Superbase. So, if you want to learn more about Superbase, you should head over to the YouTube channel and subscribe there. Definitely follow us on Twitter for all of those awesome memes. And we have a launch week coming up fairly soon, which if you're not aware, Superbase... Usually every like three or four months we have a big launch week where we release a new feature every single day of the week. So, you should definitely follow us on Twitter to find out what is coming up soon. You should join our community Discord so you can keep talking to awesome people that enjoy using Superbase. Our Superbase blog, as fairly recently we've kind of like switched focus to being like writing really like more in-depth technical blogs on our blog. So, I recommend you go and check those out. There's also this Superbase documentation, if you want to go a little bit deeper or even work out what the hell is going wrong right now. If you want to learn more from me, you can follow me on Twitter. You should go and do that.

Watch more workshops on topic

React Summit 2022React Summit 2022
136 min
Remix Fundamentals
Featured WorkshopFree
Building modern web applications is riddled with complexity And that's only if you bother to deal with the problems
Tired of wiring up onSubmit to backend APIs and making sure your client-side cache stays up-to-date? Wouldn't it be cool to be able to use the global nature of CSS to your benefit, rather than find tools or conventions to avoid or work around it? And how would you like nested layouts with intelligent and performance optimized data management that just works™?
Remix solves some of these problems, and completely eliminates the rest. You don't even have to think about server cache management or global CSS namespace clashes. It's not that Remix has APIs to avoid these problems, they simply don't exist when you're using Remix. Oh, and you don't need that huge complex graphql client when you're using Remix. They've got you covered. Ready to build faster apps faster?
At the end of this workshop, you'll know how to:- Create Remix Routes- Style Remix applications- Load data in Remix loaders- Mutate data with forms and actions
React Summit 2023React Summit 2023
106 min
Back to the Roots With Remix
Featured Workshop
The modern web would be different without rich client-side applications supported by powerful frameworks: React, Angular, Vue, Lit, and many others. These frameworks rely on client-side JavaScript, which is their core. However, there are other approaches to rendering. One of them (quite old, by the way) is server-side rendering entirely without JavaScript. Let's find out if this is a good idea and how Remix can help us with it?
Prerequisites- Good understanding of JavaScript or TypeScript- It would help to have experience with React, Redux, Node.js and writing FrontEnd and BackEnd applications- Preinstall Node.js, npm- We prefer to use VSCode, but also cloud IDEs such as codesandbox (other IDEs are also ok)
Remix Conf Europe 2022Remix Conf Europe 2022
195 min
How to Solve Real-World Problems with Remix
Featured Workshop
- Errors? How to render and log your server and client errorsa - When to return errors vs throwb - Setup logging service like Sentry, LogRocket, and Bugsnag- Forms? How to validate and handle multi-page formsa - Use zod to validate form data in your actionb - Step through multi-page forms without losing data- Stuck? How to patch bugs or missing features in Remix so you can move ona - Use patch-package to quickly fix your Remix installb - Show tool for managing multiple patches and cherry-pick open PRs- Users? How to handle multi-tenant apps with Prismaa - Determine tenant by host or by userb - Multiple database or single database/multiple schemasc - Ensures tenant data always separate from others
Remix Conf Europe 2022Remix Conf Europe 2022
156 min
Build and Launch a personal blog using Remix and Vercel
Featured Workshop
In this workshop we will learn how to build a personal blog from scratch using Remix, TailwindCSS. The blog will be hosted on Vercel and all the content will be dynamically served from a separate GitHub repository. We will be using HTTP Caching for the blog posts.
What we want to achieve at the end of the workshop is to have a list of our blog posts displayed on the deployed version of the website, the ability to filter them and to read them individually.
Table of contents: - Setup a Remix Project with a predefined stack- Install additional dependencies- Read content from GiHub- Display Content from GitHub- Parse the content and load it within our app using mdx-bundler- Create separate blog post page to have them displayed standalone- Add filters on the initial list of blog posts
GraphQL Galaxy 2020GraphQL Galaxy 2020
106 min
Relational Database Modeling for GraphQL
In this workshop we'll dig deeper into data modeling. We'll start with a discussion about various database types and how they map to GraphQL. Once that groundwork is laid out, the focus will shift to specific types of databases and how to build data models that work best for GraphQL within various scenarios.
Table of contentsPart 1 - Hour 1      a. Relational Database Data Modeling      b. Comparing Relational and NoSQL Databases      c. GraphQL with the Database in mindPart 2 - Hour 2      a. Designing Relational Data Models      b. Relationship, Building MultijoinsTables      c. GraphQL & Relational Data Modeling Query Complexities
Prerequisites      a. Data modeling tool. The trainer will be using dbdiagram      b. Postgres, albeit no need to install this locally, as I'll be using a Postgres Dicker image, from Docker Hub for all examples      c. Hasura
React Summit 2023React Summit 2023
56 min
0 to Auth in an hour with ReactJS
Passwordless authentication may seem complex, but it is simple to add it to any app using the right tool. There are multiple alternatives that are much better than passwords to identify and authenticate your users - including SSO, SAML, OAuth, Magic Links, One-Time Passwords, and Authenticator Apps.
While addressing security aspects and avoiding common pitfalls, we will enhance a full-stack JS application (Node.js backend + React frontend) to authenticate users with OAuth (social login) and One Time Passwords (email), including:- User authentication - Managing user interactions, returning session / refresh JWTs- Session management and validation - Storing the session securely for subsequent client requests, validating / refreshing sessions- Basic Authorization - extracting and validating claims from the session token JWT and handling authorization in backend flows
At the end of the workshop, we will also touch other approaches of authentication implementation with Descope - using frontend or backend SDKs.

Check out more articles and videos

We constantly think of articles and videos that might spark Git people interest / skill us up or help building a stellar career

React Summit Remote Edition 2021React Summit Remote Edition 2021
33 min
Building Better Websites with Remix
Remix is a new web framework from the creators of React Router that helps you build better, faster websites through a solid understanding of web fundamentals. Remix takes care of the heavy lifting like server rendering, code splitting, prefetching, and navigation and leaves you with the fun part: building something awesome!
React Advanced Conference 2021React Advanced Conference 2021
39 min
Don't Solve Problems, Eliminate Them
Humans are natural problem solvers and we're good enough at it that we've survived over the centuries and become the dominant species of the planet. Because we're so good at it, we sometimes become problem seekers too–looking for problems we can solve. Those who most successfully accomplish their goals are the problem eliminators. Let's talk about the distinction between solving and eliminating problems with examples from inside and outside the coding world.
Remix Conf Europe 2022Remix Conf Europe 2022
23 min
Scaling Up with Remix and Micro Frontends
Do you have a large product built by many teams? Are you struggling to release often? Did your frontend turn into a massive unmaintainable monolith? If, like me, you’ve answered yes to any of those questions, this talk is for you! I’ll show you exactly how you can build a micro frontend architecture with Remix to solve those challenges.
Remix Conf Europe 2022Remix Conf Europe 2022
37 min
Full Stack Components
Remix is a web framework that gives you the simple mental model of a Multi-Page App (MPA) but the power and capabilities of a Single-Page App (SPA). One of the big challenges of SPAs is network management resulting in a great deal of indirection and buggy code. This is especially noticeable in application state which Remix completely eliminates, but it's also an issue in individual components that communicate with a single-purpose backend endpoint (like a combobox search for example).
In this talk, Kent will demonstrate how Remix enables you to build complex UI components that are connected to a backend in the simplest and most powerful way you've ever seen. Leaving you time to chill with your family or whatever else you do for fun.
JSNation 2023JSNation 2023
30 min
The State of Passwordless Auth on the Web
Can we get rid of passwords yet? They make for a poor user experience and users are notoriously bad with them. The advent of WebAuthn has brought a passwordless world closer, but where do we really stand?
In this talk we'll explore the current user experience of WebAuthn and the requirements a user has to fulfill for them to authenticate without a password. We'll also explore the fallbacks and safeguards we can use to make the password experience better and more secure. By the end of the session you'll have a vision for how authentication could look in the future and a blueprint for how to build the best auth experience today.