In this chapter, we're going to set up our database. This will include setting up a new Convex account and a new project, installing a Convex SDK as well as the CLI tools, creating a table and testing out some CRUD operations on it, and finally, configuring the convex provider together with clerk authentication which we developed in the previous chapter. So for now no need to have your app running at the moment. In fact we're going to visit the convex website. So using the link on the screen you can get to this page that I see right here.
So why are we using convex for this application? Well primarily one of the biggest problems that I had was how do I bring a native file explorer feeling to a browser cloud based IDE because we are not writing files to any file system, right? We are writing them to a database. So we are either going to store text content or binary content depending on what type of file it is. So how do we make that feel native?
How do we make it feel so that the user is using an app even though this is a browser web application? In short, with Convex, our app will update in real time automatically. There will be no web sockets to set up, no polling, no cache invalidation headaches. It just works. And you will especially see this magic when we build the file explorer.
But honestly, you will see it everywhere else as well. So this is what I've prepared the finished app so you can actually see what I'm talking about. This is the finished result and I have purposely opened it in two different browsers. So I'm going to go ahead and just quickly create a new file something like test.tsx and you can see immediately it was created here and here. So if I go ahead and rename that to something you can see that immediately it's been reflected elsewhere.
That's the power of convex. Same thing happens if I create a new folder, for example, app folder, right? My apologies, this was a file. So if I create an app folder, you can see immediately all the things are reflected here. If I go inside of here and create a new file, let's call this container.jsx, immediately reflect it.
So this is byproduct of using convex. It's amazing sync engine that makes every app feel native. And this is reflected even further besides the file explorer. Take a look at the title of this project. So I'm using some random unique slug generator.
So I'm going to rename this to my project. Immediately renamed in the other side as well. That's what I'm talking about. That is the power of Convex. So let's get started and let's create an account.
And on the dashboard inside you're going to see a list of your projects or an empty screen if you have none. So I'm going to go ahead and create a new project. I'm going to call this Polaris and I will click create. Once you've created your project you don't really have to do anything here but you can leave this tab open. Now let's go ahead and go to convex documentation here so we can actually follow a simple getting started guide.
So in here you actually have quick starts next JS. So since we already have the project we are not going to run this command instead we're just going to go inside of our app and run npm install convex. And then I'm going to show you the exact version of convex I will be using through this tutorial. So make sure you are inside of your project here and run npm install convex. And I'm going to go ahead and prepare package json here so you can see exactly what version has been added.
So 1.31.2. For those of you who want to use the same version as me, you can install it like this. Once you have convex inside, Let's see what the next steps are. So now we have to set up a convex dev deployment running npx convex dev. This will prompt you to log in with github, create a project and save your production and deployment urls.
It will also create a new convex folder for you to write your back-end API functions in. The dev command will then continue running to sync your functions with your dev deployment in the cloud. So let's go ahead and run this command right here. So depending on if you've run this before or if it's your first time running it, you are probably getting a login link. Clicking on that login link will lead you to the login screen and then you just have to log in with your account and then it will basically be able to either create a new project in your account or you can choose an existing project and in here you should be able to see Polaris.
So I'm purposely doing it this way so you know that you did it correctly right. We created a new project called Polaris here and you can now select it here. There we go. And as you can see now, something that will happen besides just the connection is we have a new folder and we have some new things in our dot environment here. So let me go ahead and close everything here, open Polaris, and here we have the convex folder.
So right now you can ignore the generated folder. The underscore here kind of represents that it's not meant to be modified and we will never modify it. It will automatically be modified once we add things to our schema or our functions, right? This is kind of like Prisma's generated folder. Another thing you will notice is that we now have .environment.local.
So what I suggest actually is move this to 1.environment. I'm going to see if maybe we should keep it in .local or .environment. For now I'm just going to move all of them here like this and I'm just going to change this to be convex. And if you want you can leave these comments so you know what your team is and what your project's name is like this. So you should have convex deployment and next public convex URL.
And for now let's just remove dot environment dot local. And I will test this out in just one simple way. I'm going to do npx convex dev again. And if this continuously creates the .environment.local file, then I will just move it to that. It looks like every time we run npx-convex-dev, it will create a .environment.local.
So instead of moving convex to .environment, let's move clerk to .environment.local. I think that might be a better solution. So move clerk here and let's go ahead and change this to convex. And then remove dot environment. Sorry for going around, but I just wanted to figure out which was the one that gets created all the time.
In your new .environment.local you should now have the same content that you had before for clerk with the addition of new convex variables. And this time when you do npx convex dev, I believe there should be no changes to your .environment.local. There we go. So nothing new, nothing gets overridden. You don't have to worry.
Perfect. Once we have that ready, let's see what we have to do next. So, we now have to create a sample data for our database. So I'm just going to copy this and then I'm just going to create that file. Make sure you do this in the root of your app.
So outside of any folders here, create a new file, sample data.jsonl. Make sure to put the letter L at the end. And let's just paste. So just a simple three item list like this. Now let's go ahead and let's run this command.
Npx convex import table tasks sample data dot jsonl. Make sure you are running it in the root of your project so that it can actually find the sample data. Jason L. All right. As you can see we have a success message added three documents to table tasks.
And before I go any further if you failed at this command for any reason don't worry this is just to test out how convex works. So if you're using a specific operating system where this doesn't work as intended, it's okay. We will create normal data and schema later on. But I just wanted to populate it with something as in the tutorial. And immediately if you go back to this screen your database you will now see a table called tasks and inside of here you will see is completed field as well as the text field as well as their creation time and this is your database now.
So let's see what are the next steps here. Let's expose a database query. So let's find a way to fetch from this database and display that data in our app. So we're going to go ahead and do the following. Inside of our convex folder, I'm going to go ahead and create a new file called tasks.ts.
And let's paste this here. So import query from ./.generated forward slash server, which is basically this right here. We are exporting a constant called get and we are using the query. The query accepts the following parameters on arguments which is empty for now and a handler which is an asynchronous function which has access to the context and then the context itself has access to the database abstraction which we can in this kind of query builder tool well query and collect items from which table tasks and what's cool is that It should give you errors if something is wrong. We just need to make sure we have convex, npx convex dev running.
Let's just ensure that we have that. And I believe that then, my apologies, it will not give you errors here. So in order to test this out, we have to make sure that we have NPX convex dev running. So just make sure you have this running and then you will get a simple message of uploading functions to convex and then convex functions are ready. And you can actually test whether this works or not already.
You can go back here, go inside of your functions here and you will find a new function, tasks get. You can click on the run function here since there are no arguments to be passed you can see that the output was a simple fetch of all of our functions. So if you were to purposely mess it up by querying the wrong table here and then let's try and run this again. Run function you will see this time we have no output at all. I believe that later on this will actually throw an error right here in the editor but in order to do that we need to have an actual schema.
Since we don't have a schema it doesn't really know what's true or not, right? How does it even know that tasks exist? Well it doesn't. We know that because we created them using a JSONL file. So if you're worried about that, don't be, because later we are going to have a proper schema which will give us some type safety in that aspect.
Great. So now instead of testing this function through Convex's dashboard, how about we actually test it through our app. In order to do that, we first need to create a convex client provider. So I'm going to go ahead and do that. I'm going to create inside of my source components, convex-client-provider.tsx, like that.
And I'm just going to copy the content inside and then show you what it is. So we have use client to make this a client component. We are importing convex provider and convex react client from convex forward slash react and react node from react. We initialize a new convex singleton using new convex react client and we pass in process.environment next public convex url and this is the important part. So Double check that inside of your .environment.local you actually have next public's convex URL.
And then we export function convex client provider which is a very simple function which simply returns the convex provider element, passes the client prop and renders the children inside. Now that we have our convex client provider ready, let's add it to our layout, which I believe is outlined as the next step. There we go. So make sure you save this file and let's go inside of source app folder layout. And in here I'm going to go ahead and the same way we imported the theme provider, let's import convex client provider.
And make sure you do that from add components convex client provider. So don't use any NPM import. You are using your file that you just created and then go ahead and render this convex client provider. Let's see around the children. So I'm going to go ahead and do this inside of the team provider like this.
There we go. And all the way to here. Perfect. Now that that is ready, let's just go ahead and make sure our app is running. So I suggest that you have both of these running, npx-convex-dev and npm-run-dev.
Imagine this as your frontend and this as your backend. So now I'm going to go ahead and go to localhost 3000 and I'm just going to confirm that nothing crashes and everything seems to work perfect. And now that we have this provider set up we can actually use the hooks. So I'm going to go ahead and do the following. I will go instead of source app page.tsx and I'm going to change this to be a client component.
I'm doing this so I can use hooks within this page. And I'm going to go ahead and I will call tasks using use query from convex react and then I will import API from dot dot dot dot convex generated API. This will give me access to type save functions that I've developed and then what I will be able to do is iterate over them so tasks question mark dot map and in here I will have an individual task and then I will be able to return a div like this give each div a key task dot id and let's go actually it is underscore ID my apologies. So why does it why didn't it throw an error here again because we don't have the schema. So it had no way of knowing if maybe ID was some property that we had, right?
Because we could have easily added a property called ID here. So because of that it's not throwing any errors. As I said later, when we add a real schema, it will throw errors for undefined fields or tables that don't exist. So now let's go ahead and simply render task dot, You can see there's no auto-completion either so I have to manually add .text and let's go ahead and do isCompleted task isCompleted make sure you don't misspell this because as I said no type safety at the moment because we don't have a schema file. And just for fun I'm going to make this class name border rounded padding 2 flex flex column and I will go ahead and give this flex flex column gap 2.
So they appear one beneath the other. There we go. And let's give all of this a padding of four. So you can see that now and let's just go ahead and wrap this inside of template literals. Let me just see.
There we go. So you can see the actual output so is completed true is complete true is completed false. So let's actually try something out. I'm going to open a split view here. You don't have to do that.
This is a new feature so I'm learning it. Add, tab to new split view and I will choose this. Okay. So I have my app right here and I'm going to go ahead inside of my data here and I will find one is completed which is false. So integrate convex is currently in state of false.
Watch what happens when I directly modify this in the database to true. It is immediately reflected in my app. That is the native feel I keep talking about, right? When you are using a real-time database, which needs to have, and it does have, a very, very good sync engine, this becomes a completely different experience. And when we are building something like a cloud-based IDE, we need to have that feeling.
No one wants to work in something that feels like a second hand experience as opposed to you know a normal IDE on your machine. So that's why we are choosing convex for this. And same goes if you change this to false right. Immediately reflected here. Perfect.
And that's actually it for the quick start here. So what I want to do now is I just want to create a schema and then I want to connect all of this with convex, with clerk. So I'm going to go ahead and close this for now. And this time instead of having tasks, let's go ahead and go inside of our convex right here and let's create a schema.ts. So inside of here I'm going to import define schema from convex server.
I'm going to import V from convex values and I'm also going to import define table from convex server. I will export default define schema here. I will create new projects table using define table. And I will give each project a name. And I'm also going to give it something fun.
For example, let's do owner ID and let's do import status. The dot optional, the dot union, the dot literal importing, completed and Failed. So I'm purposely trying to do something a bit more complex than just a normal string so that you can see how you would, for example, create an enum, right? So each of our projects will be able to have an import status, which is completely optional because users can either create new projects from scratch or they will be able to import them from GitHub. In case they are importing, we're going to be using these set of enums to let the user know what is the status of the import.
Is it currently importing? Has it completed or has it failed? And once you save this what I want to do is I want to add an index. My apologies not save this. So I'm going to add an index here by owner.
So by adding the field owner ID, I will be able to query the projects much faster by owner ID. So every time I need to load all projects by a certain user, I will be able to do that in that manner. So now that you saved this file, it needs to be named schema, it needs to have a default export here. You should see this, You should see that it has added table indexes. You should see that there was probably some error here because we tried to basically this keeps trying to synchronize your code with convex cloud.
So if you are in the middle of writing it, it might be invalid. So that's why the errors are happening. But if the last message you see is a success, that means everything is working. If you want to, you can also restart the entire thing and that will automatically try again. And this time there shouldn't be any errors whatsoever.
And now if you go ahead inside of your convex, so let me just go ahead and go to my project here and if I go inside of my, let me see is it data, yes. Inside of my data here you can see that I now have projects in here and if I open tables you can see I still have my previous one tasks but it says this table is not defined in your schema. Right. So it's keeping them separate. And you can actually always take a look at your schema right here.
So you can see what it's doing. It's constantly synchronizing the code you have written locally to the cloud right here. That's also one of the very, very cool features of Convex. And if you learn to use this dashboard, your life is going to be so much easier because you will actually learn to see, you know, how much of your functions are hitting the cache, how much are they failing, you will be able to create log streams, all of those things. And there are so many more things Convex can do, but for this chapter we're just going to focus on what it does best which is real-time database, but you can orchestrate agents with it.
You can create schedules, a bunch of bunch of things here. And we will also be using it as our file upload system for binary files when we need that function. So now that we have projects here, you can see that you can add them by passing in the name, the owner ID, which are the required fields. So how about we go ahead and try and do that. So instead of convex we can actually now remove tasks And you can see this is what I was talking about.
Now we have an error here because the only table it knows about from our schema is projects. So it's very confused about the fact that we are querying tasks. And maybe even in page here we now get type errors because it doesn't know what this task schema is supposed to look like. So that's what I was telling you that is going to happen. So yes, you can now remove tasks from here and instead in the convex go ahead and create projects.ds and let's go ahead and import V from convex values.
And then let's add a handler which is an asynchronous function. We can access context from here. And what we're going to do is very simply await convex dot database dot insert into projects table context. My apologies arguments are separated like this. So let's just pass in the name to be arguments and owner ID which is also required.
And this is arguments.name. So for now, yeah, just hard code owner ID to 1 to 3. We are doing this on purpose because later we are going to connect this with clerks so we will actually be able to extract the user's ID here. And let's export const get which is our query. Arguments will be empty, handler will be an asynchronous function and just await context database.
Oops. So let's just get context. We have to import query the same place we imported mutation from and then we will get convex database dot get projects table and then just collect. My apologies it is not yet it is a query. The get accepts an ID get is for a single one but yeah we can just call this get for now.
And let's save it like that. And let's make sure that the last message you see here are that convex functions are ready. And now let's go back inside of the page here and instead of tasks, this will now be projects, api.projects.get. We are going to iterate over projects and we are going to use the project name. So we're going to be using project and this time if you try you should be able to see the keys.
Let me just see what's going on here. That's because we're not returning this. There we go. And now if you try there we go You can see type safety which I was talking about. So in here you can now do name and you can also do owner ID project dot owner ID and you can render this in a normal way now because it's no longer a type of Boolean.
So right now I don't think anything will appear here. So what we can do is we can create a simple mutation. So I will do const create project. Use mutation, which we can import from convex react and pass in api.projects, you guessed it, create. And then above this I'm going to add a button which we can import from components UI button.
And I'm going to add a label add new on click create project. Let's just make sure we do it like this. So create project and pass in the name. And let's call this new project like this. And when you click add new.
There we go. It immediately creates it with a name, new project with the owner ID. So if I change this to new project 1 to 3 and click again, there we go. So you can see how we don't have to do any validation, we don't have to do any polling, the sync engine does its magic. That is the magic of this use query right here.
So how do I get the currently logged in user, which I am right now, inside of this owner id? And more so in fact, how do I throw an error when the user is logged out? Because it's easy to protect the UI. But what's important is to protect our data access layer because that way, even if somehow someone bypasses our proxy or middleware, which has been known to happen, right? People have found vulnerabilities in the proxy.es which was previously called middleware.
People who still protected their API routes individually or in this case API functions were completely okay. Right. So that's what we're going to learn how to do. Let's go ahead and learn how to connect clerk with convex. You can actually find this exact guide on their documentation page.
So let's actually go through it. So we learn how to find it together. I think it's always good to learn how to use the documentation. So they offer a bunch of authentication providers. The one we are using is Clerk.
So I'm going to find it here in the sidebar and then I'm going to find the next JS example. So we already created an account with Clark and what we have to do now is we have to create a JVT template. So this is very important. Let's go ahead and head to dashboard.clerk.com. You can use the link on the screen of course.
And let's go to our new Polaris app. In here you should have only one user or more if you added more users and you know about it. And now let's go ahead through here and let's go inside of sessions and click on JVT templates. In here click add new template. For the template field, you can actually select convex out of the box.
You don't have to change anything here and you especially shouldn't modify the name. So just click save as it is. There we go. You should now have a new template convex. And now in here it says to copy and save the issue URL somewhere secure.
So let's go ahead and do that. We have the issue right here and I'm going to copy it. And now I'm going to go ahead and store it inside of my environment file. I believe that's where we need to add it. So let me just go ahead and check.
So dot environment dot local. I'm going to add one more field to the clerk section. And this will be called clerk underscore JVD underscore issuer underscore domain. And Let's just paste it here. There we go.
Now that we have added this, here's an important thing you have to do. You also need to synchronize your convex cloud environment variables. So I'm going to go inside of my settings right here and click on environment variables and I'm just going to go ahead and you don't have to add the next public convex URL or convex deployment. But if you want to you can just copy your entire dot environment dot local file and just paste things here. So I really don't think you need convex deployment here or next publics convex.
So I'm going to remove those. I'm just going to keep next publics clerk, next public clerk, clerk secret key and clerk JVT issuer domain. You most likely don't need this one either. This is specifically used for frontend, but still. We can paste our entire dot environment file here and let's just save all of these.
So this is important because the cloud doesn't have access to our local environment files, right? That is not synchronized because obviously those are our private keys. It would be a bad idea to synchronize those. So that's why we have to do it this way. So just make sure that in your project Polaris in Convex environment variables you have added Clerk JVT issue domain, Clerk secret key and Next Public Clerk publishable key.
Great. And now you will be able to learn how to, let me just scroll down, there we go, NextJS, we created the template. Yes, and now we have to go ahead and do the following. We have to go inside of convex and inside of here we have to create out.config.ts and let's go ahead and let's import out config from convex server. Let's export default providers, open an array, open an object, add a domain to be process.environment and then clerk JVT issuer domain.
And then application ID set it to be convex. We're going to go over these values and why those values specifically in a second. And let's add satisfies ALF config. All right. Let's go ahead and see.
The first thing is id needs to be capitalized. There we go. So why convex exactly in the application id? The reason is because our template name is convex. That's why I told you not to modify that because it needs to be the same.
And the second thing is clerk JVT issue domain. Double check that you have called it exactly the same here. So, clerk jvt issue domain. Copy it from here and then paste it here. It should match exactly.
And then double check once more that you have named it correctly here. Clerk jvt issue domain. Alright, now that you have that, double check that again the last messages you see here are that convex functions are ready. It's completely okay to have an error here, that's because it tried to synchronize when we were writing this code, so obviously it broke. We already have Clerk Next.js so no need to do that.
We already have this and we even have the middleware now. What we have to do now is we have to configure convex provider with Clerk. So this is what I suggest that we do. At this point we are having a lot of providers. So let's go ahead and do the following.
Let's go inside of source components and let's create a new file called providers.tsx. And in here I'm going to mark this as use client and I'm going to go ahead and I will import clerk provider and use out from at clerk next J.S. I'm going to import convex provider with clerk From convex forward slash react clerk and I'm going to establish convex here using new convex react client process dot environment dot next public convex URL using new convex react client process.environment.next public convex URL. And let me just see. Yes, we also need to import convex react client from convex react.
So we are basically kind of modifying this convex client provider. And now that I think of it, maybe we don't need to create a whole new providers again. Maybe it is enough for this to actually let me just check. Okay. Now let's create the providers because it is going to be easier to maintain things this way.
Right. So we're just basically creating the same thing you can copy from here and paste it here to ensure that you've used the correct environment variables here. And then what we're going to do is export const providers. Children react.react node. There we go and then I'm gonna return clerkProvider here and then in here I'm gonna return convexProvider with clerk and I'm going to give it client of convex and use out of use out.
And inside of here I'm going to render the children like this. And now that we have this let's go ahead and wrap our app with it. So I'm going to go inside of source, app folder, layout. I'm going to remove the clerk provider from here. And from here.
And for now I'm going to remove the entire header here. So I just have the convex client provider I'm gonna remove all the imports from clerk to clear things up and then I'm going to import my providers and providers here. And let's just add providers like this. So far not much should change. More importantly, our app should still be working just fine.
You should still be able to load your elements here. You should be able to create new ones. So nothing much has changed, right? We created the providers and We just changed from, let's go ahead and go to convex-client-provider. So we changed from using the convex provider to using convex provider with clerk.
That's basically the biggest change and we just combined them in one. So we don't have to manually do that. At this point, you can delete convex client provider entirely. And you can even add the theme provider to here. So let's do that.
Let's add the theme provider Like this. And I'm going to import theme provider from ./.themeprovider. So the local one. And then you can remove the theme provider from here. This way our layout isn't polluted with a bunch of imports and a bunch of providers.
So we just have a nice sleek providers right here. Great. So now what we have to do is we have to learn how to actually access convex, how to actually access clerks authentication within convex because sure this is still just UI. We haven't really done anything yet. So let's go inside of convex and let's go inside of projects.ts And now in here I'm going to go ahead and attempt to extract my current user and the way I can do that is by doing the following.
I can call context.auth.getUserIdentity and this needs to be awaited. And then what I'm going to do is if there is no identity in the first place, I'm going to go ahead and throw new error, unauthorized. You shouldn't be able to visit this, right? And okay, so that seems to be throwing an error now, simply because we haven't established a proper composition of inside of layout here. You need to wrap your app inside of authenticated from convex react like this and we need to whoops not here my apologies inside of providers so let's just quickly do that wrap your children in authenticated which you can import from convex forward slash react and now when you do this you shouldn't be getting an error here.
So let's go ahead and do the following now. In here, let's do the same thing. Const identity await context.auth getUserIdentity and ownerId will now be identity.subject. And let's go ahead and do if there is no identity throw new ever unauthorized. There we go.
And in here let's actually just return an empty array if there is no identity present. Great. So now I'm just going to go ahead and go back in here. Let's go inside of the source app. My apologies components providers and let's add an authenticated view from convex react.
So you can see I have imported authenticated convex react client and unauthenticated all from one place. And in here I'm just going to say not authenticated. And then in here I'm going to add outloading from the same place, convex react. And I'm going to say outloading. So what we actually want to happen here is to have a sign in button and a sign up button from Clark Connects JS.
So now I'm going to go ahead and sign in here the way I usually do with GitHub. I'm pretty sure you will get logged out to during this process. And there we go. You can see what happened. We now had outloading and now we have this.
Right. So Let's go ahead and just try and let's try and do the following now. So I will move children outside of any alt thing and I will comment out authenticated right. So now I can load and see this even if I am logged out. So, okay, let's re-enable this and let's just render user button inside.
Again, you can import this from Perk Next.js. So I should be able to log out myself now and you can see that now since I'm not logged in I cannot see any projects. That is because of this. If we are not able to detect an identity here we throw an empty array. So if I comment this out and refresh I can now again fetch the projects but that shouldn't happen this should be the normal behavior right So that's why we have added this.
If there is no identity present, we just return an empty array. But what's more impressive is this. The mutation. I'm logged out. And if I click add new, I'm getting an error unauthorized, right?
But if I sign in with GitHub this time, take a look at what will happen. I will be able to A, load my projects and B, click add new. And this time it has populated the exact user ID that I have. And one thing I've just noticed that I forgot to tell you, right now you can't select anything in your app if you try to. That's because in chapter one we copied and pasted myglobals.css.
So instead of your source app globals.css, if that's bothering you, just remove select none. And now you should be able to select things. Let me refresh and see. That should have fixed it. Perhaps you just need to, I mean, we just need to restart the app.
Let's go ahead and do rmrf.next to clear out the cache and then run the app again. There we go. You can now select things. So in case you notice that it's because of this select none on the body. Later this will be useful to get that real editor feeling and to disallow user from selecting things they shouldn't be selecting.
But for now, it actually might be useful. So no need to add that. I'm going to add a to do add select none later. And let me end the comment here. There we go.
So that's what's happening now. We have successfully connected Clerk to our backend. And here's what we can do even more we can actually now learn to use that index of ours so let's do context.database.query.projects and let's do now with index by owner and by owner accepts a query and inside of here we can do query equals for a field of owner ID to be matching identity dot subject. So let me go ahead and expand this like this. And this, there we go.
So we are now querying for all projects from the logged in user. You can see how now I can only see projects which have my owner ID. That is because in my data here, in my projects, all of my previous ones have this hard-coded one, two, three owner ID, which means that this user shouldn't be allowed to see those. So our authentication is working correctly. This is what I wanted us to achieve.
My apologies for a few hiccups here and there. It's kind of hard to, you know, demonstrate this with so little data in our project, but I think we did a pretty good job with explaining how we successfully connected Cleric with Convex. This will basically be the most important way of building our back end. So it's important that we establish this that early on. Amazing.
So I believe that actually did most of the hard work regarding this. I think I just want to check the providers to see if there's something more we can add here. I think for now, yeah, I would recommend still rendering the children only in the authenticated state simply because we aren't really, this is the kind of app that shouldn't even allow the user to not be authenticated. Right. This is a cloud-based IDE.
The only thing that unauthenticated users should see in this kind of app is the landing page and the login screen. That's it. Everything else is authenticated, right? The list to see your projects authenticated. The file explorer authenticated.
The actual code authenticated, right? So because of that, we're just going to render the entire project within the authenticated composition right here. So to wrap that up, I actually want to create a few components here just to make our app feel nicer and to kind of start building our features folder. So this will be our folder structure. I'm gonna use the features and I will create an out feature here and each of my features will have a component.
So let's add the components here and let's create unauthenticated dash view dot TSX so now I'm gonna go ahead and import an icon from Lucid React I'm gonna import a component called item from our components UI item which we have added through chat CNUI. So this has been added in the first chapter. And then I'm going to export const unauthenticated view. This will not accept any props, it's going to be purely a presentational component. And we're going to start by creating a container.
This container will have flex items center, justify center, height of screen and BG of background. Then an inner container which will limit to how wide this can be. So we give it full width but we limit it to maximum width of large which is 512 pixels and we give it a background of muted. And then we're going to do a composition using those elements above. So we're using the item component with a variant of outline.
Inside we render the item media with a variant of icon and we render the icon inside. And then inside of that we start rendering the item content. Each item content should have an item title, so we add unauthorized access here and below that we add item description, you are not authorized to access this resource. Beautiful. Let's go ahead and go back and set up the providers here and in the unauthenticated state let's add unauthenticated view.
We can do that by importing from features out components. So I'm going to move this above the theme provider like that. And let's remove the import for these two. So now if you go ahead and if you actually log out, you should see this. Unauthorized access.
You are not authorized to access this resource. Great. Now let's go ahead and while we are here let's create besides unauthenticated view let's create an outloading view so this will be even simpler. So components outloadingview.tsx outloadingview.tsx and we're just gonna import Spinner from components UI Spinner. We're going to export OutloadingView component and we're very simply going to return a div with flex, item center, justify center, height of screen, bg background and inside a spinner with size 6 and text ring.
The spinner again is just a very simple chat C and UI component you can find it in source components UI spinner and you can see it's practically just a loader icon which animates. Great. Now let's go ahead and render that here. So outloading view which you can import from features out components outloading view. And you can already see how this happens right.
It's loading and then it shows unauthorized access. Now if you want to you can extend this unauthenticated view with I believe it's called item action and maybe in here let me just check how we add this. So yeah, instead of item actions, I think you can add sign in button from clerk next JS and inside you can import a button which is a sign in and I think you can also style this to give it a variant of outline and the size of small so you should have sign in button imported from clerk next JS and button from components UI button and This should redirect you to the login page. So that is one solution you can have Another solution is to automatically redirect the user. So for example, inside of, my apologies, yes, inside of sourceProxy.ts, we can modify the proxy to only allow certain routes.
So for now, I'm gonna do const isPublicRoute, createRouteMatcher from clerk-nextjs-server. And I'm only going to allow, for example, forward slash API, forward slash ingest, and then everything after that. Because that's one of the scenarios which we want to allow, right? You don't even know what this is yet. We are going to learn that in the next chapter.
But for everything else, I want to redirect the user away. And then what I can do here is first of all move this above the default export and then open the Clerk middleware. Asynchronous. I get access to Auth and request. And if not is public route.
So if this current request is not a public route, I will trigger out dot protect method. And what this will do is it will automatically redirect your user whenever they try to access this app if they're logged out. If you prefer that solution, you can use that solution. If you don't, you don't have to. So whatever you like best.
I just want to show you multiple ways of doing things. Both are completely valid. They are basically just different user experience. They don't offer different layers of protection. If you like it this way, that's completely fine.
Just keep in mind you will probably want to couple this with some kind of landing page so that users shouldn't even get to this page unless they are redirected from your landing page. And if they do, they will be greeted with a screen like this. You're not authorized to access this resource. So if you wanna skip that altogether, you can implement what I did and just automatically redirect them. Or if you like this explicit action of sign in, then you can keep it like this.
Great. I think we implemented a lot and it's time to end this chapter. Amazing job. So Let's go ahead and commit all of this. Let's see, our chapter is 3 database setup.
So I'm just going to shut everything down. I will git add, git commit chapter 3 database setup, and then I'm going to git checkout dash b 03 database dash setup. Git push origin 03 database setup. Check out dash B 0 3 database dash set up get push origin 0 3 database set up. Let's wait a second.
There we go. Then let's go ahead and open our new app and in here we can open a new pull request. So this time I'm going to click here compare and pull request and as always we're going to review this code. So let's just create a pull request and if you've added CodeRabbit as I did in the previous chapter you will now get an automatic PR review. So let's see what it has to say about our current code.
Let's have a look at the summary by CodeRabbit. New features. Create and manage projects with automatic persistence and ownership tracking. Enhanced authentication flow with dedicated sign-in interface, loading indicators, and access control notifications. We updated the global styling which is referring to the fact that we removed select none and we added back-end service integration dependency referring the convex.
And in here it noticed some issues. So in here it is warning us that we shouldn't use non-null assertion on environment variables and risk runtime failure. And while this is true, this type of environment variable allows our app to run in the first place. So if we don't have this set up, the app will not even work. So this isn't something that will maybe fire sometime so that we have to add an if clause check here.
It will literally crash the app. So one way or another, we're going to see it fail. So it's not like this will go, you know, hidden from us. So that's the only reason it's okay to do it like this here. But yes, usually, whenever you work with runtime variables, you should do an if check if it actually exists and then throw an error so it prevents the app from running.
But this will do the same thing either way. So that's why we are okay. In here we are missing a semicolon. So if you notice a simple thing like this, You probably have a semicolon. You've probably noticed I don't have Prettier turned on or any kind of format on save.
The reason I don't have that is so that you can see every single line of code that I change in my tutorials, right? So I don't want any formatters to mess with some code outside of the screen recording view. So for you this probably didn't happen. If you have format on save and prettier and S-Lint you probably have semicolon here. Not a big issue.
In here it says that we have a schema mismatch with the projects table so you can see how well it understands our project. It read the schema table and it sees that this sample data does not match that at all and in fact this reminded me to remove that file which is exactly what it suggested. And in here it noticed some unused imports, another thing for us to resolve in the next chapter and another thing we shouldn't hard code values here right we should do something if we do we should have something meaningful like untitled project but it noticed that this appears to be a testing or debugging code. So perfectly fine for us at the moment. And the same advice here to not use a non-null assertion operator.
But as I said, if we actually do forget to add this variable, the project won't even start. So this kind of that kind of acts like a runtime error in itself. Very good review. Let's go ahead and merge this pull request and we're going to fix those issues in the next chapter. Amazing.
So we already have three branches here. Actually, yeah, three of them, including the main. And now once we've done that, we should go back here, check out back to the main branch and git pull origin main so that we pull those remote changes. There we go. And in order to confirm everything is okay, double check you are on your main branch right here and inside of your graph control you can see that the same behavior happens.
We checked out to a new branch for 0.3 and then we merged it back to main. Amazing! So let's go ahead and recap. We have set up convex account in a new project, installed Convex SDK and CLI tools, created table and tested out CRUD operations, and finally configured Convex provider with Clerk authentication. Amazing job and see you in the next chapter.