 Hello, everybody. My name is Ethan Arawood, and welcome to OpenJS World 2021. Today, I'll be talking about safely handling dynamic data with TypeScript. So what do I mean by handling data? Well, everything we do on the web involves data. Simply loading a page is loading data from a server. But let's get a little bit more detailed with that. Think about the parts that make up your web application, whether it be the API routes, the forms in your front end, the authentication between both the client and the server, and even the environment variables you're using to deploy your apps to your users. It's all data, and it's all fairly dynamic, especially user input information. So let's consider a JSON object payload, representing a user. We have a bunch of different properties here. The user's ID, the name, are they employed, what company do they work for, how old are they, and what projects have they worked on. And now let's go ahead and define a Fastify API route. This API route will be writing this user to a fake database. So we'll call it add user, and we'll make it a post request so that there's a body. So given the current code, what type do you expect that body property to be from the request object of the request handler? Do you think it's going to be a record with a generic T? Or do you think it's going to be an object or any? Well, I hate to break the news to you, but it's unknown. In fact, it's unknown intentionally as well. We want to make sure that that body property is not unsafely used. And what I mean by that is, imagine, given our user data that we're going to pass to this API route, we know as the developer writing it what shape it's going to be in. And however, who's to say that another developer in your team knows that shape of that data? And let's say you go ahead and you're in this API route and you make call to body.name. You, as a developer, expect that it exists and that it's a string. But what happens when someone only submits a user object with an ID? Currently, there's no validation. And there's no checks if that does or doesn't exist. So by setting the body property of the request object to unknown, the call to body.name will actually not result in a string but in an error. In fact, TypeScript error. So your code won't even compile. In this case, the error you're going to receive its object is of type unknown. So how do we solve that? Well, let's use JSON schema. JSON schema is super powerful. It looks a little bit like this. Essentially, you describe the shape of your JSON object or your data payload. And you can use that schema to do things such as validation, serialization, parsing, and more based on your schema. And generally, JSON schema is intended for validation. So most of the time, you're going to use it to say, hey, this JSON blob that I've received from an unknown thing could be a client in our case. I want to validate that it is the shape that I expect it to be. But this is 2021. Validation is not all that JSON schema is good for. Additionally, there's new tools, such as Typebox, which actually allows you to derive the types, type definitions from your JSON schema. And so the Typebox API is a very fluent-like API, where you can see here on the second line of the second code block, we're defining a constant variable T that is the type.string method, or a call to the type.string method. This results in that T variable having the shape or having the, you know, being an object with a single property type assigned to the value string. This is a JSON schema. It's a very simple JSON schema. And it's not very useful in this case. But you can combine a large part of the Typebox API together to build your JSON schemas. And then you can take that JSON schema and you can derive the type of it via the static method that is also provided to you from the Typebox API. So putting these two together, here in our code sample, we start with this body schema variable. It's a call to the typebox type.object. And then we define our six properties that we expect on our user object. And then for each of these properties, we define what it is. ID is a string, names a string, employed as an optional Boolean, company is an optional string, et cetera, et cetera. Then we define a type, a matching type. Body schema is the result of the static generic function from the Typebox API. And now, inside of the fastify.post method, not only are we passing that type body schema to the generic, named generic parameter object, that first line of the fastify declaration, but then within the fastify.post method itself, we are adding a second argument. And the second argument is a set of options. In this case, we're using the schema option and we're passing in that same body schema to the body property. And now, automatically, fastify is gonna do two things for us. First, it's gonna validate that that body is going to be the shape of that schema that is derived from the Typebox API. Additionally, inside of the function, the body property on the request object is going to get the static type from that body schema type. So now, inside of our request handler, you can see here we have some comments that type of body is going to be body schema. And thus, type of body.name and type of body.age are going to be string and number or undefined respectively. So, incredible. By combining all three of these tools, we're able to create a much, much safer API developer experience. Now, you can ensure that your fastify app and the data that it's receiving at any of your routes is going to be the shape that you expect it to be. But wait, it gets better. Let me introduce you to a similar approach but on the client side. For this, we're still gonna be running in Node.js so we're gonna be using Indici and AJV natively. AJV is actually the library that is fastifies using underneath the hood. It stands for another JSON validator. And Indici, if you don't know, is a brand new HTTP client written from the ground up for Node.js. If you wanna know more about it, you should go check out Matteo's talk too. I believe he's speaking about it today. So, let's look at another code sample. In this code sample, we're gonna start with a similar type box experience. We're defining a schema type or a schema variable and a schema type. Then, we're gonna define a function. This function is a type guard. So what that means is that it returns a Boolean and that Boolean, if it's true, the argument response data is going to be given the type that we're specifying on that response data is add user response schema. So to sort of rephrase that, this function is gonna be given an argument, in our case, response data. That argument is gonna be of type any or unknown. Then, if this function returns true, then that same argument reference, that response data that you may pass into this function, will be given the add user response schema type by the TypeScript compiler for the remainder of that context. And we'll see that in use at the end of this file. But to continue working our way down, you can see we're defining a user using that same user schema from the previous code sample. And then, we're awaiting the undici.request call to our API and that add user route. We're stringifying that user object. And then the data we receive back from Undici, we're going to collect it into the data variable. We're gonna parse that using just the default JSON parse method. And then, using that response, that const add user response, we're gonna pass it to our function is add user response. And again, if it validates, then within that if block, that add user response variable is going to have the type add user response schema. And thus, calling console.log add user response.message is safe. We know that it exists because we're using AJV inside of the is add user response method to validate that that incoming argument, that incoming data from Undici, the response data, is the shape that we expect it to be. In this case, an object with a single property message that is of type string. So now, let's jump into it live. All right, fantastic. Welcome in. So before we get started, I'm gonna kick off a build script. So MPM run build hyphen hyphen hyphen hyphen hyphen watch. Fantastic. Now let's jump into our server. So we'll take a quick peek at the index file. The index file simp has a single run function. This run function initializes the Fastify app. It registers our create server function, which we'll jump into next. And then it does a simple app.ready and then an app.listing call. If we have any errors, we'll make sure to catch them and log it and then safely exit the process. Inside of our server file, we have our create server plugin. And inside this plugin, we're copying and pasting that same code that you've seen previously. So in here, we're passing in that add user request schema type to our body generic, name generic property. Remember, that's gonna get passed through to the body property in the request object. Additionally, inside of the schema here, we're passing that same schema reference to the body property. So now, not only is this body property going to receive the type that we define here in the named generic parameter, but it's also going to be validated by AJV underneath the hood by Fastify using this options arguments. And now inside of our request handler, we can be certain that this body property here is of this shape and thus the user object is going to have all of the properties we expect, an ID, a name, employed, company, age, and projects. You can see that it even takes in the optional ones and then it even has the definite ones. Then finally, we're going to sort of mimic a database call and we're gonna resolve that object, that message that we're saying, this is gonna be our response for this API route. So it's gonna say user, that user ID added successfully. Then we're going to send that response back to the client. So let's jump over to our client code. So inside the client code, again, you can see a lot of similarities to the presentation. We have a type guard here is add user response. This is the same thing. You know, we're validating that argument with our schema, this response schema. We're defining a user and then we're going to be executing the in the Indici request call with that user. It's passed into the body. We're gonna collect the data from the response. We're gonna parse it and then we're gonna check, hey, if add user response is add user response, let's console.log the message. And if it's not, let's just console.log the entire response. Cause it probably isn't what we expected to be. So let's go run this. So here in another command line prompt, I'm gonna do npm run or start, yeah, npm run start server. You can see here on my computer, which is named Venusaur. We have a server listening on localhost 3000. And then we can run npm run start client. Fantastic. So everything worked correctly. User one, two, three was added successfully. Fantastic. We now know that we got that message here on line 32 because the response that we received from our server matches the schema that we defined here. So let's go check out those schemas. So here in my schemas file, I have a couple of schemas. We have that user schema object and then we have an add user request schema. So this is one of my favorite things about using the type box API is I can reuse my schemas really easily. So I can say, okay, my add user request schema is just going to be an object with a single user that is matching the user schema. And then that type can derive all that information even with that reference. And then here, again, we have that very simple message of type string. So jumping back over to the client, let's see what happens when we break things. Rather than sending just user, a body with the user property, let's send a response body with, sorry, a request body with a new user property. Remember that that new user doesn't match what we are expecting. See this request schema? We're expecting user, but we're gonna be passing new user. So let's see what happens. Perfect. Our API not only responded with a 400 bad request error and that that body should have a required property user, but our API here didn't fail, didn't try to call response data.message instead it logged the entire response data. Perfect. So this is everything in action. All right, welcome back to the presentation. I hope you enjoyed that short live demo. So I wanna do a big shout out to Undra. They are the original authors or graphical artists for all of the images you've seen in this presentation. Definitely, I am not very artistic, but because of them my presentations can be. This whole thing was built with tools called Highlight.js and TMCW Big. Check them out, they're really useful for building presentations using HTML and CSS. And finally, a big thank you again. Reminder, my name is Ethan Erewood. You can find me on Twitter and GitHub. Please reach out if you have any questions or you wanna talk about these topics further. I'm really excited. I got to show you not only Fastify and Undici today, but we got to combine them with tools like Typebox, JSON schema, TypeScript, and AJV. So I hope you can take all what you learned today and go and build type safe full stack web applications. Thank you again. Have a good day.