Let's go ahead and let's create the schema for our boards. So for that I'm gonna go ahead inside of my convex folder right here and I'm gonna create a new file called schema.ts. Inside of here let's go ahead and let's import v from convex values and let's go ahead and import define schema and define table from convex slash server like that. And then let's go ahead and export default define schema here. And let's add boards to be define table.
And let's add a title to be a type of string. Let's add organization ID to be a type of string as well. Author ID to be the same thing. Author name as well. And we're also going to have an image URL.
And now let's add an index for faster querying by organization. And let's define the field to be organization ID and let's also add a search index for searching so we're going to add search title like that and let's define the search field to be title and let's also add filter fields to be organization ID because we only gonna search inside of a single organization grid. So we have our initial board schema as simple as that. And just ensure inside of your terminal that you have npx convex dev running. And now once you do some changes here you can see how that's going to tell you that convex functions are ready.
If you did anything wrong you're gonna get an error here and I believe that already if you go inside of your convex you're gonna see right here I have boards right here I can click on show schema and there you go you can see the exact code which we've just wrote inside of our code is now visible right here in this dashboard. Great! So now what I want to do is I want to go ahead and prepare all the possible images that we're going to use for our boards. So for this tutorial I picked 10 random images from Undraw which I will also leave a link in the description. So here is what I want you to do.
I want you to go inside of my repository or simply go to Undraw and in here I want you to download this entire placeholders folder. So basically all of these files inside. So I'm going to pause the video and I'm going to add all of those inside of my new public folder. So I'm going to go inside of the public folder and I'm going to create a new folder called placeholders. Like that.
And I'm going to download all of these files here. And there we go. So I just dragged and dropped all of these files here. So make sure that you have at least a couple of them. The more you have the more random they're going to be every time you create a new board and just ensure that they are inside of your public folder under a placeholder subfolder.
So let me just close this and try and expand this. There we go. So public folder, placeholders and inside I have 10 images all from Undraw. So I'm going to leave the description for that as well or you can simply download them from my GitHub repository. Great!
So now let's go ahead and let's create our an actual API route which is going to be used to create a board. So for that we do something as simple as going inside of convex and creating a new file called board.ts. And this is our new API endpoint. And then in here we can do export const create and call a mutation and we can import mutation from dot slash generated slash server like that and let's also go ahead and let's import V from convex values like that and now inside of here let's go ahead and open up this mutation and let's go ahead and pass in the arguments which we expect every time we create a new board. So we expect the organization id which is going to be v.string and we also accept the title which is going to be v.string as well.
And now let's go ahead and add a handler function which is going to be an asynchronous function which has access to the context and the arguments from above. And now with this we can go ahead and get the user identity. So identity is equal to await context out get user identity and then in here we can check if there is no identity throw new error unauthorized. There we go. So now we have full identity, full authentication inside of this convex backend which you can see is not in route handlers but in its own folder so that's why we had to do the auth.config.js with the template and all of those other configuration elements.
Perfect! So now I want to create a little array of images here and what I'm going to write inside is basically a list of all the images which I have inside of my placeholders right here. So let me go ahead and show you how this is supposed to look like. So your images should look like this. Placeholders slash 1 slash 2 3 4 5 6 and make sure that you add a root slash right here at the beginning and then the name of your folder and then the name of the image you are using.
So just make sure that that matches. So you don't have to write public but you do have to write slash placeholders and then simply make sure that they match inside. Great! So now what we can do is we can pick a random image from this array of images right here. So let's go ahead and get a random image.
And let's make that images and then math.floor math.random and let's go ahead and simply multiply the random function by images.length. There we go and now we have our random image and now we can write const board to be await context.database.insert and choose the schema which is boards and very simply pass in the fields we need. The title is going to be arguments.title, organization ID is going to be args.organizationid. We're going to have an author ID which is going to be identity.subject and we're also going to have author name which is going to be identity.name and let's also pass in the image URL which is going to be random image. And since we know that our user will have a name you can go ahead and put an exclamation point right here like that and in the end just return a board and there we go that's how easy it is to create our API route with convex Now what we have to do is we have to create a little button right here which will fire...
Well we have to create a functionality that when we click on this button it actually calls this API mutation and then we're gonna go ahead inside of right here in the database and see if that has created it or not. So let's go inside of our empty board component. So inside of app folder dashboard components we have an empty boards component right here and let's go ahead and mark this as use client and let's go ahead and let's import use mutation from convex react and let's also import API from convex generated API and then what we can very simply do here is const create to be useMutation API.board.create so you can see how we have exact typings here. And then what we can do is add const onClick create and let's pass in the title to very simply be untitled like that and let's pass in the organization ID that's gonna be, we can get that from organization right here using use organization from Clark Next.js like that. So inside of here, let's break the function if there is no organization like that and then we can safely use this organization whoops dot id here and there we go we no longer have any errors and now let's add this on click right here on click there we go so let's check it out now if I go ahead and click create right here and if I check it there we go you can see that it is immediately added inside of our database We of course cannot see it here because we are not rendering any of them.
If you are not seeing this make sure that you are importing use mutation from convex react and make sure that your typings are correct api.board.create you can see how I can click here and it will lead me to that mutation right here and also you can always check your terminal just to confirm that there are no errors in your convex functions right here. Great! So we have that now and you can see that immediately once I click here it adds a new one. So here's what I want to show you next is that what I like to do is I like to add a little loading indicator while this is happening even though it is extremely fast. But I kind of don't want to do it every single time.
So I'm going to show you how we can create a reusable mutation hook so that we always have our loading exported from here because right now we only have the action itself. So let me show you a way we can do this. So let's go inside and create just a new folder in the root of our application called hooks and inside I'm going to create useAPIMutation.ts. Let's go ahead and let's import useState from React. Let's import useMutation from convex React.
And let's export const useApiMutation. We're going to pass in the mutation function to be a type of any. And let's define the pending and setPending here to come from useState, which is false by default. And let's define the API mutation to be useMutation, mutation function. And then let's add const mutate to accept a payload of type of any.
Let's do setPending to be true. And then let's return API mutation. Let's pass in the payload. Let's add .finally. Simply setPending back to false.
And .then will get the result and very simply return the result. And if we get an error, Let's go ahead and throw that error back. There we go. So what did we achieve with this? Well, you're going to see in a second.
What we have to do from this hook is return mutate and pending like this. And now what we can do inside of our empty boards, instead of having to call this use mutation, and then in every single on click, we would have to manually add pending here, and then we'd have to do that finally here and change the pending back to false and we would have to do that for every mutation that we do. What we can do instead is now use the following. Use API mutation from our hooks API mutation and then in here we now have execute, sorry mutate and pending. So we can very simply just called mutate here which will work exactly the same and then on this pending right here we can add disabled pending like that.
So now if you try this, you can see how for a second my button is disabled and let's just confirm that this is still working. Now you can choose whether you want to use the first method or the second method. Both will work for this tutorial, they're not that terribly important but I kind of like my method. The only thing that I really don't like is that I couldn't get the types to work. So we lose on the types which is pretty important But I just think it's fun for a tutorial for us to explore how to create this custom abstractions around hooks So you learn something new this kind of reminds me of using 10 stack query mutation hook where we have you know Mutate and the pending state here if you don't like it if you think this is a too big of a trade off by using any, feel free to use the initial version in where we use the use mutation here directly.
And then you simply have, you know, mutate or whatever you want to call it here. Like that. And then you simply won't have access to this disabled spending. So it's your choice. I'm going to be using use API mutation throughout the tutorial.
So you can either write it from here or copy it from my GitHub. Great. So now that we know how we can create our boards let's go ahead and add some toast notifications so it indicates to us whether this was successful or not. We can do that by going inside of our terminal here. Let me just open a new one.
And do npx chat-cnui-app-latest-add-sonar. Like that. So that's going to add the sonar-toast package inside of our project. So we now have one file to add, which is called toaster. So go inside of the app folder, layout.tsx, and let's import toaster from components UI Sonar.
And then we're simply going to go ahead and render that. Well, you can just render it inside of the body or inside of convex client provider like this. And then let's go inside of our empty board where we added this on click right here and very simply what we can do now is we can attach a dot then from where we're going to get the id of a newly created board and let's do toast which you can import directly from Sonar like that we're gonna do toast.success board created like that and I'm gonna add a to do redirect to board slash id And then let's add .catch and this one is already simply going to call post.error something, actually let's be more specific, fail to create board. Like that. There we go.
So now, when we go ahead and create a new board, there we go. We have a message board created and that is true right here. Perfect, and you can see how we have the matching organization ID, the title is untitled by default. And I believe that most of this will have a different image placeholder. I don't know if I can expand this so we can actually see that.
But nevertheless, we're going to be able to see it in a second when we actually start rendering these things. And here's the cool thing about convex. So at any point you can click on this functions right here and there we go. You can see that the functions which we wrote inside of our VS code are actually visible here. Even the images array is visible inside of convex.
So that's what I think is really amazing about convex is that it manages to replicate your local code inside of its cloud right here. And in here you can see the exact operation which we wrote. So it's very very easy to debug things in production. It will be amazing if we had this for every database that we are using. You can also manage the invocation, the errors, the execution time.
Let's go ahead for example and add a little console log to that mutation of ours. So we're gonna go inside of convex board.ts. For example, let's add console log random image and let's do test here. And I believe that we're already gonna be able to see those logs. So if I create something first let's see if this has updated.
There we go you can see how it has immediately updated the code right here. So if yours hasn't make sure that in your terminal you are running npx convex dev or if you're having any errors fix the errors or simply restart the terminal. And let's go ahead and click on logs right here. And I think we should be having some logs. I'm just not sure how I can access them or where but I did manage to do this initially I think.
Nevertheless you can explore this yourself not only you have that you also have like scheduled cron jobs and a bunch of different things which can run in the background It also has its own file upload. So it's a very powerful database and all of it is completely real-time. So you're going to notice later when we add a query here to load our boards, we're never going to have to refresh that query. It's just automatically going to pull the newest from the database. So the whole application has a very optimistic feel which will fit perfectly into what we're trying to create here as a Miro clone.
Perfect. So what we're gonna do next is we're gonna go ahead and actually display our boards right here. Great, great job!