Let's Learn tRPC!
The promise of tRPC is end-to-end typesafe APIs. In this episode, Alex / KATT will teach us what that means and how we can get started using tRPC in a React app.
Links & Resources
Click to expand the full transcript
Captions provided by White Coat Captioning (https://whitecoatcaptioning.com/). Communication Access Realtime Translation (CART) is provided in order to facilitate communication accessibility and may not be a totally verbatim record of the proceedings.
JASON: Hello, everyone, and welcome to another episode of Learn With Jason. Today on the show, we've got Alex. Thank you so much for taking the time to hang out with us today.
ALEX: Thank you for having me.
JASON: Yeah, I'm thrilled to have you here. I think this is going to be a lot of fun. I'm really looking forward to it. So, I feel like -- we're going to talk more about tRPC. So before I start talking about that at all, let's maybe start by just having you talk a little bit about yourself. Can you say a bit of background for yourself?
ALEX: Yeah, cool. So, I'm Alex. I'm Swedish. I've been making websites and everything programming related since I was a kid. So, I started -- I did my first website in the late '90s and sort of got into more serious programming and stuff while I was doing gaming as a teenager. I think that's a familiar story for a lot of people. I was playing a lot of Counterstrike, had a clan. A clan needs a website. A lot of that. And then I got into hosting my own servers and started a company with that where I had the server in my mom's closet and did a website where you can book a server, configure that. Through that, I sort of learned the foundation of everything web related and whatnot. It was pure lamp stack back then. This is like 2005, 2006 or something like that. So I was sort of born in the PHP stack. You'll probably see some of that in tRPC as well. Because ever since I've been working with -- ever since I worked with PHP, I sort of missed that feeling of just having everything in the same place. You can sort of do a database call straight in your HTML sort of and just render stuff dynamically from the server.
JASON: Right, right.
ALEX: But yeah, I started working professionally as a developer in 2009, something like that. Been jumping around the stack from like front end to back end on iOS and Android development and lived in a bunch of different countries. Right now I'm back in Sweden. I live in Stockholm right now. And I'm currently working for a startup that is in stealth mode. So I'm not going to talk too much about that.
JASON: Got it, got it, got it. Okay. So I am very curious about tRPC. I feel like it's something that I've heard a ton about. And so let me do a quick preface. For everybody who's wondering why I'm on my Macbook Pro camera and the mic, it's because I'm in the middle of moving. I just accidently turned off the internet at my current house. So I'm in the new house, which has nothing in it yet, and I haven't had time to actually set all my stuff up yet. So anyway, welcome to the new house. We'll be back up and running by Thursday, when I get all my gear moved over here. So also, thank you for the sub. Appreciate that. So with tRPC, I don't know anything about it. This is sort of my very true to the spirit of the show. I don't know a single thing about tRPC except the tag line, which is that it promises end-to-end typesafe APIs. So for someone like me who has no context on what tRPC is, can you give us the overview?
ALEX: Well, so, you've probably built on API before and used that API on the front end. So traditionally speaking when you do an API nowadays, you probably want to document your API somehow with a schema or something like that. So there are two main ways of doing APIs today. One is swagger/open API schemas or GraphQL. But when you're working with either Open API or GraphQL, you sort of bring in a new language into the stack. Either it's YAML or GraphQL, even though your both front and back end might be written in the same language. So what tRPC tries to do is instead of you having a clunky API definition, you just write functions on your back end. Then you can infer the type signature of those functions on your client. So it completely removes the need for an API schema because it's guaranteed by TypeScript itself. So RPC stands for remote procedure call. So tRPC is TypeScript remote procedure call. It helps you to call remote functions on your client.
JASON: Gotcha, gotcha.
JASON: So, this is interesting. Somebody asked in the chat earlier, is this related to gRPC?
ALEX: No, it's not related at all. It's a common misconception because of the similarity of the names. But both are sort of RPC-inspired things, right.
JASON: Got it. So is RPC something that is -- is it like a common, you know, computer-science concept? So the tRPC is the TypeScript implement of this existing concept, or is there something else to it?
ALEX: So, RPC -- I mean, I should actually look into the history of this a bit more, but it's a really old-school way of dealing with APIs. So traditionally speaking, when you have a system that needs to talk to another system, you then -- rather than send a lot of data back and forth like some strings or whatever, you expose a sort of function from that system to that system that you can call, as if you're a part of that system. It's really an old way of doing APIs. It predates like -- it goes way back.
JASON: Gotcha. Okay. And so my -- I think my initial question, whenever we start introducing a technology that a lot of people are going to be new to, is what is the benefit over -- you know, so we talk about -- we're defining APIs and sharing them out with people. So there are a bunch of ways we could do that. Two of the most common I've seen now are Rest and GraphQL, seem to be the two ways people reach for first. And when you've got a GraphQL API, you are defining a schema, as you said. Then that schema gives you the ability to kind of introspect and figure out this is exactly what's coming out. So it offers a similar benefit in that I write my schema to define my GraphQL API. Then anybody consuming that can use that schema to say, well, this is exactly what's going to come back and what the types are and all those things. Now, with Rest, Rest would give me the ability to optionally open an API spec or something similar that would let me share my types with anybody who wants to consume it. But that one is not automatic. That one, I have to maintain my open API spec and not let it drift out of sync with the production code. So maybe the first one, as you said, tRPC is going to automatically, based on your TypeScript, generate this typesafety without doing anything with the schema. So how do you compare and contrast the benefits of, like, tRPC versus GraphQL?
ALEX: So, I can preface this with me being -- I'm a massive GraphQL fan. As soon as GraphQL got released, I was all over it. I've been an ambassador for using GraphQL for, yeah, six years now. I still am. So tRPC is -- so in GraphQL, you sort of define a full data modeling for the whole domain off what you can query on the server. It's sort of like -- it's a language in itself, right. And then from that definition, you then derive this is how I fetch a specific thing on my back end. Then on the front end, you can use those types of the schema to generate, for instance, TypeScript definitions. The thing that I have discovered is that a lot of the power features you have from GraphQL are things you don't really necessarily need in a smaller scale. The thing that drew me to create tRPC is sort of like if I have TypeScript on both ends, why do I need to write a schema or whatever? Can't I just get the value from the function defining my back end and get that straight to the front end without adding a library for code generation on the back end and a library for code generation on the front end to transform this GraphQL schema into TypeScript. So what tRPC gives you -- the main thing is speed. All you have to do is to -- you define a function on the back end, you call that function on your front end, and that's it. You don't have to have any code generation. You don't have to really think about the schema. Obviously, you need to think that you don't want to return any sensitive data from the back end. But all you need to do is to define this is the input validation that this function requires. Then you return data, and that data is then available straightaway on the client without any further work.
JASON: Gotcha, okay. That makes sense. So, when we're talking about -- you know, one of the things that I think is the biggest barriers to adoption for GraphQL is that you don't just, like, write GraphQL code. You have to write a schema. You've got to write resolvers. You've got to write, you know -- there's a decent amount of code and boilerplate that goes into getting a GraphQL API set up, which absolutely makes sense. Like you, I'm a big fan of GraphQL, but I think we've got to be pragmatic about our choices. Sometimes GraphQL is just too much for what you need. Like, if you're not modeling very complex, actually graph-like data, a lot of times you just don't need all of that. So this idea of using tRPC where I get to write a function and that function is what I would have written anyways, I need to pull this data from somewhere. Then the client is able to just infer the types the same way that I would get with GraphQL, which is one of the reasons I like it. So, you've got me interested here because this sounds like what I've wanted for a long time, which is the benefits of GraphQL without the initial setup. So how much setup are we talking when we look at bringing tRPC into a project? Is it something that I drop in, in a few lines of code? Is it, you know, a ton of back-end boilerplate? What are we up against when we start thinking about putting it into our projects?
ALEX: It's not much, really. The thing that's a bit different from GraphQL is that, like, you have the GraphQL spec and you have the GraphQL node library or whatever you use on the back end. But you don't have helpers for the types. You don't have helpers for how you do the client. If you want to do a client, you have to install a second package, which is like a relay. With tRPC, you use the tRPC server, and then you use the tRPC client on the client. If you use React, you then also add in the tRPC React client, which depends on the React query. So in that one, you get all of the features that React Query has, plus extra-type information of everything you call.
JASON: Gotcha. Okay. So is there a more set of problems you're going to reach for tRPC with? Or I guess to rephrase that question, where does tRPC shine? Like, when is it the right tool for the job?
ALEX: So private APIs within a company. If you're not going to have a publicly exposed API that third parties are using, you don't really need this widget API spec that both Swagger and GraphQL offer. All you need to do then is to trust TypeScript, that it infers the right type, and use tRPC. You can just trust the types rather than keeping a big schema up to date. So, yeah, like any organization that's heavily invested in TypeScript, it probably makes sense. If you're going to have a native iOS app in Swift or whatever, it might not make sense to use tRPC. Because then you have to write a swift client for tRPC, and it's -- you'll miss all the typesafety. But if you're working with full-stack web or React Native or in general work with TypeScript everywhere, then tRPC is really good.
ALEX: So, I'm heavily invested in React myself. So I spent most of the time -- like, I made an official React adapter and an official Next.js adapter as well. But people have done adapters for Svelte and other libraries as well. As I said before, the official tRPC packages are four distinct different packages. Web to server, the client, React, and Next. The server and the client are not dependent on React as such. There's a bunch of people that are using it for, like, server-to-server communication. When you write a CLI or something like that, you can use it just to call from the CLI to the server.
JASON: Nice. Very nice. Okay. And you mentioned it's -- so if you are writing a Swift app, that's one case where you wouldn't recommend tRPC. Are there any other places where you would say it's probably not the right tool for the job?
ALEX: So, if you're anything but invested in TypeScript and if you want to do a public API. Although, I'm working right now on something that will automatically generate an Open API schema from your tRPC back end. So even that will be possible soon.
JASON: Gotcha. Okay. I mean, that sounds pretty exciting. All the rest of my questions are about implementation. So I think this is a good breaking point for us to switch over and start actually coding. So let me take us over to the other setup here. Here's the pair programming setup. All right. And now I'll just start us off by doing a quick shout to our captioning. We've got Rachel here with us today from White Coat Captioning and that is available on the homepage, learnwithjason.dev. That is made possible through the support of our sponsors, Netlify, Nx, and Backlight, all kicking in to make this show more accessible, more fun, give us a little more bandwidth. We are talking today to Alex. If you don't follow Alex, you can head over and give him a follow on Twitter right now. And we're specifically talking about tRPC, which is a tRPC.io. With that being said, we have now reached the end of my knowledge of how to get started. Audio bitrate issue. Dang it. What's going on? My bitrate is a little choppy today. Let me see here. Just a moment. Let me do some debugging, make sure I'm not doing anything silly. Get to my device settings.
ALEX: Hey, Theo. What's up?
JASON: Okay. So I dropped down to 720p to see if that's going to make any difference in the audio quality. Hopefully it will. I'm, like, sitting right next to a fiber modem, of course. First-day jitters, I guess, for this modem. Let me know if my audio keeps doing anything weird. I am just using the Macbook Pro mic today. So I apologize for the boominess of this audio. All right. So let's forge ahead here. All right. I need to create my first setup here. So what are my first steps, if I'm looking to do tRPC from scratch?
ALEX: Okay. So what is your normal setup? Do you use Nextjs, do you use React?
JASON: I can use either. I typically reach for React. Like we could set up a Create React app.
ALEX: So, what I usually try to push people to do is to start off with one of the example apps. Because then you get all of the boilerplate set up for you. So the recommended one is the top one, as you see here, which is the Next.js one. That example is set up with everything you need for production. You have serverside rendering and stuff like that. So let's hope that this works.
JASON: Okay. So we're running the Create Next app, and we're going to use the tRPC repo and then example path goes into examples. Next Prisma starter. I didn't realize this. This is a cool little bonus. So that's going into here. That's clever. I didn't know they let you do that. That's cool. So, we have installed. We're downloading. It's going to get all of our dependencies and all that good stuff in here. Fetching packages. And you said this is going to include all the goodies.
ALEX: A bunch of stuff. Yes, I have a helper command too, to set up a database locally.
JASON: Nice. Oh, yeah. We've got the ability to see the database. Start the database. Migrate it.
ALEX: It also has render YAML for deployment, or we can deploy it off our cell.
JASON: Cool, all right. So we've got all the pieces we need here. And it looks like we're done. So we'll go into tRPC Prisma starter. We've got our node modules here. So I can just run yarn dev.
ALEX: Let's see if that works. So, it says it's running, right? I think that's too easy. We need to create the database as well.
JASON: Okay. So it's started, but we don't have a database, which would mean invalid. Yep, no posts yet. So we need to create our database.
ALEX: Yeah, so if you open the project in VS Code or something and then look in the read-me, there's some command to help you out here. So it has a yarn dx command, which is a command that should start up a docker container for you, if you have docker installed.
JASON: Let's see. I thought I had Docker installed, which I don't. That will be interesting. How long does it take to set up Docker on a new computer?
ALEX: We don't have to do that. We can work around it as well. Do you have Postgres set up locally?
JASON: We night need to do SQLite.
ALEX: Okay, so we'll start with some debugging. In the Prisma folder, the schema.
JASON: It's ignoring this whole folder, so I'm going to actually close this and open it again so that we can actually see these folders. Prisma, schema, there we go.
ALEX: Then we need to switch the provider to SQLite. I think Prisma gives you auto completion there, right?
JASON: Let's see. It wants to give me -- SQLite. There we go. Okay.
ALEX: Then we have to update the database URL, which is in the .env file. And here we need something which is like a file path. File:dev/db.
JASON: Like that?
ALEX: Yeah, skip the "slash, slash." Yeah, yeah, like that. Now we need to run the Prisma command to sort of set this database up. So in the package JSON, I think I have a shortcut for that, which is like migrate dev or something. Yeah, we can run that one.
JASON: Okay. SQLite does not -- move your current migration directory. It's not going to like that.
ALEX: Yes. So if you nuke that folder, we can move on.
JASON: Okay. There we go.
ALEX: And now, hopefully, we can run yarn dev.
JASON: Yarn dev or yarn dx?
ALEX: Yarn dev because the yarn dx will also try to set up the Postgres database for you using Docker.
JASON: Okay. So it's doing Prisma queries. That seems good. So if I come back out here and reload the page, it did a select for us. So if I add a post -- all right.
JASON: We're in and running.
ALEX: So we have our first app. So we can start by looking at what's going on here. Then maybe we can try to add a feature or something like that.
JASON: Sure, yeah.
ALEX: Do you have any idea of what you want to build? Have you thought about something?
JASON: So, I think what I would love to be able to do is, you know, for me the lightbulb moment for me is when we realize how to really connect this thing end to end. To the starters are always really helpful, but it's sometimes hard to tell which pieces are part of the starter and which pieces are sort of for convenience to make it look nicer, you know, to do something else. So I think what I would love to see here is what is the process of adding something to my database -- so maybe we can just add a completely new type. Then write the -- maybe just create and read. Put something new into the database and get it out and show how that typesafety works in the process.
ALEX: Okay. So how about we add some comments to our posts. Maybe that's a good thing.
JASON: Yeah, let's do it.
ALEX: Maybe first I can explain the folder structure here. If you make that window full screen, it's going to be easier to read for us.
ALEX: So I can walk through the folder structure of the project to just show you what's in here. The standard Next.js stuff we have is the source pages folder. That's the sort of Next.js magic of mapping. Then we have -- what we've added for tRPC we have in the _ app, the function that we wrap this app with.
ALEX: Here we wrap with tRPC. And here there's some boilerplate to set up. We say that we want to batch our HTTP calls. So if you do two parallel requests, they will be combined into one request in the browser.
ALEX: Similar to GraphQL, you have automatic batching for parallel stuff. So this is set up with SSR, I believe. So that means that when you reload the page, the server will do a pre-pass and fetch everything that's needed for this page before it's rendered.
ALEX: So here's the app. Then we can look at the pages index file. Look at first the tRPC query. So here you see we're using tRPC use query post all. And if you hover over, for instance, post query, you'll see that that has a bunch of types. So you see that has the ID, title, text, and created at. And that's straight from our back-end function.
ALEX: So if we go to the server folder instead in the source server, we can see where this data comes from. It comes from routers and the post router.
ALEX: And here we should be able to find a function that is called all because we queried post all.
JASON: Let's see. So we have our post router. Then we've got all. And this is going to do a Prisma find many. If you're not familiar with Prisma, we do have an episode on it that you can go and watch to get you familiar. And here we've got Prisma returning our posts and find many. And this default post select is just the fields. That's pretty slick. Okay. I got you.
ALEX: So, I usually just define sort of the data object globally so I can reuse it. I always make sure to do an explicit select when I use Prisma. So we didn't accidently leak any extra stuff that we don't want to return to the browser. So here we're using a Prisma validater. But to just give you a glimpse of the power of tRPC, what you can do is to have the post index page open at the same time as you have the server. You'll be able to see how types are inferred. So maybe you can split this with -- yeah, like this. You can just change this to not return updated at. You don't even need to save. And you can see that disappears right away.
JASON: Wow. That is real fast.
ALEX: So there's no code generation involved. You don't even need to have your server running to have this working.
JASON: That's slick. And is this because it's TypeScript. This is just leaning on the VS Code TypeScript parsing that's built in?
ALEX: Yeah, exactly.
ALEX: So we have a base app router in our server that we gradually extend with more and more types. And that sort of gets resolved into a massive object, which we then can infer on the client. And then map different sort of strings, like post.all to a certain type.
JASON: Gotcha. Okay. So this is -- I mean, I'm following you. This is pretty slick. And we can see that -- so, we've got our router here. The router is leaning on some Prisma stuff. That gives us the ability to use these queries. And the queries bring back our posts, all of which totally makes sense. I'm getting it. Then once we have our post query, we can just come down here and throw data on the screen the way we would want to, which is --
JASON: Okay. So that makes sense. I'm with you. I'm following.
ALEX: Yeah, I'm of the belief that TypeScript don't really belong in normal apps. Sorry. Generics. TypeScript generics doesn't really belong in application code. And the sort of complexity of dealing with generics should, ideally, be offloaded to a library.
JASON: That is very much my feeling as well. I feel like if I have to read a generic type, I'm immediately unclear on what the thing does. And I'm no longer getting a benefit from TypeScript. I'm now just hoping that I don't break it, which feels like it kind of works counter to the benefit.
ALEX: Yeah, and that's also -- I know it's a bit counterintuitive to start from an example, but I want to start from an example because the sort of boilerplate of tRPC might look a bit intimidating because we have so many generics that need to be there to infer stuff from the client to the server. But once that's set up, you don't have to think about that again. And that's why I want to start from, like, an already set up example so we don't get stuck with the sort of TypeScript generic wrangling of this.
JASON: Gotcha. Okay. So this all makes sense. We're creating a router. We've got a health check point. We merge in the post router. That's where we got our post dot from. That's all built in here. So the next thing that I'd like to do is you talked about adding some comments. If I want to add comments to this, where do I start? Like, my instinct would be to head over to this Prisma schema and start just defining the comment type.
ALEX: Yeah, so we have like three elements that we need to add when we have comments. First, we need to add stuff in the database, which is Prisma. Then we need to have some sort of API logic or back-end logic to talk to Prisma. That's in the router. And the third thing is some UI. That's probably under post/id page. So in whatever end you want to start, we can start. You can start with UI or Prisma.
JASON: Okay. Let's assume that we're going to do this the front-end dev way. I want to try getting some comments on the screen. So my idea down here is to do something like comments for this post, and we're going to put up a UL and LI. We'll give like a comment title. Then we need a comment body. So this is a pretty generic comment setup. And we could do -- make it a pretty usual comment post area, right. So then when we come out here and look at these, we click in, and we get some basic comments. Nothing fancy. Nothing special. But this does what we want it to do. We've got a name and the thing, the person.
ALEX: And then you probably want that to be hooked into an API.
JASON: Right. So up here, I'm getting the post by ID. So what I would want to do is load comments for this post.
ALEX: Oh, that was smart.
JASON: Yeah, this GitHub Co-Pilot stuff really saves me a lot of time.
ALEX: Okay. So you can try writing that post, that query here to see that you actually get an error.
JASON: Okay. So like comments query, and that would be tRPC, use query. And I would want, like, comments by post ID.
JASON: And what else? And then I would give it an ID.
ALEX: So that needs to be in the (indiscernible) that you have there.
JASON: Got it, got it, got it.
ALEX: This is a convention that comes from React Query. So you have a query key, a query path, and then a query input.
JASON: Gotcha. Okay.
ALEX: And this is red now because that doesn't exist. And you also see there that we get all of the routes that actually exist in our back end shows up there. So, I guess we can try to go ahead and create it.
JASON: Yeah. All right. So let me see if I can reason my way through this. So what I would do next is I know the post router has to come in like this. So I would create a comments.ts. And I would look in here. Okay. So I'm going to need Prisma. I'm going to need Prisma Client, Zod. Let me grab it, take it into my comments. Then the next thing that I need is in here. I'll end up needing one of these, but I don't know what exactly it is yet. So I would have my export const comment router equals create router. Let's grab that one. And we'll say comment router. So we're not going to do a mutation yet. We will need a query. All right. So all of that will need to go in, but for right now, it kind of doesn't need to do anything just yet. So this is incomplete, but theoretically it's correct.
JASON: Then I go back to my router app, and I need my comment router. Get this from comments. And down here, I just need to duplicate that. And we'll call this comments. So theoretically speaking -- now, this doesn't actually register anything because we didn't put any routes under there. But as far as plumbing goes, this is correct. Now we just need to line up how to get actual data in here. So maybe we look at this one because we're going to go by post ID.
ALEX: Yeah, so when you define the tRPC procedure, you can -- you always have a cell function. That's sort of like if you're familiar with GraphQL, you also have a resolve function and then the sort of back-end implement takes of GraphQL. Then you can optionally define an input type for it. The input type has to be some sort of validator. In all of my reference code, I use (indiscernible). But there's also Joy. There's a few others that it has support for. So it can be of any type, but we only accept one input argument. And then you have a resolve function that automatically infers that input based on the shape you define there.
JASON: Got it. Okay. So if we want to fake this for now, that's going to be, what, z.string. Oh, it just wants that to go away. Down here, we can kind of fake this. We could do a name of, you know, some person and a text. Thank you, GitHub Co-Pilot. Then we can do another one. Now we've got like sort of the structure we're going after. But we need to determine what the return type would be. So this would effectively be our return type, right? So if I take this --
ALEX: You don't need to define any return type.
JASON: Oh, okay.
ALEX: It's there automatically because you define an object, which has an array of those strings. And you return that. So that should be inferred.
JASON: Nice, okay. Now that I've saved and let it format, it's happy again with that. So then if I'm correct here, this should work and give me my comments, which are now typed, which is amazing. I didn't have to do anything there. I just get my ID, name, and text. Then I can take my comments query, and that's going to give me back a status, like so.
ALEX: Yeah, in my own apps, I've written some abstractions that are similar. I've done my own implementation of sort of cells for this. So you don't have to do all of this if loading, if error. But for now, you can just skip the loading state and do optional chaining under comments. You can just do like comments query.data?
JASON: Got it. Then does that come back as --
ALEX: That should be an array.
JASON: That'll be an array. Okay. So then we'll do one of these in like a map. Wait. I'm doing that backwards.
ALEX: Yeah, it's backwards.
JASON: Then we've got our comment. We can take this junk out that I don't need anymore. We'll move this up to the top. And then we'll move this up to the top. Delete that one. And then this will be --
ALEX: I'm seeing some comments here. I'm missing some of the history of the comments. Something about Prisma where input.
JASON: Is it comfortable for these to be input types?
ALEX: The reason why we have a validator here is we actually want to validate the input that comes from the client. We could say that we're getting an input of any type, but that wouldn't be runtime safe. That's why we want to have a validator in place. So in Next.js, for instance, in their example code, they have like input is of this shape. Then we use that. However, that means that in runtime, you can actually send any object, and that would be passed straight to your database. So it's very good to have a schema there for the inputs that validated that type. And yeah, I'm also interested in integrating with Redwood.
JASON: So, I just want to call this out here. We were able to -- you know, looking at the posts, I was able to work us backward here to get these comments loaded on the screen. So these are our hard-coded database of comments that's now loading on the screen. So if I come in here and say -- change that out, right, it does exactly what we want it to do. So the next piece then is just figuring out, instead of hard-coding this data, we've got to get it from Prisma. And to do that, I'm going to go look at this post again where this one is saying it gets the ID out of the input, and then it's going to get the comments by doing a Prisma. In our case, it would be a comment, find many -- or find unique. Find many?
ALEX: Yeah, a find many array.
JASON: Then we'll put in our default comment select, which we need to define.
ALEX: The default comment select part is completely optional. It's just a convention that I try to do. I try to just leave hints in this example project on how it's a good way to set things up. But it's nothing you have to do. You could also just do a find many.
JASON: Got it. And look at this thing just understanding how this stuff works.
ALEX: That's mad.
JASON: Unbelievable. Okay. So let's go define this, actually, before we start writing this stuff down because otherwise it's going to feel way too much like magic. So let me go to my schema. Let's make this full width here. Okay. So here's our Prisma schema. I want a comment. So my comment is going to have an ID, the default. So I'm going to need to rewrite that because it's not going to be a number. It's going to be a string. It's going to have a name. It's going to have text. And then I also need it to have a post ID, which will be a string.
ALEX: I think you can just write post: -- or post and then write a model post. If you remove the .id. Remove the model part. And just say this -- doesn't Prisma do it?
JASON: Oh, it did it for me. I did not realize -- that's cool. That's a good little auto save tip.
ALEX: But you probably want to -- yeah, exactly. You need that one that you just removed. It's because you called this post ID. Prisma does really good autocompletion. I will connect these two things, and it just does it.
JASON: Save me from myself. Okay, so that's great. This'll take the post ID, which is this post ID, and reference it to the ID, which is this ID, so we end up with a one-to-many relationship. One post can have many comments. Okay. So that makes sense. Then can I just run that Prisma migrate?
ALEX: Yeah, now the only thing you need to do is run migrate and update the query.
JASON: Okay. So I'm going to go back in here, get your shortcut you wrote, which was db migrate dev. So we'll run yarn db migrate dev. And that should work. Do you remember off the top of your head what the command is to open the Prisma dashboard?
ALEX: Yarn Prisma studio, I think.
JASON: Yarn Prisma studio. Okay. So this is like a Prisma built in so we don't have to built all the things we need. So I'm going to jump into comment here and add some. So let me add a record. And this will be the post. Now, the post is going to be this one. And this will be -- let's say this one is going to be Sally bananas. That stuff will all get added for us. Then I'm going to add one more record. And this one will also be for this post. Then I'll be Joey pancakes. I'm hungry. Okay. Save two changes. Oh, wait. I broke it. How do I get it to do the thing?
ALEX: Yeah, that should work, no?
JASON: Ah, come on.
ALEX: Do you have to give a proper date there?
JASON: Fine. I'll do this from memory. 04/05. And it is currently 11:27 -- oh, crap, what is it?
ALEX: Impressive to get that right. But doesn't it have a default already?
JASON: It really should have, but I think when I clicked into it, I broke it. Here's what I'm going to do. I'm going to delete that record. I'm going to save this change. I'm going to add another record, and we're just going to do this again, and I'm not going to get in there. So this is the post ID. I don't need the post ID. I'm going to set that by selecting from here. Okay. Then we're going to go and say this post is the worst. Okay. Save one change. All right. So that then means we should have two posts, both linked to our existing post. And if I come back here and run dev, we're now running against our updated database that's got the comments in it. So theoretically speaking, I should be able to actually use these. So my default comment select is going to be very similar to this default post select. So I'm actually just going to start with that one. And had is going to be a comment. We need an ID. We need a name. Oh, it picked that up for us. Look at that. That is slick. Okay. So then when I put a name in, it's happy. If I go over the top of this comment select, it just shows us what's -- oh, that's so freaking cool. Okay. And we don't really need any of the other pieces here, right? We know what the post is because we're selecting by the post ID. So I don't need those fields. So then if I come down here, I should be able to -- let's go side by side so I can see what's going on. We've got our post, and we're going to go const id equals input. Then we need to do a const post equals await Prisma comments. And we're going to do a find many.
ALEX: Then it's where.
JASON: Post ID is going to be ID. Select will be our default comment select. Okay. We want that to be comments. And then if no comments, we can throw -- okay. So talk to me about what this tRPC error is here.
ALEX: Yeah, so for our collection, you wouldn't really do this. Potentially, you would return null. But this is sort of if you query for a post that doesn't exist, you typically want to show a page. You can do that in a few different ways. On the client, you can do a null check. Or you can throw an error and let -- and automatically pick it up when we are server-side rendering it. So if we throw errors, we can then propagate those errors into the page handler for when we load this on the server. And give the right status code straightaway. So we don't need it in this case. You can just do if length equals null, show, you know, no post. Or no comments.
JASON: Okay. So in this case, because we kind of don't care, like, we want an empty array. We can just return comments because Prisma will -- it'll return an empty array if we don't -- if nothing is found.
ALEX: And that should just work. And if it doesn't work, you should have type errors.
JASON: Let's go give it a shot. Look at it! Oh, my goodness. Okay. So this is actually something that I think is really exciting. This isn't one of those cases where I've used the tool before and I have passing familiarity with it and I'm able to find of fumble around. I've literally never seen tRPC before today. So I think this is a good vote of confidence for the intuitiveness once you get up and running. I was able to reverse engineer my way toward building this very typesafe setup here. And if I go back out to my ID and we look, I can see what my available things are. And if I go to put something else here, you know, we can do like a post on and comment.createdat, right. Then this is going to look terrible, but it'll at least let me -- what don't you like?
ALEX: It's because you're actually trying to render a date now. So you need to do to locale date string.
JASON: And there we go. So it's just nice that you can do that sort of thing, right. And the fact that this all came through typed, where I was able to do that. Because I'm not going to remember that off the top of my head. If I just hit control, space because it's TypeScript and we have that autocomplete, I can see what my available options are. And I can just, oh, string, perfect. This is slick. This is really nice.
ALEX: And do you want to see what happens when you're navigating a page? I can showcase, like, the actual HTTP calls that happen when you use this page.
ALEX: So go to the network tab. If we navigate to the index page first and navigate into the post, because when you reload the page, you first get it rendered on the server. So if we go here and then navigate into a post --
JASON: So here's our post.
ALEX: You can see there it does a query and the comments in one request.
JASON: Yeah, post by ID, comments by post ID, batched.
ALEX: Then what happens here is it takes the input arguments of the things you have, and it just does a JSON stringify on those and adds it as a big blob to the query string.
JASON: That's really nice.
ALEX: Preview is usually easier to watch. The preview tab.
JASON: Preview. So, we've got our JSON. Here's each of the comments. So these just kind of got passed through as values for each of those. That is really, really slick. You know, I love that it combined them to a single network request because this is one of the things that I think causes people to reach towards GraphQL. Here's all the data I need, and GraphQL sends it off as one request, right. That batching kind of happens by default in the way that those queries are defined. But that's only sort of true. If you're not thoughtful about your GraphQL and send off 15 GraphQL requests, they're all going to be independently fired, unless you've set up something like this batching. So what I like here is that what we set up in the example, if I go and look at this app here. I can close this one, and let's close that down. We pulled in the HTTP batch link here. Then all we had to do was when we set up this with tRPC, we just pass in the HTTP batch link. My assumption is that what we're saying is if you find requests from this particular end point, batch them together. That's what that configures.
ALEX: So, I've sort of stolen how Apollo -- they have a concept of links and exchanges. That's a way of controlling the data flow. When the client sends a request and then when data is coming back. So there's three different ending links in tRPC. There's HTTP batch link, there's HTTP link, and there's web socket link because we have web socket supports as well. What the batch link does is just -- it's using this sort of data loader pattern that you might be familiar with, with GraphQL. When it gets a request to do something, it doesn't fire that off straightaway. It waits one millisecond before it fires it away. And then it takes all of those requests that have been done in that same tick and fires them off together. Then likewise on the server, we have a way in tRPC to handle batched request.
JASON: Gotcha, gotcha, gotcha. Well, I mean, that just feels really powerful. You know, for this little bit of config here, we get this benefit of these requests happening. You know, kind of all at once. So it's a single request, post by ID, comments by post ID. So we're not burning that extra time of HTTP overhead.
ALEX: And another thing we can notice here is that it actually does an HTTP get, which is one of the more annoying things with GraphQL, right. Everything is a post. You can't cache anything. So here you can actually do HTTP-level caching on queries pretty seamlessly. So if you deploy this and add so much cache headers on the back end, you can actually have, like, a server-side rendered app with a lot of server-side requests, but cache all of the API responses on the edge by just adding some HTTP headers.
JASON: Gotcha. Very, very cool. So a question from Allen in the chat. Is the time configureable for batching? Look, right now it waits one millisecond. Can you tell it to wait ten or something?
ALEX: I had a feature for that. Because it's actually quite nice to use batching for optimistic prefetching as well. So if you have a list of posts, you might want to be like, okay, let's fetch all of the posts on the page optimistically. But don't do it straightaway because you might scroll and change the things that are in view. I never merged that pull request because it felt like it was only me that was going to use it. But if someone wants to have that, just reach out because then I can dust off that old PR and test for it and everything.
JASON: Nice. So if you have a use case, chat, take it to the repo. And, let's see. Would love to see tRPC in Vue and Nuxt. What would it take if somebody wanted to fire this up in Vue or Nuxt right now?
ALEX: So, we did some exchanges recently. There were some problems with ES modules, to run it in Vue. Then Nuxt is using ES modules, which we had some interoperability problems with. I think I fixed those. But I haven't looked at it. I know there are example projects with Svelte. If it works in Svelte, it should work in Vue as well.
JASON: Nice. Okay. So we're looking at about 19 minutes before we got to wrap this up. Do you think that is enough time to put together a very quick comment form and post?
ALEX: Oh, yeah, yeah. I mean, you've done the hard parts already. Now it's just copy pasta.
JASON: So I'm going to add a comment, put together a quick form for this, figure out how that form is going to submit in a minute. For now, we'll do a name, and the input is going to have an ID of name, type text, name of name, and -- I hate that whole thing. Getting it one too many times. Let's get the comment. Let's make this one a text area.
ALEX: So just a note here for people watching. In this example project, I'm not using any form library, but it's a really -- there's one that really integrates nicely. That's React Hook Forum. Then you can actually reuse the same validator for your form on the front end and for your tRPC back-end mutation.
JASON: Let's see. We don't have any styles, so I'm going to do this the quick and dirty way, which is just add line breaks so everything sort of looks like a form. There we go. Okay. So we've got a basic setup here. Now, I'm going to need to capture this form submit so we can do an on submit. And that's going to need a handler. We'll call that handle submit. Then up here, we can do function handle submit. That's going to get the event. And this I'm going to need some help on because I can't remember how to type form submissions.
ALEX: I can't remember how to type form submissions either.
JASON: Look at this. Is this correct?
ALEX: It's not far off.
JASON: Holy crap, Co-Pilot.
ALEX: That tRPC use mutation needs to be in the render loop.
JASON: Right. So this part -- this will just be called whenever somebody hits the submit button. So that'll be on any kind of user action. So this part we need to move out. In fact, I'm just going to do that for right now. I'm kind of blown away how well that worked. So that's going to be our input, which is name text. We have our post ID, which is the ID. We don't currently have. I'm going to drop that in as a hidden element. So we'll do input type hidden. Name of post ID and a value. Data ID?
ALEX: I think you have your use mutation in copy/paste, that you want to paste in here.
JASON: Oh, I actually definitely did not do that, but it's going to help us. (Laughter)
ALEX: Oh, wow. It does the invalidation and everything. That's bad.
JASON: Okay. So this is going to be post ID instead of the regular ID. But otherwise, dang.
ALEX: It's like 80% right. Before that, you have to do -- you have to use the tRPC context on the line before. So do like const utils before this hook. And that's a hook. So it needs to be in line in the render function.
JASON: Got it.
ALEX: So the use hooks line up.
JASON: No overload matches this call.
ALEX: Oh, it's called ID in the back end. So it's not post ID.
JASON: Comments by ID.
ALEX: Not there. In the input.
JASON: Oh, oh. I see what happened. So we've got comments by post ID. That's this. We need to define our comments add. Then this will give us a mutation, and to call that mutation, we put in our input.
ALEX: Yeah, you need to add comment.mutate, I think. And now we need to make a mutation that takes that input.
JASON: Okay. So now we have a .query, and I can make a .mutation. That mutation is going to be called add. And we can drop down here. So I'm going to just save for a minute. The input is going to be -- it was z.object. That needed -- yeah, we'll need a name and post ID. And I'm not going to cap the name. So, it can't be empty. Good. And then we have our async resolve. That needs to be something.
ALEX: Wow, that's amazing. It's correct. It's exactly how you want it to be.
JASON: That is really impressive, how much GitHub Co-Pilot just does for you. So that then works. So out here, this now works. And is doing what we want. Which means aside from the fact that this probably needs -- I don't know. There's probably a way to type that. I don't exactly know what it is.
ALEX: What I usually do with handlers is rather than defining them in the render like this, I just call how this is a big blob in the submit handler. Then you get the type inferred.
JASON: Okay, I got you. So we'll drop it right down here.
ALEX: That's why I don't know how to do it because I always do this.
JASON: And how it's happy. Oh, and there's the type we need. So if we ever need to manually type it, we can do it like that. So we have our name, our comment. We have our input, which includes the name and comment value. We get our post ID, which is defined way up here. And then we have our mutation that runs. We reset the name and value. I think this will work. So let's try it again.
JASON: Unbelievable. So if I refresh the page, it's still there, right. And if I go out to Prisma Studio and refresh -- oh, Prisma Studio is not running anymore so that's not going to show me anything. This is slick. This is really slick. So if I come back to our homepage and say another post, okay. So now when I go into my new post, all works. Another post, no comments. We'll say -- this is -- you know, I love it when things just make sense. Like, I love how nice it is to work with tools that just get out of the way. You know, it feels like what's happened here is once you get through the initial setup, because this, to me, makes sense. This file with the comments and everything we created, it just makes sense. We're not using that, so we can drop it out. These app setups, they're not -- like, they're a little overwhelming, but the fact it's all built into the example is really, really nice. The baseline of the server, this honestly is not particularly overwhelming to look at. And it just kind of makes sense to me. You can see what we're doing. We're just adding more features there. This is really well done. This is really nice to use. I can definitely see this making its way into a lot of apps for people who are going to be, you know, looking at doing this sort of thing. So, chat, who's talked about this? Let's get a W in the chat if we're feeling it. Alex, where should people go if they want to learn more about this?
ALEX: So, on the tRPC website, there are a bunch of different example projects that you can have a look at. Also, in the bottom of the side bar, there's an awesome tRPC collection, which is at the very bottom. Here there are more examples to look at. So there's React Native examples. So one thing I'm really stoked about is you can get this exact experience that we've showed you in a React Native app, and I don't know anything else that can couple your back end to your front end to an app like that.
ALEX: So you get autocompletion of all the queries you want to do. You can easily update. Considering we have, like, older updates and stuff today, you don't really need to think about API versioning like you use to. So even if you sort of break an old app with a change, you can just roll out a new version to everyone silently.
JASON: Nice. Very nice.
ALEX: But yeah, I think the best thing to do is to go here, play around with the example app. If you get stuck, you can look at the docs. If you get stuck in something that's not covered, don't hesitate to reach out on the Discord, on Twitter, or on GitHub discussions. There's a bunch of people that are really helpful and help me with the sort of help channel on Discord and stuff like that.
JASON: Yeah, and this is really cool stuff. Make sure you go and check this out. If you build anything, make sure you share that with me. Share it with Alex. Put it out there. Learn in public, everybody. So with that, I think we're going to call this one a success. So let's head back over and just do a quick shout out to Rachel at White Coat Captioning, who's been doing the live captions this whole episode. Thank you so much, Rachel. That's made possible through the support of our sponsors, Netlify, Nx, and Backlight, all kicking in to make this show more accessible to more people. While you're checking out things on the site, take a look at the schedule. We've got a lot of good stuff coming up. We're going to have Brandon Roberts coming back to teach us how to build a live voting app with Appwrite. We're going to have Chance Strickland teaching us how to build esbuild plugins. You know me, I love to generate images, so that's going to be a lot of fun. And we have even more coming that I haven't had a chance to put on the site yet. So stay tuned for those. You can get all of those on your calendar with Add on Google Calendar here, or you can follow on Twitch or subscribe to YouTube to make sure you get the latest episodes. Alex, any parts words for the chat?
ALEX: No. Just give me a shout out if you like it or hate it or if you have ideas or if you build something cool with it or something shit with it. Let me know if you look at it.
JASON: For sure. All right, y'all. I think we're going to call that all done. So stay tuned. We're going to go find somebody to raid. We'll see you all next time.
Closed captioning and more are made possible by our sponsors: