In this chapter, we're going to work on workflows, create, read, update, and delete methods. In order to do that, we first have to update the workflow schema. And then we can create the workflow TRPC API routes. Let's start by updating the schema. So head into schema.prisma located in the Prisma folder and find your workflow model.
Inside, we just have ID and name. Now let's go ahead and extend this by adding created at which will be a type of date time with a default value of now and then let's add updated at with the same value and a decorator updated at And now what we're going to do is we're going to make a model belong to a user. We're going to do that using relationship and foreign keys. So let's add user id field which is the type of string and then I'm going to expand my screen a bit because this is now a longer line. User is a type of user referring to the model above which we have called user so user type of user decorator relation Targeting the field User ID which we defined above.
It's referencing to field ID within the user model. And then let's add on delete here cascade. So when the user gets deleted all of their workflows will be deleted as well. That's what on delete cascade does. So we will never have a workflow which doesn't belong to anyone.
And now that we have that relation set, we have to create an equivalent relation in the user model. So let's find the user model again and just as we define sessions and accounts, let's add workflows. Workflow and an array. And you can see that the moment you save, you will no longer be seeing any errors here. So I think that is enough for now in regards to the workflow.
You can save this file and inside of your terminal let's do npx prisma migrate dev. And let's call this workflows improvement or maybe workflows update something simple here we go after that you can do npm run dev all make sure your next JS server starts successfully now that we have that let's go ahead inside of our source folder, features, and in here let's create a new folder called workflows. Inside of here another folder called servers and finally routers.ts inside. Let's export const workflows router to be create trpc router from trpc init and then add a create procedure here which will be for now a protected procedure from trpcinit. It's going to be a mutation and in here we're going to execute a function.
In the params of that function you can extract context and then what you can do is return prisma from lib database dot workflow dot create. Open the data object, give it the name of todo and let's go ahead and give it user ID of context dot auth dot user dot id just like that that's all the things we have to pass in order to create a new procedure. Now I do want to slightly modify this by not having to write to do for the name. We can do that by installing a very simple package. Npm install random word slugs.
It's a nice little package, which I often use in my projects to very simply just generate random words, which looks familiar to what big apps do. Generate slug from random word slugs. You can configure this generate slug function quite extensively actually but I will just keep it to three words as simple as that. Now that we have the workflows router we actually have to go back inside of our underscore app folder in source drpc routers and we get to finally clean this up. Let's remove everything inside.
So just an empty app router and just map workflows to be workflows router like this. And then finally, you can remove all the unused things from here. That includes the base procedure as well as the two other procedures. There we go and now we have just a simple create procedure here Now let's go ahead and add the delete procedure which I like to map as remove instead of delete simply because delete is a reserved keyword in JavaScript. So I just like I feel safer using remove.
So let's go ahead and add a protected procedure here as well, mutation. And again, we execute a function here. And let's go ahead and simply destructure context from here. And let's return prisma.workflow.delete where, and let's go ahead and do userId matches context.userId. So only the user who actually created this workflow can be the one to delete it.
But the only problem with this where query is that user id doesn't make a workflow unique so we also have to add something before we define the mutation. So besides mutation, we are also going to have .input and that's gonna be using Zod. So make sure you add Z from Zod, Zod.object, ID Z.string, like this. And then besides the context, you will also have the input and then you can assign the ID to be input dot ID and this will then attempt to delete the workflow using this ID and the currently logged in user. So if there is a mismatch between the two, it's going to fail, either because the ID is not correct or because we don't have permission to delete this because we are not the person who created it.
We are not the author of the workflow. So yes, you can do that very easily by just properly querying inside of Prisma. You don't have to first query it by ID and then check if the user ID matches, except if you want to throw specific errors but for now this is okay. And now let's go ahead and let's create a simple very specific route which I'm going to call update name. So update name again a protected procedure with an input here so Z dot object and it's going to accept an ID just as the previous one did but also a name with a minimum of value of one.
So at least something has to be passed. And again, it's going to be a mutation. Let's go ahead and destructure the context from here. And then in here, return Prisma workflow update, where and data is what we need in the where make the id be input.id which we also need to destructure from here and then we can properly do it here and user ID context out user ID and the data will very simply just update the name and just like that we've created a secure authorized and authenticated endpoint to update a workflow name. I am very purposely separating just the name update because later updating the nodes will be a little bit more specific.
And I think it's smarter to separate these two endpoints to keep them simple and well not exactly to keep them simple but just to keep them one for each purpose. It's easier to maintain this way. And now let's go ahead and let's do get one here. So get one will be a protected procedure and in let's go ahead and just add a query here and we can also extract the context here and let's just do return Prisma.workflow.findUnique which means it will either find this or it will throw an error. And then where we're also missing an input.
So let me just copy the input from my remove procedure because we are looking for a very specific one, right? So we need the ID. Where ID is input.ID, user ID is context.auth.userID like this. And then let's go ahead and copy this and change this to be getMany. For this one, we don't need an input now.
We will need it later for all the various querying, like searching and things. But for now we can just do context, find many and change this. So it will now load all of this user's workflows. So this is obviously a simplified version because we will have pagination too. Right now this doesn't have pagination, it doesn't have anything, but we will do that later.
For now I just want to have all the methods here. And I think this is enough for us to start testing these routes. So what I want to do now is for each of these actions, I want to create an equivalent hook that we can call. So inside of source features workflows where we just created the server, let's go ahead and create hooks folder. And inside of here I will add use-workflows.ts.
And let's start with a very simple one, a hook to fetch all workflows using suspense. So I like to add comments like this. Export const useSuspenseWorkflows. I like to be specific because it's important for developers to know whether they are using suspense or not. So TRPC use TRPC.
And let's go ahead and just return use suspense like this, trpc.workflows.getmany.queryoptions, like this. So this will now be used as a reusable use suspense workflows hook, which fetches all the workflows using suspense, which means it needs to be wrapped in the proper hydration boundary and suspense and all of those things. We are basically going to be using The third option of our, whoops, the third option that we discussed, if you remember, in TRPC chapter, we explored TRPC with server and client using prefetch. So for that exact scenario is what we are preparing right now. We are going to use the best of both worlds if you remember.
So for now I think that just this one actually might be enough. So I'm going to stop here because I just want to start demonstrating how we are going to use this. So let's go ahead now inside of workflows here create a new folder called components and inside of here create workflows.tsx like this And then let's go ahead and let's return a very simple, let's just do a paragraph, JSON stringify workflows.data and just some other things here to make it format in a prettier way like this. So we are later going to create this in a proper component but for now it's just going to be an indicator for any data. Now let's go ahead and render this within a workflows page.
So I'm going to go inside of source app folder dashboard rest workflows page dot TSX. And now in here I'm going to go ahead and prefetch my workflows. Now the way I'm going to prefetch my workflows is by creating a reusable prefetch util just like I did with my hooks. So inside of features workflows we have the server folder and let's just add prefetch.ts inside because this is relating to the server side. And let me just see, I think, yes, one thing that we are missing here is let me just try and quickly check inside of TRPC server yes we only added caller We didn't create any other helpers here.
So let me quickly open TRPC docs and hopefully we can find what I'm looking for. So because I want you to see exactly where I find my information. So click on 10 stack react query and then click on server components and we already followed this entire process here but if you scroll a bit down you will see these two hydrate client and prefetch. So if you can find this, copy it and paste it. If you can't, pause the screen and you will be able to see what I'm talking about.
So you can also create a prefetch and hydrate client helper functions to make it a bit more concise and reusable. So that's the purpose, because very often we're going to use both of them. So let's save some time here. So in the server here, I'm going to add the prefetch function and we need to import tRPC query options from tRPC 10 stack react query like this. So make sure you have that and now you have prefetch right here and then let's also copy hydrate client function.
For this it is very important that your server file has .tsx extension. If it doesn't change it. So make sure you can pause the screen again if you don't have this. The only thing we had to import was the DRPC query options and then below this add hydrate client and in here import the hydration boundary from 10-stack react query and dehydrate from 10-stack react query. So let me show you hydration boundary from 10 stack react query and dehydrate from the same place.
So make sure you have this. And make sure you have this. Again, you can pause the screen if you were not able to find it in the documentation, but it should be there. Great. Now that we have that, let's go ahead and create our prefetch here.
So the first thing I want to do is I want to import type, infer input from trpc, 10-stack react query, and then I'm going to import prefetch from trpc server as well as trpc itself. Then I'm going to create a type input which will infer the input type of the RPC workflows get many. So I have the exact type that will be accepted for my get many. At the moment, this makes no sense because we don't have an input. But later when we add an input, which will have search and things like that, it will make sense because this input will be exactly typed as we need it.
So we never have to repeat ourselves so let's go ahead and add a prefetch all workflows util export const prefetch workflows accepting the params which are input and return prefetch trpc.workflows.getMany.queryOptions and pass in the params. So this now works, right? The query options don't exist. They are nothing, right? It's the same thing if I do this.
But imagine I changed this to be .input here, z.object, search, z.string, something like that. You can see that now it's expecting that but I don't want to remember which are the props that I've passed so I created this which will automatically infer that you can see how it added search so I can safely just pass params here so even if I remove it it still works I hope that clears it up It's not fully clear because we don't have any input here, but that's how you can infer the input type for any TRPC procedure that you have, which is quite useful. Because imagine if I had to manually add search and then string here right and then imagine I had a million of these so it's just easier to reuse the types even if they're completely empty and the type of void So now we have prefetch workflows and I can go back inside of my dashboard REST workflows page.tsx here with the workflows paragraph and now what I'm going to do is just do prefetch workflows like this. I didn't have to pass anything here, but usually this is where params would be accepted and Now that they are prefetched.
I also have to properly Render these by adding hydrate client from the RPC server, which is another util we just added and in here let's add the error boundary now we actually don't have this so don't import it from anywhere The place to import this is a third-party package. So let me quickly install it. Npm install react-error-boundary As far as I know, it is the most popular package for react-error-boundary. So let's now import error-boundary from react-error-boundary like this. And passing the fallback here to be error and then add suspense from React simply because we are using use suspense workflows so we need to wrap that component within suspense and for the fallback here let's go ahead and let's use loading.
And finally let's render the workflows list. So make sure you import the workflows list from features, workflows, components, workflows. And inside of workflows list, we are using use suspense workflows. So because of that, we can safely wrap suspense around this. And if it fails, it will show the error.
And we already prefetched this in the server component, which is asynchronous and loads faster. So it will have that effect that we first learned in chapter 3 where I demonstrate you how this is the best of both worlds and you will get very good results here. So let's refresh workflows and we should just see either an empty array or an error as I am getting let's see why attempted to call use TRPC from the server but use TRPC is on the client I think what I forgot is in the workflows list I have to mark this as use client there we go And now let's refresh and try this again and there we go you saw the loading fallback and then an empty array. Great. Now let's go ahead and add an ability to create a workflow.
So the way our workflows container here is going to look like is exactly the same how our credentials and how our executions container will look like. So in my opinion, it makes sense to start creating some reusable components here. The way we are going to do that is by going inside of source components and in here create entity views.tsx or maybe entity components would be better. So entity refers to workflow, credential, execution, right? Because all of them will reuse these components.
So I want to start by having one called entity header And entity header is going to have a couple of props. So let's define entity header props. Let's go ahead and define this. So entity header props is an interface and it's going to accept the following props. Title, optional description, optional new button label, optional disabled prop, and optional is creating Boolean.
And then let's extend it even further by giving it some very specific combinations of types. So if the user passes on new, which is a type of just an arrow function just avoid in that case new button href should be never like this and let me just check if I am doing this correctly. This should be a type, my apologies, not an interface and yes, do this. So if we pass on new, it means that inside of the entity header, the add button, as in add new workflow, will be done using a function And then we should not be able to pass the link for the new button screen. But if someone passes a new button href first and it becomes a type of string in that case on new should be a type of never so it can only be one or the other and let's also do on new to be never and new button href to be never.
So one of them is required. It's just up to the developer what they're going to choose. So now that we have that, let's go ahead and let's extract all of those props. Title, description on new, new button href, new button label disabled and is creating and in here let's go ahead and let's return a div with a class name flex flex column and then an h1 element which will render the title. And now let's give this h1 element text-large, onMedium text-extra-large, and font-semi-bold.
And now, before we do anything with the rest of the props, I want to reuse this. Actually, what I meant to say, I want to use this. So instead of features, workflows, components, workflows.tsx, let's go ahead and let's create the workflows header so export const workflows header will accept only disabled did I do this correctly Let me just check what am I doing incorrectly here. I think it's just because I didn't like complete it. Yeah.
OK. So in here let's return empty fragment entity header from entity components which we just created and in here I'm going to pass the title to be workflows like this and a description to be create and manage your workflows. And then you will see what I was talking about. If you pass on a new which can be a function in that case new button href should not be passable. You can see I have an error, right?
So you can only do one of the two. And if you pass new button href, then you can't pass on new because this will be like workflows new. If you want that, then you will be able to pass this. So this is just like a little exercise on having good developer experience when writing your components. So for on new, I will just do this now.
New button label is going to be new workflow, disabled will be disabled, and is creating is going to be false. And keep it inside of fragment for now, even though it makes no sense. It's simply because we will add some other elements later. So that's how we are going to reuse these entity headers. Later when we have credentials, we are going to reuse it and have the title credentials, description, create and manage your credentials, new button label, new credential.
So we don't have to write this entire entity header a million times right I think after you see how we are going to use this you will agree with me now let's check if we have the description So we are now completing the entity header component. If we have the description, let's add a paragraph here and let's render the description inside and let's give the paragraph a class name of text extra small on medium text small text a muted foreground. And then let's check if we have on new and if we don't have new button href, that means we are intending to use this button which we have to import from forward slash UI button right? Because we are already in the components folder so if we have that in that case it means that plus icon should be rendered here make sure you added this from Lucid React give it a class name size 4 let me just remove the extra space here and let's render a new button label and now let's go ahead and give this disabled is creating or disabled Size small on click on a new. And just to make this more readable I will collapse these props.
But basically you see the point if the user passes on new then we are going to immediately execute that function when this button has been clicked. And we also should have a fallback for the new button label. Or maybe we don't. How about we just make the new button label required. This way we don't have to have a fallback.
It will just be required for everyone. And now let's copy and let's paste this and let's reverse the situation. So if you have new button href and you don't have on new, In that case, the button will never be disabled. It will not have on click. Instead, it will have as child and we're going to use link from next link to redirect the user to an href which will be newButton href.
And let's prefetch that so it's faster. So make sure you've added a link from next link. So that is how this is going to work. Basically if inside of the entity header someone passes newButton href, in that case we're going to serve the button as a link to redirect to wherever we intend to, or if we have an exact function to create a new thing, we're just going to execute that function. Great, so that will be our reusable entity header.
Now we have to create two more reusable entity components here, but I think it would be a good idea to start to render this simply so you can see what we are doing because it's quite hard to develop this way when you don't really know what you're doing. So after workflows header let's go ahead and do export const workflows container here. And inside of here let's simply render the children give it a type like this and inside of here we have to render entity container which will render the children and give it a header option which will be workflows header like this and then give it search which will be an empty fragment and give it pagination which will be an empty fragment as well. So in order to see our workflows header we have to create the entity container first which thankfully isn't too difficult so let's go back and set up the entity components here and let's do export const entity container. Let's go ahead and return a div and let's import entity container here just so we stop getting the error for its existence and now let's go ahead and properly give it the props so I'm just going to copy the type here I'm going to add it here and I'm going to change this to be entity container props.
Now the props will be thankfully a little bit simpler so we can just add these and let me go ahead and just add prefix to all of this so it's react.reactnode so children optional header optional search optional pagination and let's just assign entity container props there we go let's extract all of the props children header search and pagination here. And then finally in here let's render the header. Now let's give the top div a class name padding adding 4MDPX of 10 MDPY of 6 height full and let's give this one MX auto MX max with screen extra large full width flex, flex column gap y8 and full height like this. And now that we have the workflows container which renders the entity container which we just developed here and allowed it to render the header for which we used workflows header for which we used entity header. Now we can finally go inside of source app dashboard rest workflows page and in this page we can now wrap the entire thing in workflows container like this import workflows container from features, workflows, components workflows and this is what you're going to see.
Workflows create and manage your workflows and a new workflow button So this will now be a nice chunk of reusable code for each of these entities credentials executions right that's why we are spending a little bit more time on it than we usually would is to create a reusable structure right so we have this entity container which has this exact layout that we need and if we need a very specific header we can just pass a different header here so now that we have workflows header let me just see one thing one thing that I'm noticing is that I'm no longer seeing the body here and that is because of entity container so let's finish entity container so we can actually see the body that we are passing here So after the header here we have another div with a class name flex flex column gap y four and height full and inside of here we should render search if it exists and we should render the children and below that we should render the pagination and once you save this you can see that we pass the children here and when I refresh I think that I should start seeing, oh, I'm seeing it's just right here at the bottom.
So I'm not sure if that is a bug. Let me just check entity here. Just a second. It's hard to say if it's a bug because we don't really have proper data here. Let me just change the workflows list into a div and give this a class name of flex one justify center items center.
Will this make it any better. I'm not so sure. And then in here maybe a paragraph wrapping this. Oops. All right.
Yeah I think something might be wrong here or maybe just in mobile mode. I'm not really sure. I'm going to debug just a little bit. All right. So first thing that I can see that's missing here is another flex.
So if I add another flex here to my workflows list what changes is that now it's centered but for some reason all the way down here and I want it to be here. So something in the workflows header maybe entity header here could be taking too much space. Let me just check by adding BG red 500. Great. So header is taking only the space it needs let's take a look at the entity container here mxauto flex flex call header alright flex flex call height full ah yes there is a bug this container here which we have added to entity container is not supposed to end here It's supposed to end at the bottom.
There we go. Okay, I had a feeling something is off. I just wasn't sure because we don't have proper data here, but yes, this div, MX auto max with screen extra large with full flex flex call gap wide hide full is supposed to encapsulate all elements the header the search the children and the pagination so after we do that you can see it works nice and we no longer have that weird thing at the bottom. You can no longer scroll there. So that the fix was in entity container.
Make sure that this div doesn't end after header. It should end here. Right. So now we can close entity components. And in the workflows list all we did is turn this into a div and we have centered it just so it's easier to find it here.
But now what I want to do is an ability to click here and for a new workflow to appear. For that we're going to go back inside of our use workflows hook where we added use suspense workflows and now let's create a hook to create a new workflow so let me close this export const use create workflow hook and inside of here let's go ahead and let's add three things the router the query client and useTRPC you can import use router from next navigation and you can import use query client from 10 stack query so make sure you have added all of these. Now that you have that let's return use mutation here and looks like we also need to use mutation from 10 stack react query just make sure you have added use mutation from 10 stack react query. Instead of use mutation, we're going to add the RPC.workflows.create. And let's actually collapse it like this.
So it's easier to look at. Let's pass in the mutation options here. On success, let me just properly do this. On success, we get the data that was just created, the new workflow, and let's first send a toast from sonar.success which will say inside of template literals workflow data.name created. So it will tell you the exact name of the workflow because names are randomly generated so that you know which was the newest one.
After that it's going to push to again template literals forward slash workflows and then data id. So it's going to redirect that newly created workflow and after that let's go ahead and do query client invalidate queries trpc workflows get many query options. So we are going to re-invalidate our current state of get many so the new one is loaded immediately. And on error here let's get the error and let's do toast error failed to create workflow and let's pass in error.message like so so now we have our use create workflow so let's go ahead and use it inside of the workflow header now let's go ahead and add our newly created create workflow hook use create workflow so let me show you exactly where I imported that from from hooks use workflows the same place I have imported use suspense workflows this is inside of our workflows.tsx file. Let's go ahead and do const handle create create workflow.mutate pass in undefined as the first argument and on error here we're going to handle error a little bit specifically so for now let's just console error but later we're going to change this let me in fact add a to do here to do open upgrade model We could have technically done that directly in here but the question is you know do you want that to happen or not?
Some could even argue that I shouldn't do router push within a reusable use create workflow as well because when it comes to developer experience, this shouldn't have any unexpected side effects and redirecting to the newly created one actually is an unexpected side effect. So yeah perhaps we shouldn't do it. I don't know for now I will leave it like this and let me just go ahead and test this right. So I will now just pass in handle create here and is creating will be become create workflow is pending and let's try it out now so at the moment you can see that I'm using server side prefetching and the client side use suspense to load this and when I click new workflow, that will allow me to both leverage the speed of server components for the initial load, but allow me client-side hooks for re-invalidation because this wouldn't be possible if we only used server components. So when I click new workflow, there we go.
You can see how it immediately updated the list of my workflows and redirected me to that specific workflow ID. And now I can see them here and they are cached, right? So you can see how fast it is when I just go back here. It doesn't have to load them. Let's try again.
Immediately works and immediately the list has been updated. Amazing. So there's one more thing I want to do to wrap up this chapter simply because it's already been 45 minutes and that is handle this error right here. So let's do that by doing the following. Instead of source components, let me extract this, let's go ahead and create upgrade model.tsx.
Let's mark it as user client and let's import all the alert components from chat-cn ui. Dialogue, action, cancel, content, description, footer, header, and title. So we have all of these from chapter one when we added all components from chat-cn. And let's also import auth-client from lib-auth-client. Let's create the interface upgrade-model-props which receives open and on open boolean change and then finally let's export our component upgrade model so it will accept these two props and it maps them to this.
Let me collapse it so it's easier for you to understand the structure. There we go. Now instead of this upgrade model, we're very simply just going to return all of the alert dialog compositions. So alert dialog itself with open and unopen change props. Then we're going to have alert dialogue content.
Then alert dialogue header. Inside of that we're going to have alert dialogue title with a text upgrade to pro. After that we're going to have alert dialogue description and in here just some text. You need an active subscription to perform this action upgrade to pro to unlock all features. And then let's go ahead and let's add alert dialog footer but we're gonna do that outside of the header here so alert dialog footer let's add a cancel action first alert dialog cancel and lastly let's add the upgrade action.
So alert dialog action with on click which calls out client dot checkout with slug pro. We already had this exact scenario inside of our app sidebar. So if it worked here in the sidebar it should also work here just make sure you didn't misspell the slug and that's it that is our upgrade model now that we have that let's create a accompanying hook here so I'm just going to go instead of source hooks and I will create a new hook use upgrade model.tsx I will import TRPC client error from TRPC client. I will import use state from React and I will import upgrade model from components upgrade model. Let's export const use upgrade model.
Let's define open, set open from use state and set it to false by default const handleError will receive an unknown type of error we are going to check if error is an instance of trpcClientError and if error.data.code is forbidden. In that case, set the model to open and return true otherwise return false and do const model upgrade model with open prop and on open change prop to be set open and return handle error and model like this a very simple use upgrade model hook and now we can finally go back to our workflows.tsx and in here after in the workflows header component after use create workflow add use upgrade model from hooks use upgrade model extract handle error and model itself And now finally you will see the use of this fragment below and the header render model actually maybe above it would be a better semantic decision. And instead of this you're just going to pass the handle error like this and you can see how the type is working right perfect so now this will still not do anything unless you actually go inside of workflows routers so server routers.ts find create and change protected procedure to premium procedure so now not only logged in users but also only premium users otherwise forbidden code will be thrown and then inside of use upgrade model we're going to check for forbidden code thrown and open the upgrade model So make sure that you're testing this out on an account which is not premium.
So let me go ahead and check. Let me log in. I'm going to refresh just to make sure no cash is left and when I click new workflow I should now see an error active subscription required and upgrade to pro text and when I click upgrade now I am redirected to the checkout page. Amazing and let me just confirm this on my premium account here. So I know that it is working as expected, new workflow and this should just create the new one and redirect me.
Perfect. And this entire time I've been thinking about should we redirect the user or not. And I think the right decision here is to redirect them in here. So let's separately handle on success here passing the data and let's go ahead and do router.push workflows data ID and add the const router use router from next navigation here from next navigation and now go inside of useCreateWorkflow inside of your useWorkflows hook in the workflows feature folder here and remove the router from here and remove the push from here. This way, this use create workflow has no unexpected side effects.
So when you specifically want to redirect, you will be able to extend onSuccess once again, just like we are doing here. So both of this useCreateWorkflow, we already have onSuccess and onError. But since we return the entire use mutation, it allows us to add very specific onSuccess and onError for very specific use within workflows header. So this way if I create a new workflow within the header which I'm doing right now the behavior is exactly the same but Maybe sometimes I will have new workflow buttons somewhere where I don't want to redirect. And then I very simply just won't have this.
Maybe somewhere I don't want to open the premium model, so I just won't have this. I think this is a better decision. I think this is a better decision. I think this is a better way to do it. And I just reordered this by length because I like it this way.
I think it's time to end the chapter here. It's 52 minutes long already. Amazing job you've been doing here. And I know this seems a little bit unnecessarily complicated but trust me later when you see how quickly we are going to create credentials and executions by just reusing everything that we just did you're going to be so thankful that you went through this and created reusable components. So let me go ahead and end here regardless of what I wrote that we should be doing.
Let me go back here. So we updated the workflow schema. We created a workflows API for create, read, update and delete. 11 workflows CRUD. So let me go ahead and create a new branch here.
11 workflows CRUD. And Let me go ahead and quickly review my changes. I have 14 uncommitted files. You might have 15 depending if you got any mprox error log files. So that's fine.
Package log, package.json, schema, migration and all of the files we have modified. So I'm going to do 11 workflows crud commit commit. Oops. I first had to change stage all of them. And then commit and then publish branch.
Perfect. And now that we have that let's go ahead and let's review all of these changes. This was quite a large pull request so it's definitely a good idea to have another set of eyes look at it before we merge. And here we have the summary. New features.
We added a workflows area with list and one-click creation. We redirect to the new workflow on success. Faster page loads via server prefetching and client hydration, so we populate the cache using the prefetch. • UI wrapped with loading and error boundaries. • Upgrade to Pro model prompts when an action requires subscription, thanks to our premium procedure data access layer protection.
Workflows are now tied to your account with automatic created updated timestamps, referring to our updated schema. Some refactoring regarding the TRPC endpoints and some dependencies for name creation. As always, file by file summary, but here we have the sequence diagrams. I think the more interesting one here is how the user fetches the workflows. So when we go to workflows, and once we confirm out, we call prefetch workflows.
The prefetch workflows then does get many, returns the workflows, and it does cache fill. And once we render the client using dehydrate and with the hydration boundary, we can do something called hydrated render, which is basically the super fast data load that you are seeing when you use a combination of prefetch. And we do that finally with useSuspense and we serve the cached workflows result. That's why it's faster than normal. And in here we have a diagram explaining how create happens, more specifically what happens if we're not premium.
So if we are not premium, we throw forbidden error, we handle that error and we then open upgrade model if forbidden has been thrown. We already understood this, but it's always nice to have a diagram here as well. And it's so impressive how CodeRabbit knows exactly what were the two most complicated routes here, What were the two most complicated jobs and it decides to create a diagram of those two. In here we do have some comments. The first one is regarding the migration file.
Since migrations are using the dev command I don't really take these seriously because obviously in production you would do migrations in a proper way. The reason this is critical is because yes, we are dropping all workflow records whenever we modify something. So yeah, in development, we are doing that in production, we should not be doing this a completely right comment here. And this is where it's actually wrong. And I haven't seen any AI get this right yet, simply because pre fetching is kind of a new concept, especially in TRPC and 10-stack query.
Adding a wait to prefetch workflows will do absolutely nothing. In fact, you can see inside of TRPC server, this little helper that we have for prefetch, it's not using a wait, it's using void. The reason it's using void is so when you use prefetch yourself you never actually expect anything to be returned so let me show you page.tsx in the workflows. It is used so you never do things like this. This will never work.
Workflows will always be a void because people were struggling to understand what prefetch workflows does. So that's why this is actually incorrect. Awaiting prefetch workflows will do absolutely nothing. And in here, it's actually a good comment. We can use shorthand operator because prop will be automatically passed and we don't have to use three lines for it then.
So yeah, that's correct. And in here, let me see. Okay, so yes, technically this is correct because it noticed that we don't have any params passed in the prefetch but we accept it here we will fix that later when we actually have some params for get many and they will not be optional they will actually be required And this is another thing where I'm pretty sure the AI is wrong because I also had Claude code and GPT tell me this but I'm pretty certain that you can use find unique with a composite query like this. I think that Prisma would throw an error if we tried to use find unique with a property that doesn't make it unique. So in here it's telling me that I should switch to find first and Cloud Code and you know, Cursor did both of that when I was developing this, but I'm fairly certain all of them are wrong.
I think in newer versions of Prisma, you can very safely do this. We both tested that it works and there were no errors being thrown inside of our IDE nor the server. So I will keep my eye on this because it's telling me that composite where clause will fail for find unique, but I'm very certain that that's not the case anymore. Perhaps it was the case in the past, but now I think you can pass a composite query with one unique field and one authentication field. I mean, authentication field, one other field for authentication.
:04 So I think Find Unique works perfectly fine here and it's not needed to use Find First. The reason I recommend using Find Unique is because I think it relies on indexes more than find first and it will then be a better and smarter query. So I will definitely research this to make sure I'm not telling you incorrect information because we will be doing this a couple of times within the project but I think we just proved by finishing this chapter that it works fine. So let me go ahead and click merge pull request and let me confirm the merge. And once we have done that, let's go ahead and switch back to our main branch here and then let's go ahead and hit the synchronize changes and the moment you click that everything should be updated here there we go perfect amazing, amazing job as always you can check the graph to see that you just detached chapter 11 and then merged it back here.
:02 Perfect. That means everything is fine. And then to wrap it up, let me go back here and just mark this as completed. Amazing, amazing job and see you in the next chapter. Thank you.