Do you know we can replace API schemas with a lightweight and type-safe library? With tRPC you can easily replace GraphQL or REST with inferred shapes without schemas or code generation. In this talk we will understand the benefit of tRPC and how apply it in a NextJs application. If you want reduce your project complexity you can't miss this talk.
Get rid of your API schemas with tRPC
Transcription
I'm so happy to be here, and today we are talking about how to get rid of your api schema with TRPC. So I want to start my talk with schema fatigues. So we have two main ways to communicate from your server and client. We have OpenAPI and we have graphql. So OpenAPI is an OpenAPI specification, the final language agnostic interface. So you know HTTP verbs like post, get, patch, delete, et cetera, et cetera. And it's a new language to know. So if you want to use OpenAPI, you have to learn a new specification. Then you need to write case-sensitive JSON or YAML. By the way, YAML sucks. And you have to generate your schema, your typescript types, every time you change something into the OpenAPI documentation. Okay. Let's see the graphql part. graphql is an open source data query, manipulation language, again, language. And you need to learn new things. So it's a graphql format. And you need to learn the new specifications. So query, mutations, stuff like that, so on and so forth. And then you have always to generate your typescript types. So if you change something in the graphql, you need to generate this type and use them into the client. Okay. I'm struggling a lot with these kind of things, because I'm working in a consultancy company, Flowing. And I found a solution in TRPC, and then I will show you how. My handle in LinkedIn and Twitter is Giorgio underscore Boa. If you want to tweet about this talk and tag me and tag also the conference, I really appreciate. So let's jump back to TRPC. TRPC is a quite famous library. More than 100,000 weekly npm downloads, and more than 60,000 starts on GitHub. So quite popular. Okay. And all the library is based on this type of that you can see on the screen. Okay? So here we have an object, my job object with my first name and last name. And with the type of, we are able to extract the shape of this object to get a type. Okay. And now we will show how it works with TRPC. Here I have a next.js application, a simple one. And I decided to add from scratch TRPC to show you how easy it is to implement it. So I created also a connection with prisma, because TRPC and prisma are really a big match. Okay? So here we have our schema, prisma schema is our model, model author, okay? And we have a seed, because prisma can help us with the seed, seeding our database if it is empty. Okay. We have a source folder, and as you may say, know, in pages, we have the slash api folder. Inside of api folder, we have, we need to create a TRPC folder, and inside of a TRPC folder, we need to define a file like this one with square brackets, and next for us, grab all the query parameters and give us the information. So we need to expose this endpoint. In this endpoint, we define from TRPC next with this api, we define our endpoint. In this endpoint, we have a few configurations, but the most important thing is this up router. In this up router, we have the possibility to define many routers. Here you have only the author router. And here we have the type of, I mentioned before. So from this object, we are extracting the shape of the object and defining the up router. This type is really important for the client. And now we will see how. This is my author router, and in this author router, I have a few different procedures. So I define to get a list of author, I can add author, and then I can delete one, of course. So this is my business logic. And in the business logic, I put the prisma api. So prisma author dot find many, or for example, if I want to add a new author, prisma author find first, and then if he's not present, I will create the record. But let's jump to the client. We see before that we are export type up router. So we are extracting the shape of this router. And now in the client folder, we define the TRPC file. And in this file, there is a few configurations, but the most important part is this one. Create TRPC next with the generic, and here we define up router. The up router arrives directly from the folder we saw before. So server routers underscore app. In this way, we are able to use TRPC in the client part. So here is our slash index, okay? The home page of our application. We can use TRPC like this. TRPC dot author dot list. But if I remove this list, and I press control space, as you can see, with typescript, we are able to know that I can call the add, delete, and list. And this is so cool, because we didn't generate any kind of types to do that. It's all of the things out of the box. Okay. So I... Okay. Great. So, as you can see here, we are looking for the authors. Then we manage the handle delete and handle save. And every time I save and delete, I invalidate the list to refresh the list. So we can start our application. Here there are some prisma calls. Okay. And if we go here, we have this list, and with the save button, we save new author, and then we can delete them. Great. Okay. One really cool thing is that if you go into the routers, in the author router, great, and we decide to change the name of a procedure, so let's change this one, we can rename the symbol add to add new. Okay. And boom. Into the index. Here. You can see that typescript renamed it for me. So it's really great things. But if we decide to not do with the renamed symbol, but do it manually, so I remove here is head. Okay. If we do it manually, like this one, as soon as I move myself into the index, we can see that typescript is notifying me that something changed under the hood. So it's a really great benefit to interact from your server and client part, and I think this is so cool. But I want to show you more. Now we are going deep into the library, because I want to show you how it works under the hood. So let me refactor my class here. Add new, remove, save. So I think now it's okay. So if I go here, perfect. If I open the inspect, I place the few breakpoints into the client library of TRPC. Okay. So if I click on this one, yeah. Great. And I refresh. As you can see here, we are defining. We are into the TRPC client. Maybe you can see. Maybe yes. Okay. We are inside of the TRPC client, and we are defining the methods. So query is a get, mutation is a post, of course, because a mutation needs to have some information extra. Okay. If we go on with the debugger, and we enable this to debug, okay, refresh. Maybe I can, okay. Zoom a little bit. Okay. As you can see, we have use query. This is an internal function. And inside of use query, we have the path. So author.list. If we go on with debugger, we have the mutation. So we have author.add. And also author.delete. Great. And I want to show you another thing from the client. If we go on. Here we have the HTTP request. So under the hood, the library is going to trigger an HTTP request. And look at the URL. So as you can see, slash api slash TRCP slash blah, blah, blah. So author.list is our procedure, and then you have a lot of parameters that next.js under the hood grab for us. And then the client part of the RCP is able to call our business logic. But how? Let's see in action the client part, the server part. So if I put a debugger here, and I run, maybe I stop this one. And I run the debugger from the Visual Studio code. The application, of course, is the same because it's the same code base. As you can see here, we have a breakpoint into our business logic part. But if we jump back a little bit, we can go into the library. So here you can see TRCP server, this index dot blah, blah, blah. So if I refresh the application, I want to show you this particular property. So let's check. Okay. Great. So inside of the OPTS, we have procedures. Inside of these procedures, we have list, add, and delete. And this is the internal function of TRPC server. And inside of delete, we have underscore def. And inside of underscore def, we have the resolver. Resolver is a function. We have webpack stuff. But in the end, the function is our function. So prisma dot author dot find many. Select and blah, blah, blah. So if I run the application, as you can see, all the flow ends here. So this is how more or less the TRCP server works. Great. So let's jump back to the slides to discuss about the pros and the cons of the library. So pros. No code generation, as you see before. We didn't generate any kind of code about our procedures, mutation, and stuff like that. Type safe by default. So we changed the name of the procedure, and typescript soon alert us that something is wrong. And then better collaboration between frontend and backend. Because we have a big language. Because in the backend, we call the add, and in frontend, the same. With REST api, sometimes the name of the function are different. So now with this library collaboration, I think it's better. What are the cons? typescript stack. So you need to have a backend in typescript and a frontend in typescript as well. If you want to have a versioning of your api or select some particular fields in the model, you want to select only one field and stuff like that, you need to create more procedures. One for the version one, one for the version two, and so on and so forth. And another cons that is not really a cons, if you need to expose this api to the public, you need to use this particular library. This library is on top of TRPC and is TRPC-openAPI. So with a decorator, you are able to expose this api in the public and create also the open api documentation. Great. So a summary. If you want to start right now with TRPC, you can use create.t3.gg. This library bootstraps TRPC application with Tailwind, prisma, Next authentication, and et cetera, et cetera. A lot of things to do without manual configuration, and it's really cool. Who is using TRPC? Netflix, Intervalkal.com, but in this particular issue, you can find a lot of companies that sign the name to show that they are using TRPC. And if you want to try it now, you can go in TRPC.io and you'll find a lot of resources about TRPC. So here I insert a slide to take a picture of you, because I need to prove to my wife that I was at the conference. Please say hi. Great, great. Thanks. Thank you very much, Giorgio. Please come into our Q&A booth. That was a great talk. I have been a little bit lazy myself learning about TRPC, and I'm glad I didn't, because now I learned it from you. So that was time well spent. That's awesome. And I think a lot of other people have also learned a lot, because we have a lot of good questions here. And of course we have to start with the first question. It is the Elon question. Would Elon Musk approve of more than 200 TRPC calls, and how do you make sure that they aren't poorly batched? Okay. I think Elon Musk is too pitchy, let me say. Okay, it depends on your context. You have to reason on the TRPC stack, and if you have a lot of procedures, you need to divide them into many services, and then communicate with the services with TRPC or maybe other channels. Okay, so basically he does not know what he's talking about. Yeah, yeah. Never, never. Ellie's clapping over there. Very sad clap. Okay, let's keep going. This is a question I actually also was thinking about. How would you handle a situation where you have multiple frontends that aren't in a mono repo with a backend, right? Is that something TRPC can support, or is it only for that backend for frontend architecture? Okay, so you have a mono repo with a... You don't have a mono repo. I don't have a mono repo. So you have a client in one repo, and you have a server in another repo. How do these two things keep in sync, and how do they communicate? This is a really great question, and fortunately I have the answer. So you can... If you have multiple repos, you lose the refactoring feature, of course, because you have the code base in two different parts. But if you encapsulate the type of the router inside our library, you are able to include that library in the client, but in the end you will be struggling with the versioning things and stuff like that. So you can do it. Would you recommend it? I think the full stack typescript is the best approach, but if you want to include TRPC in your application with multiple repos, you can do it. Next question is a question of scale, as always. Can TRPC work for large-scale applications, especially monoliths, or is it best suited for smaller apps and microservices? I think it's best with microservices and create multiple services. There are some POC from the author of TRPC that is showing how to integrate multiple services to communicate and create microservices architecture. That's cool. There's a question. What are the advantages of TRPC over Relay, specifically Facebook's Relay framework? Have you used that and do you know what the advantages might be? No. Yeah, that's a tricky question. I think Relay is one of those things where the people who love it truly love it and everybody else doesn't understand it. So talk amongst yourselves, find yourself a Relay buddy and convince each other to use it. I don't think I haven't answered this question either. It's an Elon Musk question. Yes, exactly. How do you deal with caching and cache invalidation of entities in TRPC? Is it built in or do you have to do it yourself? Hander the Hoodie is using a react query, so you can grab all the features of a react query. Also, a cool feature about TRPC is the batching. So if in one second you fire a lot of calls, Hander the Hoodie is batching them to create cool stuff. Nice. Let me just see. There's a couple more questions. Maybe let me just get them through the moderation and then we'll read them together. Here we go. Does TRPC handle race conditions when multiple people hit the api to perform the same action or are things like that offloaded to prisma? So basically I think the question is about conflict resolution. The conflict resolution is inside of your business logic. As I showed before, if I want to create another author with the same last name, last name for in this case is the key, the primary key, you need to check before if the primary key exists or not, and then if it exists, show an exception. This is an interesting question. So TRPC for clients only. Does TRPC have any use if you don't actually have a server component? There's not much you can do with it, right? I think no. No. The TRPC client doesn't make sense without the TRPC server, because it's calling the TRPC endpoints. By the way, TRPC is a hell of a mouthful to say. So RPC is for remote procedure calls, but what's T for? Is it typescript or types? typescript. I think it's typescript RPC, because RPC is calling a function of another server. And I misspell TRPC, TRCP a lot. It's a mess. All right, well, let's get into a hot topic, a hot potato issue. Do you think graphql or REST is better than TRPC in some cases? Are there cases where TRPC is not going to be the right solution and graphql or REST would be a better approach? Yeah, I'm smiling because I know the answer. In some contexts, TRPC is so good, but if you need to expose with a public api, maybe a REST api is better, graphql as well. If you have multiple clients, like let me say TV, mobile, and front-end application, and you need to grab some property from a model, maybe graphql fits better for you. So there are many, many use cases to use graphql and OPA api as well. This won't be a replacement for them, so it can be a good solution in some contexts. Nice, yeah. Always use the best tool for the job. There is no one silver bullet or one magic hammer. And always it depends. It depends, of course. Does TRPC provide a runtime type check or the validation of data coming into the api? Yeah, yeah. This is a part that I need to skip for the timing, but when you call a mutation, you have in the input object, of course, if I want to add an author, I need to pass first name, last name, and in this case, the country. And you have Zod and many other libraries for validation the input and throw an exception and notify you that you are passing something wrong. So Zod is at the base layer for TRPC, and there are many other validation libraries that can be useful in this case. Yeah, it's incredible how much Zod has taken really the typescript development world by storm, because there's been libraries for this forever, but I think Zod just gets something right about the api. It's just so friendly to use. Yeah, it's really amazing. It's very nice. I use it for everything as well. It's wonderful. All right, we have time for a couple more questions. This one is interesting. Can we use TRPC for service-to-service communication, or is it only for when you have front-end talking to... Yeah, we mentioned before multiple microservices architecture, so you can use it for communication. Let me ask my own question, because this is something I was wondering. So for example, if you look at other RPC frameworks like GRPC from Google, right? One of the reasons why it's really good for inter-service communication is that it handles things like versioning of the things you can always add into documents without breaking existing services, and it also does the binary packing layout thing, so it's very performant for things. Is TRPC at all concerned with these things, or is it mainly a simpler solution to where GRPC might be overkill? I think if you have multiple services in multiple repos, you have the same problem, because you need to version the type and then use the right version for the right service. So if you change something in multiple services, you end up with a big bang deploy. Yeah, so that is going to be challenging. So maybe GRPC is still better for the inter-service communication. Here's a question, it seems like headless CMSes are a big topic today. So have you worked with TRPC for classical headless CMS use cases, and is it of benefit there? Can you... No, I didn't use TRPC for headless CMS. Currently I'm working with Dato CMS, and I'm using graphql, and I end up in the schema Fadiq, so if I change something in the schema, I need to create all the type, typescript type, and it's a mess. Nice. Okay, and one final question, this is the easy one. Oh, here we go, this one. So first of all, congratulations for doing a live coding talk and not messing anything up. Can we have an applause for Giorgio? You are a braver man than I am. Well done. So where can we find the code for your demo? Is it available somewhere that people can look at it? Yeah, I can share my repo in my Twitter, and I tag also the conference, of course. So you can find all the code and the slides there. Yes, please do tweet pictures about Giorgio for Giorgio, so his wife doesn't get too jealous of us and his love for javascript. Now let's give a big hand for Giorgio. Thank you very much. Thank you.