TRPC's Creator on the Builder Pattern
Alex, the creator of the TRPC library, walks through their understanding of the builder pattern in TypeScript using an example.
0:00 I asked Alex, the creator of tRPC, to talk me through his understanding of the builder pattern and how it was implemented in tRPC.
0:08 We've got this publicProcedure then, that has a .input and a .query on it. This is using the builder pattern. I would love to use this as an excuse to just get your thoughts on the builder pattern. Could you explain it like I'm five, using this as an example?
0:26 Basically, here, you have a publicProcedure, which is just an object, mind you. It's just an object. When you call input on that, you return a new object with different behavior. You can do that recursively because every time you call it, it returns itself but with some more type information.
0:53 We have the base procedure that doesn't have any types. Then you call input on it. Then it's another builder that has some more types. Then you can call input again. It will combine those inputs that you already had. Then, every time you call it, you will return a new instance of itself, until you call the ending thing that actually returns the procedure itself.
1:19 The ending, in our case, it's either a subscription or query or mutation. You can either have a query, a mutation, or a subscription as a procedure. That's the last thing you do. In that, you actually write the logic that is unique to this procedure.
1:36 Everything else, as part of that build chain, can and should be reused across multiple procedures. You can do a base procedure that has authentication, where we check that a user is authenticated. The context object includes a user.
1:57 Then you can infer a new context to the following builder, the new builder instance that is created. In that way, you can make these really nice, reusable snippets of code. Yeah, build out your application that way.
2:11 The idea here is that we have publicProcedure. It looks like a ProcedureBuilder. Then we have inputs. You call input, which returns another ProcedureBuilder with more generics in it. Then you call query, which doesn't return a ProcedureBuilder. It returns a BuildProcedure, like the final instance of the thing.
2:33 Yeah. That should change name. It should be just Procedure or Result or something. Yeah, that's what it does.
2:42 But we can't do like input on this or something and continue the chain.
2:46 No. The procedure is built up to that point. Then you end it. It completes the craziness.
2:53 Why would you do this like this instead of, let's say, having, let's say, an object with input on it or a query on it, like this? Why use that specific type of syntax?
3:06 It makes it easier to make things reusable. We have considered this pattern, as well, as a shorthand way of doing things. Because I do agree that an object reads better, if I were going to be honest. When you have that, I think it reads a lot better.
3:25 The problem with this is that you can't do a base procedure and reuse it. If you only had this API, any time you define a middleware, you would have to define that in every procedure. Without a builder pattern, that I know of, in nice way, achieve something like that, so we've opted to have an only builder pattern API.
3:53 There you go. The builder pattern gives you a lot of reusability but also gives you a really nice paradigm where you can add generics, bit by bit, into a structure and then get back something that lets you be really strongly typed.