In this chapter we're going to set up TRPC, which is going to be our data access layer. Let's go ahead and let's head to the TRPC documentation page. You can use the link in the description or the link you can see on the screen to let them know you came from this video. Once you're on the landing page, go ahead and click on the Docs. And in here, click on the client usage.
And in here, you can find 10-stacked React query with a little star icon. And in here, going to Server Components. Be mindful that at the top here, you do have a Next.js tab, but that is talking about an older Next.js version. So make sure that you are inside of tanstack-react-query-server-components. This is what we need.
The first thing we have to do is install all the necessary dependencies. So if you're watching this video far into the future, I would recommend waiting until you see the exact versions that I had installed. So before you run this what I suggest you do and what I suggest you do before you start any of my chapters is click on this button right here and just click OK. Basically This is just a sanity check to confirm that you are on your main branch and that you didn't accidentally forget to synchronize your merged branch from the previous chapter. So you only care about this if you are actually following the git workflow.
Once you've done that and you are confirmed to be on the main branch, you can go ahead and install all of these packages. I am going to show you which versions exactly I have installed. Let's head into package.json so we can see all the changes here. As you can see, I have 10 stack React query 5.80.10 and all trpc versions are 11.4.2. So If that's something you care about, you can go ahead and install these versions as follows.
You would add this exact number to all of these packages regarding trpc. And then you would change this from using the latest version to using 5.80.10. And the ones for Zod, client-only, and server-only are not that important, but I'm going to show you them as well. So server-only is 0.0.1. And Zod is a package we already have installed and we had it installed in the first chapter setup because when we added all ShatCN component we also added forms and forms in ShatCN use Zod for validation.
So that's why this isn't marked as a new dependency because you already had it. Great. So now that you have confirmed to install all trpc ones, just double check that you actually have the same versions of all TRPC packages because that is quite important, right? So the version here is... The important version here is 11.x, right?
The minor versions probably don't matter that much but there is a big breaking change in 11 if you're coming from 10 or 9 so make sure you are using at minimum 11 point something and then you're good to go. Perfect. Now let's go ahead and let's create a small init file here. So I'm going to copy this. It's a very simple snippet.
So even if you don't have access to this documentation page, don't worry, I will pause the screen and you will be able to copy with me. Let's create a trpc folder inside of our source folder. And inside, let's create init.ts. And in here we are importing init-trpc from trpc server package and cache from react we are setting up the create-trpc context here with some mock information and we are also creating our initial T object which is basically initializing the TRPC and then we are extending it to create the router callers and factory and base procedure. Perfect So that's our init file.
Now let's go ahead and scroll a bit down and let's create our base routers. So I'm going to go ahead inside of trpc, I'm going to create a routers folder and inside underscore app dot ts. So I'm importing Z from Zod and from the previously created init file I'm importing the trpc router with the base procedure. So in here we have a very simple procedure called hello and it accepts text which is a string and it returns back an object with a property greeting, which is a string with the information from the text that we've entered. We are going to test this out later, so it's easier for you to understand if this is the first time seeing the DRPC syntax.
So that's it for the routers. And now what we have to do is we have to create our API folder, TRPC, and then a special Next.js variable folder, and then route.ts. So let's do that first. Inside of source app folder, let's create API, then let's create trpc, and then let's create a dynamic folder inside of square brackets TRPC again, and then route.ts. And let's go ahead and copy this.
Now in here, we're going to get some errors. It's specifically about this import alias. So we don't use this curly little string. Instead, we use an at sign. So you can just switch it to that and you will have no more errors.
As you can see, all of these things already exist. So we have the TRPC server package, they are just extending it here, some tree shaking, it seems. And the TRPC init is the one we just created, as well as the routers app, right? So you can command click on this to visit that, the same as the init one. And this is a node.js package, so that's a different thing.
Great, you can save this as well. Just double check that you have app folder, API, trpc, trpc in square brackets, and then route.ts. It's very important to have this exact structure. Great. Once we've done that, let's go ahead and let's create the query client.ts.
So I'm going to go inside of source, trpc, query, client.ts. Yes, it is .ts. Perfect. So about SuperJSON, we can, let's do this. Let's immediately install it.
So SuperJSON, because we are going to need it. And I will go inside of my package.json here. So this is my version, in case you want to use the exact same one. And what we're going to do is we're going to immediately enable serialized data using superjson.serialize and deserialized data using superjson.deserialize as well. So you can leave the component like this.
No need to modify anything further. Now let's go ahead and let's create our client.tsx. So this will basically be a wrapper, a provider of trpc and 10-stack query which we are going to wrap our entire app around. If this is your first time ever seeing 10-Stack Query or TRPC, this is a lot of information at once. But if you've ever worked with React Query or something like SVR, I promise it's a similar API.
It is just a bit more advanced data access layer format that we are doing here. So as much as this setup seems a little bit complicated, it is definitely worth it. You will see how easy it will be to build your API routes and your procedures later on. You will thank yourself for going through this because of how easy it will be to maintain this project going forward. So just stay with me, I promise it will be worth it.
So let's create client.tsx inside of here. Client.tsx. So this extension is important because this will be exporting a component. So since this is a bit of a larger file, I'm gonna go ahead and explain what it is. So first of all we are adding use client because this has to be a client component you can see it tries to access the window here and it's also using some hooks like use state that can only be achieved using a client component that's why we are using use client at the top and you can also see the explanation here.
So we are importing all of those things we should not have any errors because we either created or installed these packages. So one thing that we are going to change is this. So I don't like how this is specifically tailored for Vercel because I don't know where you want this deployed. So don't worry, I'm going to show you how you can modify this so it works well regardless of where you deploy. So let's go inside of our...
Let's just save the file. And let's go inside of .environment here and let's simply add next public app URL. And this will be the following. When you go ahead and do npm run dev, you're going to see where your app is being run. So go ahead and copy this and simply paste it inside.
Then I always like to copy from here and then paste it rather than type it out because you can do some typos if you're not careful. And you can see how complicated this is, right? So it recognizes Vercel URL and then it has to append the protocol and then it has to add this because Vercel URL doesn't have the protocol. Otherwise, it has to guess that we are using the 3000 port. It's just completely unnecessary.
We can do this much easier. You can return this completely. And instead of using this, you can just do process.environment next public app URL. So then when you deploy, you will simply change this to your production URL, regardless of where you deploy. And this will work just fine.
And in my opinion, it's much simpler to work with. And this is important, right? You can see that this will attempt to load the URL from localhost 3000 slash API slash TRPC. So it's important that you didn't do any changes here slash API slash TRPC. That's why this structure is important.
And then in here we just have some regular TRPC and TAN stack query setup. But we have to enable the transformer SuperJSON because we did enable it here. So we have to enable it here as well. So let's do the following. At the top here, I'm going to import SuperJSON from SuperJSON.
And I'm going to go down here, remove this part, and simply uncomment. So we are using the SuperJSON. And you can ignore the error for now. It's because we need to enable it in some other places as well to get it to work. So once you've done this, we didn't do any changes besides this and this.
That's the only thing we changed. So now, as per their instructions, we have to go ahead and wrap this in the root of our application when using Next.js. Since we are in Next.js, let's go inside of the root of our application. And that's inside of the app folder layout. So in here, simply go ahead and wrap the entire application.
So be careful with the component you are importing. It is trpc-react-provider from trpc-client. The reason I'm telling you to be careful is because there are similarly named imports from packages. We are not importing from any npm package. If you need to be able to command or control click here and it should lead you to this exact component, which has this little super JSON error.
That's the one we need to import and wrap our application around. Because if you just do trpcProvider, you can see that that also exists. But that's the incorrect one, right? It's trpcReactProvider from trpcClientComponent that is currently throwing the error. That's the one we need.
Once you've done that, we have to create a server.tsx. And this is where things become interesting. So let's just do that now. I'm going to go inside of drpc, and I'm going to create server.tsx, and I'm going to paste this here. And you can immediately delete this part.
So this is just an example. If your router is on a separate server, not the case for us, so we can remove this. So you can see it's much simpler now. Again, we have all of this either installed or already created. Great.
So now that we have that, let's go ahead and just fix this little SuperJSON issue here that I'm having. So I'm using SuperJSON in client.dsx. I'm using it in queryClient.ds and I think I should also be using it in init.ds. You can see that I have it commented out here. So let's enable this and let's import super.json from super.json.
This basically helps with serialization when it comes to passing specific props from server to client components with complex objects, right? Like well, object, array, date, things like that. SuperJSON helps parse those things. Great. So we now created server.tsx.
And this is actually a very, very interesting file. And I'm going to try and do my best to explain why. So that is basically it for the setup. We are now ready to use this API. So let's go ahead and let's do that.
In here I think they've added the most complicated example so I'm gonna try and use a more familiar example first. Let's go inside of source app folder and let's go inside of page.tsx. And let's try and do the following. Let's add trpc using use trpc from naturally client, right? That's our client.tsx component.
This is where we import everything client related. So now we have access to TRPC. You can see that I can find my hello and I can go ahead and pass in the query options inside. I can find the greeting or the text and I can say hello. Right, this isn't doing anything now.
I'm just showing you the API and how it works. So let's just quickly go inside of our routers here so you can see this change in real time. And so I can give you a little tip if it doesn't change for you. So go inside of the TRPC routers app and rename this from hello to, for example, create AI, something like that. You can see how immediately I've gotten an error here because that's how tRPC works.
So instead of having to do localhost 3000 slash API slash create dash AI, right, which is most of the time a literal string, right? So it's very hard to do. It's very easy to make mistakes, right? I can accidentally do this. And this is now an invalid API route.
But I wouldn't know until I see a 404 error. So what tRPC does is it enables full stack type safety from start to end. So if I accidentally make a mistake here, it immediately throws an error, like this route doesn't exist. That's what tRPC is. And it is much easier to build your apps when you know that you can rely on your code, rather than having to see it break in production and then go and fix it, right?
So if it works inside of your IDE, if there are no errors here, it will pretty much work everywhere. That's the power of having end-to-end type safety. And let's go ahead and change one more thing. Instead of this input here, this basically represents things you can send to your API. So let's go ahead and imagine an API post request again.
For example, this would be create AI. In here, we would send something like body and then we would somehow, you know, stringify this with text. Hello, right? This is a stupid example, right? But you know what I mean.
This is also very easy to break. But in here, you can see that if I try sending a number here, I'm getting an error. Why? Because we clearly defined this needs to be a string. So if I change this to a number now, you can see that it works, right?
But if I try a string, it will break. So what happens if you are not being able to see the same result as me, right? When I hover over text, I can see the text is a type of number. When I hover over create AI, I can see the input and text number and output is a greeting of string, right? Exactly as I'm typing it here.
If you are seeing type any for everything, you could be having a problem with your setup. So what you can do is you can go inside of extra information and go into frequently asked questions. And here we have it. It doesn't work. I'm getting any everywhere.
So there are a couple of things you can do. The first thing is you can check your tsconfig.json and in here make sure you have strict true enabled. The second thing is to make sure that you are using the proper TypeScript version and make sure your editor is using the same type script version as package.json. So for me none of these things were ever an issue but I did have this as an issue and this is what actually fixed my types. So go inside of your VS code settings.json.
Let me just go ahead and try and do this. So settings. Let me Oh, I think that Yeah, you can just create it if you want to. So go inside and create a .vs code like this and then settings.json and you can paste this inside and then you can click allow if this happens. And let me go back to page.tsx and nothing changed for me because this worked from the start.
But if you're having any problems, when I say any, I mean this. You're getting the type any all over your project. It could be due to these missing settings. So this actually fixed my problem once. So that's why I'm sharing it with you.
And you can always do command shift p and then reload window and this kind of restarts the TypeScript server so then it could work maybe. Great! So now let's actually see the result of this query. So make sure that you have your app running and that you can visit your root page. And first thing you should see is this very big error.
Why? Because by default in Next.js, every page and component is a server component unless specified differently, or if it is a direct child of a client component. So let's go ahead and add useClient to the top. This will then turn it into a client component. And you can see we no longer have these errors.
We can now use hooks as much as we want. So what I want to do now is show you how we actually get data from our API using a very familiar use query from the package 10-stack React query. And in here, you would usually create, you know, your own fetch method, which would then call forward slash API, create AI, and then you would pass in the body, right? And you would have the JSON stringify blah blah blah. What you can do now is the following.
You can just use the trpc1 trpc, create AI and pass in the query options inside, text. And let's try Antonio here. Let's go back inside of, yeah, you can also command click here and it will take you directly to the router. So I'm gonna change this back to string and then what we can do is simply render data. Let's just do JSON stringify data.
There we go. And there we go. Greeting, hello, Antonio. If I change this to John, it will change to hello, John. That's how we are going to fetch our data.
This is our data access layer. Great. So this is the most simple way of fetching data using a client component. Everyone knows this. But let's go ahead and cover two more examples which I think are important.
So we've kind of set up the RPC and we experimented with a client component. Now let's experiment with a server component and then finally let's experiment with prefetching and I'm going to try to explain what it is and why it is important. So basically server components have several advantages over client components. They are not, of course, one is not a replacement of the other. They work together.
But server components have that advantage that they are well on a server, which means server components have direct access to the database, for example. But in our case, what's important is that they render sooner than the client components. So what is currently happening is that our application has to wait for page.tsx to be rendered from the server, and only then does it start fetching the data. But what if we could start fetching the data on the server and then continue using the data using this familiar API within a client component? That is prefetching.
But in order to understand prefetching, let's first remove useClient like this, and let's break our app. So immediately, our app is broken. We can't use tRPC here at all. So now what we have to do is we have to learn how to use tRPC inside of a server component. And let me just show you.
I'm going to try and do this. So if I add console log here and just remove these things and just refresh, I think that you should be seeing server component inside of your terminal. How come you're seeing it in the terminal? Because it is a server component, right? Because if I change this to use client now and change this to client component and refresh.
I'm pretty sure we can still see it here. Yes, so this is a bad example. My apologies. Previously in the past, you could not see it if it was a client component. So yeah, ignore this.
I tried to do an example, but I used the wrong one. Let's just learn how to fetch data from a server component. So in here, we actually have some guides. Let me just go back here. There we go.
So in here, getting data in a server component. So let's go ahead and simply create this little caller inside of our server.tsx. So go inside of trpc server.tsx, and at the end here, export const caller. Call the app router, create caller, create the RPC context. So there is a way you could fetch data from a server component, and that would be either directly calling the database, like calling Prisma, or there is nothing stopping you from fetching inside of a server component.
So you could again do create AI and then the body, blah, blah, blah, right? But that is unnecessary overhead because server component already has access to the database, right? No point in doing this. That's why TRPC has invented something called a caller. So what you can do now is let me just confirm the API, you can import the caller.
So let's just do that. So I'm going to do const data and turn this into an asynchronous server component and await caller from drpc server, create AI. And let me just see, do I have to do it like this? I have to Antonio server. And then I believe, let me just check, is it data like this?
There we go. JSON stringify data. So this is how you would fetch from a server component using TRPC using a caller. So this isn't doing a network request on that server component. This is server component, literally directly having a remote procedure access a remote procedure protocol to tRPC.
That's why tRPC is so powerful, because I think it's one of the only RPCs available that has these types of callers. I could be wrong, but it's the only one I've seen that performs this well. And this isn't so impressive right now, but if you've worked with server components before and you tried any kind of RPC here, you would almost always encounter an issue that you lose authorization headers, right? The server component would never know if the user is logged in or not. TRPC solved that problem as well.
That's why I love tRPC so much, because it allows us to leverage server components. So now let's finally do the way we are going to use the TRPC and that is using prefetching. So we would do things like this const queryClient would be getQueryClient from TRPC server. We're not going to call the caller here but we will import the TRPC from here and then in here we're gonna do void the query client dot prefetch query TRPC create AI query options text Antonio and this time prefetch and then in here what we would do is we would add a hydration boundary from Dan stack react query. We would pass the state to be dehydrate, again, imported from danstack react query and pass in the query client.
And then in here, we would render a client component. So let's go ahead and create that. Client.tsx. This is not a reserved keyword. So this is just a component which needs useClient at the top.
And then in here, what we would be able to do is the following. We can now get the data by using useSuspenseQuery from tanstack react-query. And let's add our TRPC from the client and pass in trpc createAI, queryOptions, and important, you need to have the exact same text here. Otherwise, the prefetching will fail because this would usually be some kind of filter. For example, instead of text, you would most likely have page one, limit 10.
So if you prefetch one thing and then expect to have something in suspense in the client component, it wouldn't work. That's why it is super important that your query options are exactly the same in your prefetch and in your client component. And then in here you would be able to do JSON stringify data. And now let's go ahead and let's do this. So let's go inside of here.
Let's import client from dot slash client and let's wrap this inside of suspense from react and let's add a fallback here loading so what's going on here basically instead of directly calling the data instead of a server component, what we are doing is we are leveraging 10 stack queries cache and state, and we are immediately populating it the moment server component gets created, the moment server component loads, because this will then allow the client component to load whenever it loads, because we know a server component will load sooner than a client component. But this time, the client component won't have to wait until it gets loaded and only then initiate a network request. Instead, it will already have the data ready, even though, because we prefetched it inside of a server component, and it's very important to use a void here. And this prefetch query actually doesn't return anything. So even if you tried things like this, this wouldn't work.
It doesn't return anything. They've done that on purpose so you don't actually use this data because pre-fetching query, all it does is it initiates a call on a server component, but only for the sole purpose of populating a 10-stack query, which can then be accessed in a client component. So we are leveraging server component to start fetching our data immediately. And then we are passing it down to the client component, which uses a familiar API. So because this is use client, we can now go ahead and have a use effect here, right?
We can go ahead and we can have a state here, right? We can do all of these things. And it would work as fast as if we did the entire thing in a server component. So we don't lose the familiarity of client components, and we also don't lose the speed of server components. We basically get the best of both worlds by doing this.
And let's finally try it out. You can see it works just fine. I know this is still a bit confusing. I try to explain it the best I can. I would highly suggest, you know, reading about prefetching and just going through this documentation in the first place.
Perhaps Dave explained it a bit better, but this is how I like to explain it. Basically, we are now getting the best of both worlds, both the speed of server components and the familiarity of client components. I think that this officially marks the end of this chapter, right? So let's just remove these things because we don't need them. And what we're going to start doing in the next chapter is finally initializing our background jobs and start introducing some background actions.
We now have our database, we have our ORM and we have our data access layer. So those are the three things that we need so we can start saving some data in our database properly, right? So let's go ahead now and mark this as completed. We did all of these things. And let's go ahead and branch out now.
So I'm gonna go ahead and go down here and create a new branch. I'm going to call this 03-trpc-setup. I'm going to go inside of my source control here and I'm going to stage all changes and then I'm going to add 03-trpc-setup comment and I'm going to commit and then I'm going to publish the branch. Then let's go ahead to our GitHub to open a pull request. So in here compare and pull request and let's create a pull request.
And as you can see, I have something that you probably don't in your pull request, and that is an AI code review using CodeRabbit. As you can see, not only do I have a complete summary of this pull request, as you can see we introduced tRPC integration for type-safe API calls between client and the server. And we also added some chores. Not only that, but I have a change file by file summary so you can see exactly what I did in each file. And I also have a sequence diagram explaining exactly how every single component works in which order and how it responds with data and result, which is especially useful if this is your first time working with TRPC and 10-stack query.
And I also have some potential issues it caught. For example, we added this dummy TRPC context from the documentation, right? So it noticed that, and it told me that I should replace this hard-coded user ID with proper authentication. In this exact case, it is okay for us to proceed because this is just our initial TRPC code. We will replace this later when we add authentication.
But you can see how it already started noticing some potential issues in our code and then it also told us to change this further on when we update this react context so it actually understands our code very very in-depth. If you're interested in having the exact same code review, you can use the link in the description or the link you can see on the screen and create a CodeRabbit account. But that is not all. So now I'm going to go ahead and merge this right here. Let's merge this pull request.
I'm not going to delete my branch simply so I have access to it right here. So this was our previous chapter and now we have the RPC setup. And you can see that I got this pop up in my Visual Studio Code as well, asking if I want to start a review inside of my Visual Studio Code. So since I just reviewed my code in a pull request, I'm going to click no, but I'm going to show you in a second what this is. So before we proceed, go ahead and make sure you change back to your main branch.
And then just click on this little button to synchronize the changes. And then when you click on a graph here, you should see our last one was a database commit And then we merge that and now we branched out for TRPC setup and we merge that back inside and what I suggest you install is Code Rabbit extension. So Code Rabbit is a completely free VS Code extension. You can go ahead inside of your extensions here, CodeRabbit and install it. And if you don't want it connected to your pull requests you can have complete free code reviews in your Visual Studio Code.
All you need is an account with CodeRabbit. You can use the link you can see on the screen and you will get amazing pull request reviews but also you will get completely free code reviews in your IDE. We're going to try that in the next chapter because we just reviewed our code in a pull request this time. So again, just confirm you are on your main branch and you have synchronized all changes. And that will allow us to continue to the next chapter.
So let's go ahead and mark this as done. Amazing job! We now have the database access, our ORM, and our data access layer. We are finally ready to start doing some AI related things and background jobs. Amazing, amazing job and see you in the next chapter.