Refining the tRPC setup
Refining the tRPC setup
Get the project source code below, and follow along with the lesson material.
Download Project Source CodeTo set up the project on your local machine, please follow the directions provided in the README.md
file. If you run into any issues with running the project source code, then feel free to reach out to the author in the course's Discord channel.
Lesson Transcript
[00:00 - 00:10] So at this point, our backend and frontend are able to communicate with each other. And that's great, but as you saw in the output, it's using plain JSON for this communication.
[00:11 - 00:19] And while JSON is great for most things, it only supports the primitive types in JavaScript. So specifically that string number and Boolean.
[00:20 - 00:29] It doesn't even support undefined. If you pass an object that has a value that's undefined, it will simply be removed from that object when stringifying.
[00:30 - 00:39] And that's not great because we might want to specifically send an undefined value. In addition to that, it doesn't support any type of class or anything like that.
[00:40 - 00:53] You can alleviate this a little bit by creating a method called .2JSON on a class. And for instance, the built-in date class has this specifically done, which means that you can stringify something to JSON, but it doesn't help when parsing it.
[00:54 - 01:00] When you try to go the other way, it will just be a string. And we want to make sure we can actually pass, for instance, a date.
[01:01 - 01:12] So in order to alleviate this, we're going to use something called a transformer, which is something that TRPC supports. That sort of helps you enrich the protocol underneath without really noticing it.
[01:13 - 01:31] We're going to use the transformer called Super JSON, which is just a library that will take a JSON object and turn it into a sort of combination of JSON objects, where one is the values and the other one has the metadata. So it knows that this particular object, or this particular property, is a date property.
[01:32 - 01:41] And so it will be able to both stringify and parse those values. We can extend this with our own things as well, so that will come in handy later on.
[01:42 - 01:54] We're going to need to have Super JSON installed in both our back and front end, because it needs to take care of both serialization and deserialization on both ends. So let's start by adding it to both of our services.
[01:55 - 02:10] So I start by going into the back end, and I install Super JSON here. And so in order to enable it on the back end, I go into my TRPC here and provide an options object.
[02:11 - 02:18] And I can specify a transformer. It needs to be Super JSON-like so.
[02:19 - 02:29] I will then go into my front end folder and install it here. And I will likewise go to the TRPC in the front end.
[02:30 - 02:40] And peculiarly or enough, you can now see that I'm getting an error message here, because I'm actually not specifying a transformer. So I guess they have a little check for that in place, which is very nice.
[02:41 - 03:01] So we'll add the transformer here and say that should be Super JSON as well, like so. So now we have Super JSON set up as the transformer on both ends and all communication both in terms of parameters and results or return values should be serialized using that.
[03:02 - 03:18] So we should now be able to pass undefined and date and other classes and non-primitive types through that, which is very nice. The last improvement we want to make to our TRPC configuration is to add runtime type checking.
[03:19 - 03:24] And you might ask why we would need to do that. Since we saw that everything was statically type checked at compile time.
[03:25 - 03:38] And that is true. But as you might also know, the TypeScript type system isn't sound, which means that it's quite easy to end up having something that at runtime is actually different than what you specified at compile time.
[03:39 - 03:47] And in order to catch bugs as early as possible, it's nice to do some type checking at runtime as well. We're going to use a library called SOD.
[03:48 - 03:56] There are many alternatives, but SOD seems to be the most popular, at least at the moment. And I think it has a fairly straightforward syntax and a nice developer experience.
[03:57 - 04:10] So the nice thing about SOD, as with several of its competitors, is that you can specify something with the runtime syntax. And TypeScript will be able to infer the compile time type from that object.
[04:11 - 04:16] So at least we don't have to specify our types twice. We only have to specify it once with this new syntax.
[04:17 - 04:27] And we don't risk having types that are diverging by accident as we go along. I tend to use SOD a lot for all kinds of checks here and there.
[04:28 - 04:35] So let's just immediately install it both in our backend and front end. I'm in the front end folder here, so I'll install it here.
[04:36 - 04:47] And then in the backend as well. Now, in order to add runtime type checking to our endpoint, all we need to do is specify it.
[04:48 - 04:56] So we have these two endpoints here, and let's ignore the ping one for now. The get service types doesn't take any inputs, so there's nothing to verify there.
[04:57 - 05:03] But it does return an array of objects. So if we want to make a SOD type check this far as a runtime, let's try and do that.
[05:04 - 05:21] So we start by importing the Z namespace from SOD. And then using that, we can create an object like so.
[05:22 - 05:32] And so let me try and explain what's going on here. So the Z namespace exposes a number of functions that represent the kinds of types that we can create.
[05:33 - 05:35] This is getting a little bit meta. I hope that I'm being clear here.
[05:36 - 05:46] But basically, you can see that Z, for instance, has a function called array, which we're invoking here. So important to note is that get service types output is an instance.
[05:47 - 05:47] It's a variable. It's not a type.
[05:48 - 05:58] So this exists in JSLAND, and will be available at runtime. So this array of objects have each have an ID and a name.
[05:59 - 06:02] That is a number and a string. So this matches what we're trying to send down here.
[06:03 - 06:12] So with this object now, we can add this to our query here as the output type. So the way we do that is we specify output here.
[06:13 - 06:26] And then we say get service types output. And since the compile time type is being inferred from this, we do have static type checking at this point.
[06:27 - 06:43] So if I change this to a string, for instance, you can see here that I'm being told that this isn't assignable to the type number. Before I had this, though, I would be able to say as unknown as number.
[06:44 - 06:51] Did I mistype that unknown? There we go.
[06:52 - 07:01] So this cast would trick type script into thinking that this hello here was actually a number. And this would survive compile time checking.
[07:02 - 07:07] But it would now fail runtime checking because Zard is going to check that the ID is a number. And so that is going to fail.
[07:08 - 07:21] So we've now made it a little bit safer to make sure that everything works. And that gives us a bit more confidence that we're not making any mistakes even at runtime.
This lesson preview is part of the Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL course and can be unlocked immediately with a \newline Pro subscription or a single-time purchase. Already have access to this course? Log in here.
Get unlimited access to Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL, plus 70+ \newline books, guides and courses with the \newline Pro subscription.
![Thumbnail for the \newline course Fullstack Typescript with TailwindCSS and tRPC Using Modern Features of PostgreSQL](/_next/image?url=https%3A%2F%2Fs3.amazonaws.com%2Fassets.fullstack.io%2Fn%2F20240619184857865_KristianDupont_FullstackTypeScriptwithTailwindCSSandtRPCusingModernFeaturesofPostgreSQL_1450.67x816_CoverImage%2520%25281%2529.png&w=3840&q=75)