Now let's add the functionality to actually enable this create button. In order to do that, we have to revisit our project schema. So let's go inside of source, database, schema. And now we're going to go ahead and create projects. So let's go all the way down here.
Let's export const projects and let's use pg table projects. Actually let's write project. Why? Because I want to be consistent to our existing schema. So we write authenticators and then the table is a single version of that authenticator so I want to do the same thing here projects project and let's just not mess this up like this okay and now the second argument are going to be the actual fields so the ID will be text ID dot primary key and we're going to add a default function here which will use the crypto library to call random UUID besides the ID we're going to have name so we have to put a comma here.
The name will be text and name and it's not going to be required right because we can create empty projects. So let's also now add a user ID which is also a text and it's going to map to the actual user here. So I know that the ID is a text. So that's why I can write the same text here. So let's add ID not null.
So it's required to be related to some user. And let's add references to users.id. So users is our schema here. So it's referencing this ID here. And now we're going to add some rules if the user itself gets deleted we're going to cascade all relations they have with their projects And now that I think of it, we can actually make name required for our project.
So let's add not null here as well. Besides the user ID, we're gonna have JSON. So JSON will be text, JSON, and also required. And this is actually going to be the thing that we usually export, you know, in a .json format. That's what we're going to save in our database this time, in this field.
Now let's add height, which will be an integer. I'm not sure if we have the integer imported. Let's see, we do. Alright, make sure you have from pgCore, boolean, timestamp, pgTable, text, primary, here, and integer. So just make sure you don't have any imports for MySQL core, for example, right?
You should have everything from pgCore. So the height of the project will be an integer and we're going to store it as height like this. And it will be required. Same thing will be for... Oops, I have a misspelling here.
So let's copy height. And this will be width. And the column will be named width. There we go. Then we're going to have a thumbnail URL, which will be text thumbnail and let's see, let's be consistent, So they use camel case, I believe.
Yes. So if our next out used camel case, let's also do the same thing. So we're going to add thumbnail URL like this, and we're not going to give it any additional rules right so you don't need to have a thumbnail and now it's at his template which will be a Boolean which we do we have we do have Boolean. OK great. So this template is a Boolean, isTemplate.
Like this. Besides that, we're going to have isPremium or maybe isPro. So it's only available for pro members if it is a template, right? And then we're gonna have created at, which will be timestamp. We already have this imported, created let's see how they use it do they use it anywhere looks like we don't have it oh yeah looks like it's also a mix of camel case and snake case but okay let's add created at here mode date and let's go ahead and add not null and then copy and paste this for updated at and make sure you name your columns correctly right so don't make sure that each of your column has unique name Make sure you didn't accidentally copy and paste from one and then forgot to change it.
There we go. Now let's go ahead and let's define ProjectsRelations which will use the Relations API from DrizzleORM. So make sure you added this import here. The relations for the projects will have one right here and go ahead and return an immediate object like this. We are going to have a relation with the user so each project can have one user which works on the user's schema.
It uses the fields projects user ID field and it references the users.id field like this. There we go. And now let's go inside of our users here and let's export const users relations to be relations users, the structure many and yes, many and return an immediate object. And very simply user can have many projects like this. There we go.
And to be honest I don't fully like the Relations API from Drizzle ORM. I don't think it's exactly clear because for example we already have relations with accounts here. You can see the references to the user ID and to the sessions and things like that. So I'm not sure whether the Relations API is optional, additional, or I'm not exactly sure. Perhaps just writing projects and user ID here would be enough.
I don't know. So maybe we don't even need the project's relations here. And that would mean we also don't need the user's relations here. But I'm not entirely sure. All right, so I quickly googled.
So let me go ahead and copy this URL. Okay, let's go. So foreign keys, you might have noticed that relations look similar to foreign keys, right? So that is this. I just explained that I'm not sure whether I need to add relations if I already use this, references, right?
And they look like they have a segment in that. So relations look similar to foreign keys. They even have a references property. So foreign keys serve a similar purpose, defining relations between tables. They work on a different level compared to relations.
Foreign keys are a database level constraint. So this instruction right here, references, This works on a database level. If you modify this you will have a new migration file and you will have to do a proper migration but as I understand this itself does not actually affect or work on database level at all and it will not change the schema. So foreign keys are a database level constraint. They are checked on every insert, update, delete operation and throw an error if a constraint is violated.
On the other hand, relations are higher level abstractions. They are used to define relations between tables on the application level only. They do not affect the database schema in any way and do not create foreign keys implicitly. So that means relations and foreign keys can be used together but they are not dependent on each other. You can define relations without using foreign keys and vice versa.
So that kind of answers the question I hope. It looks like technically we don't need the relations API so we don't need to write this and I assume we also don't need to write this at all but I'm gonna stay true to my source code I'm gonna write it like this and I think there is one advantage to writing the relations API And that is when you actually go and query things, right? So if you go ahead and start querying things, so let me go ahead and find first, right? So if you wanna use this kind of querying, because Drizzle often is the relations API, right? Which we're not gonna use in this tutorial.
But if you wanna do things like this, which is more similar to Prisma, I'm pretty sure that you will need the relations API because as we just read, they work on application level. Great. So I'm just gonna leave it like this. As we've just learned this actually does not affect the schema. So later, I think you will be able to safely remove this if you don't find any use for it.
Same thing for this. But now what we have to do is we have to actually run our migrations. So let's go ahead and do the following. Monrun database generate which creates a new SQL and then monrun database migrate And that will add the new projects to our database. So let's go ahead and look at our drizzle here.
There we go. So as you can see, we added the project here and you can see that we only added a constrained project user ID, user ID foreign key, right? And the delete cascade rule. So we didn't actually add any of our relations API exactly as they've written. Great.
So let's go ahead now and do the following. I want to first of all confirm that this works correctly by simply going inside of my terminal and running Boundary Run Database Studio. This is always my sanity check to see that everything is fine and I can continue developing. So now there we go. I have project and my user should also have projects and they do right here.
Great. We can close this now. And now let's go ahead and let's create an API endpoint for creating our... Let's go ahead and let's do bandrun dev for creating the projects so let's head inside of our source app API right here inside of the route catch all create a new file projects.ts. Let's import Hono from Hono.
Let's create a new app, new Hono and export default app. Now let's go inside of route.ts let's import projects from projects and let's chain the route with the prefix of projects and the projects route group. Great! Again, ensure you have getPostPatch and delete requests here And now let's go ahead and let's create a post method to create a new project. So let's go ahead and add post like this.
I'm gonna collapse these so that it's more readable. So this is the first argument and then we're gonna add verify auth. So I only want authorized users to be able to access this endpoint after that I'm gonna have a z-validator from Hono Zod validator and we're going to validate the JSON field using Zod, so add the Zod import as well. And now let's write our schema. And here's a cool thing, actually, we don't need Zod.
My apologies. Yes, this is the cool thing that I completely forgot about. So if you go back inside of schema you can actually create a schema for Zod from projects themselves but in order to do that you need to install a package called drizzle-zod. So let's do that. Bun add drizzle-zod.
It's just that one package that we need and then go ahead inside of your schema here and import create insert schema from drizzle Zod like this create insert schema then go all the way down to your projects and after projects relations you can go ahead and add the following export const projects insert schema create insert schema and pass in projects. Like this. Just make sure you do the export const. And you can save this. And now in your projects route, instead of using Zod, we can simply use the projects insert schema right here.
And since we won't exactly need to pass the ID from the front end or update it at fields, we can also go ahead and execute the .pick method on it and select exactly what we need. So I need name, I need JSON, I need width, and I need height. So those are the elements I need to create my project. Now let's add an asynchronous controller here and first thing we are going to do is get out from c.getAlfUser like this and then we can also extract all the necessary fields from C request valid JSON. Those valid fields will be name, JSON, height and width.
Now let's go ahead and let's check. If we don't have out the token question ID, let's return c.json with an error unauthorized and a 401 status code. Now let's go ahead and add data to be await database which you can import from database drizzle so let me go ahead and separate these imports here. Insert projects which we import from the database schema. The values which we are going to insert are the following name, JSON with height, user ID, which will be out the token.id, created at which will be new date and updated that updated at and if you want to return this back you also have to explicitly write .returning like this and Drizzle always returns an array because Drizzle works like SQL does.
SQL will always return you an array of items. So now let's go ahead and write return c.json data and then first in the array. There we go. Great. And we can also go ahead and add an additional check here.
So if we don't have data first in the array we can return c.json error something went wrong or how like this there we go now that we have this let's go ahead and let's turn this into a mutation. So let's copy an existing one for example features let's see what we have here we have a use generate image so we can copy this one and let's create a new features folder which we're gonna call projects and inside API folder and use create project.vs. Let's paste, oops that's how I copied it, okay so just copy the contents of use generate image and paste it inside. And now let's go ahead and let's rename this. So our response type is the first thing we have to fix.
So it's not going to be .ai, it will be .projects. And same thing for our request type .projects. So this is how it looks like in one line. The hook name will be use create project and we are also calling the dot projects here. Great!
Now let's also check if response is not okay we're gonna throw a new error something went wrong like this now let's go ahead and let's add our own success methods here which will do the following it will call toast.success project created Let's also import toast from sonar Oh, this should be together, my apologies. Let's also add on error here. Post.error, failed to create project. And let's also add a to do here, invalidate projects query. We don't have that yet so that's why I'm adding a to-do because otherwise it's going to be a little bit confusing.
Now let's revisit our banner component because Banner Component has the button to create a new project. So let's go inside of App Folder, Dashboard, Banner. And inside of here we're going to do the following. We're going to mark the Banner Component as Use Client. And then we're going to import use create project from features projects API use create project.
Let's create a mutation here, use create project and now let's go ahead and right on click to call mutation.mutate and we're gonna do the following. So for the options array, or the body array, we're gonna give it the name of UntitledProject. JSON will be an empty string, width will be 900, and height will be 1200, like this. And then we can add an onSuccess which goes where does the onSuccess go my oh so yeah we have to open the options and then we write onSuccess. So yes, you can add an additional onSuccess on top of your existing onSuccess here.
So you can just extend it. So onSuccess, I want to destructure the data. So the data in here, let me go ahead and properly write this function okay it says that the onSuccessData does not exist on response types so did I forgot to do something yes I forgot to do something in useCreateProject right here if I hover over my response type you can see that it can be a type of error because remember our projects here also handle errors if we weren't able to create one or if we are not logged in So what we have to do here for the response type to be valid, because we're gonna handle errors here explicitly, we are going to mark this with a comma 200. So let me show you how this looks like. So now response type is only the success message, right?
But if you remove this, then response type can be the error or another error or data. So in here we are explicitly gonna sell, yes, we want the response type to be the success response type. So now inside of this on success you can see I have the data and more importantly I have the ID which means that I can do the following and for this I just need my router so const router use router from next navigation so let me move that here we are simply going to do router.push slash editor data.id like this and now let's go ahead and let's add on click to this button here and let's go ahead and add disabled if mutation is pending like this Let's go ahead and try it out now. So I recommend you do the following. Make sure you have your app running and also open your studio so you can see the project being created.
Let's go ahead and try that out. So inside of here I'm going to refresh my entire thing. I have my Drizzle Studio here and my projects are currently completely empty. So let's go ahead and try it out. Once I click start creating, you can see it's creating something.
It has project created message and now it's compiling that page, so let's just give it a moment. This is of course only in development because it doesn't automatically compile the sites we are not using and any second now I believe I should get redirected and notice my URL localhost 3000 slash editor and now I have my project ID inside so usually we've wrote you know slash editor slash 123 but now we have the actual project ID. And now let's go ahead and check out Drizzle Studio. So if I refresh my projects page here, there we go. I have an untitled project and JSON, which is an empty string.
I have my height and my width. And I also have a relation with the user that created this project. Great! So now this gives us a ton of more options to work with. Primarily the first thing we actually want to do is that when we detect that we have an ID inside of our URL, that's the project we want to load.
Because right now it does not matter which ID is in the URL because inside of our app folder our editor project ID does absolutely nothing with that project ID. It simply loads a new editor every time. So that is our next step. Our next step is to load the JSON from a project and to be consistent with the pages with the project it loads. And then we're gonna add autosave functionality.
Great, great job.