 Hello, thanks for tuning in. My name is Jose and I'm a software engineer in the Azure SDK team at Microsoft. I'm sure some of you have worked with REST API before, and notice how the editors provide little to no help when discovering which resources are available for you to work with, or even when building up your request payload. In this talk, we're going to see how we can use TypeScript to build a client that will help our REST API users with suggesting which resources they can use, which operations they can execute on those resources that they have selected, and also we will help them build their request payload. Before jumping into the code, let's take a look at the sample REST API call. This is a simple call to our imaginary book catalog service that we will be using throughout the talk. The first part is a method which describes the operation to be executed on a resource. Following is a resource which is composed of a base URL that remains constant for the service and a path which identifies the resource. Then we have the payload, which is the data sent to the service. In this case, we're sending a request to add a book to our catalog. On the right, we have an image with the structure of the service we're going to be writing our client for. It has two resources. The first one supports get, post, and put methods. Our second resource takes a book ID as a path parameter and supports get and delete methods. You can find the code from this talk on this URL. Now, let's get started. We're going to start by creating a simple factory function that takes the base URL. We're going to call it the books client. For now, let's put it that it will return a void. I need to take a base URL, which is going to be string. We have the books client and our users will create a books client by calling this factory method. GonsClient equals booksClient. The booksClient will take a base URL. Let's use localhost. Now, we need our client to have a path function that takes the path that we're going to be using on the REST API. For that, we need to change the return type that we set the void to have the shape that we want. We want to have an object that has a property named path. This property is going to be a function. Let's put void as a return type for as a placeholder. But this function needs to take a string. Let's do a path of type string. Now, our client has a path function and it takes a string. If we don't pass a string, then we get an error. Now, we need that our path function only accepts the paths that are available in the REST API. Right now, it accepts any string. For that, let's start with moving the path function definition type to its own type. Let's do export type path. We're going to have the same thing as we had here. This is a type path. Now, we need to limit the path. It cannot be the whole string. For that, we can create an interface that defines the shape of our REST API, where the keys are the paths that are accepted and then the values are more details about the service. Let's start with the interface. We're going to call it the book library. Now, our keys are going to be our paths. Now, we have one path that is slash book and slash book ID. To want the path type to be one of the keys of the book library interface, we can set the type of path to key of book library. So now, we get an error because the empty string is not a key of the book library interface. And if you start writing, then you will get suggestions about the paths that you can choose. Now, we need to be able to detect if the path has a path parameter than it should be filled in. For that, we can use TypeScript template literal types. So let's start by creating a helper type that will help us detect if the string that we are passing has at least one path parameter. So let's create a type. Type and we're going to call it path parameter. Since we're going to be working on a string, we're going to call it tpath. And it's going to be just a string, so we can say extends string. OK, so now, for detecting if tpath has any parameters, we can start by saying tpath. So this is the check that we're going to be doing. So tpath extends. And now, we need to do our template here. And then, if it matches our template, we're going to say that it's going to be true. Otherwise, it's going to be false. OK, now let's define our template. So we can say that our parameters are surrounded by curly braces. And then, we have a head that is anything that precedes the template. I mean, the parameter and then a tail. That is everything that succeeds the parameter. So in this case, the head is going to be book. The parameter is going to be book ID. And then, the tail is going to be an empty string. If we had something different, like a bar here, for example, then the head would be book. Then the parameter, again, would be book ID. And the tail would be slash bar. So with these, we can start building our parameter. So we can say that, first, we'll have the head. We can use the infer keyword for that script that will look at the string that we passed, and then it will infer the part, the head. So we say head. And then after the head, we're going to have a slash. And we're going to have curly braces. And whatever is inside the curly braces is going to be our parameter. So we can, again, say, infer the parameter. And just after the parameter, we're going to have the tail. So again, let JavaScript infer the tail. OK. So now we have our template. So this will match perfectly. So book ID and bar. So let's create a test type for this. So const full of type head parameter. And then this guy will take book. So let's see if book matches. So it's false. It doesn't match because book doesn't have a path parameter. But what if we add book ID? All right, is it true? Cool. And then if we have something in the tail, true again. OK, so this is looking good. So we're able now to detect if there's at least one parameter. Now we need to use this to make it to have required parameters on our path function. So how do we do that? So we can use tuple types to do that. So for example, instead of returning a true, we can say we're going to return a tuple with a string parameter. We're going to have a string parameter because we know that we found a parameter. So the list of required parameters for the path function is going to be at least this single string. Otherwise, if we didn't find any parameters, then we'll say there are no parameters. So we can return an empty tuple. So let's try to integrate this into the path function. So right now we have the path is type of the keys of the book library. And we say that you can have a path parameter. And this will be of type path parameter. OK, but path parameter needs to be working on a string. And we would like path parameter to be working on the path that we're passing, the exact path that was used as the first parameter. So for that, I think we can just lift this up and put it as a generic of the path or maybe just as a generic of the function. So we can say that we're going to be taking a t-pad and the t-pad is extending, extends. So it has to be one of the keys of book library. And now, I'll reformat it. OK, so we have this generic definition here. So t-pad is going to be any of the keys of book library. So now we can change the path type to just be a t-pad, which is basically the same that we had. And now the path parameters are going to be the helper type that we just created and it's going to be working on t-pad. Cool, nice. So let's see what path looks like. OK, so since book doesn't match the template that we defined, then it's saying that it needs a path parameter of an empty tuple, which means it doesn't have any parameters, but we're still requiring the parameters. So what we can do is we can just do the spread on the path parameters, because we know that this type is going to be in the form of a tuple. But we want the path parameters to be just positional parameters after the path. So we can say that the path parameter, we're going to spread it. It's going to be a rest parameter. So now you see the type of the path function when it has book, then we don't have any parameters. But if we were doing the one that has a parameter, then it's complaining now because we're not passing a path parameter. You see here, we get another very nice name. We can just name it path parameter because tuples allow you to have a label. So you could do path parameter. Yep, so now it will be path parameter. Fortunately, TypeScript doesn't support jet dynamic labels. So we'll need to just keep a predefined label for the tuple. So, okay, so now it's complaining about not having the path parameter. So we can pass any path parameter and you will be happy now. Our path parameter helper type still has a problem. And the problem is that it's only able to identify one path parameter. For example, if you had another path parameter here, let's say bar ID, then path parameter will still just detect the single path parameter. So in order to fix this, we can actually recursively call our path parameter helper type. To identify the rest of the strings. So far, whenever we have already identified a path parameter, we have looked into this part of the template, the head and the parameter, but then we still need to look at the tail. So what we can do is we can say that if you find a parameter, then the parameter is gonna be looking at the tail. So we will do path parameter, path parameter on the tail. And since this again returns a tuple, then we can just spread patterns. And now our helper type is finding both of the parameters. And if you keep adding parameters, it will keep detecting them. Once we set the path, we need to expose methods for the bars that are supported by the rest API. So for example, book should support get, post and put. Well, book on book ID should support get and delete. So let's add some placeholder function definitions on our interface for each of the paths. Now that we have the placeholders for each of the functions, we can start using this interface in order to set the right return type for the path function. So for doing that, we can replace the void placeholder that we had. When we're working with interfaces, we can access the content of each of the keys. So for example, we could do book library on one of the two keys that it has defined. When you set the key, this will be whatever is defined inside. However, we don't want to have it hard coded this way. We would like to be using whatever path has been passed to the function. And since we're using a generic T-pad, then we can work with that. So instead of passing an string, we can just use the T-pad that is being used. So just T-pad here. With this working on this pad, we should get delete and post. And if we set the slash book path, which doesn't require any parameters, we should get a post that put in the get methods. Post get and put methods. Now we need to set real return types. We have set placeholder voids here, but we need to use real types for the operations that we're executing. So for that, I have already created a few definitions for response types. So let's start looking at the general response type that has a status code, which is a required property because any response will have a status code, which is string. Then we have an optional body property. This is unknown on the general response because this is better than the general response. Because this is very specific for each request that we're going to be sending. And then we have the headers, which is basically a key value pair of strings and an unknown type for the value. Then we have an error response. This is any response that is returned by the service will have a status code. We know beforehand that our service made return of 400 for invalid input, 404 when the resource is not found, or 500 when there was an internal service error. When the service returns an error, you will have in the body, we will have an error code and a message about the error. And then we have some types for each of the specific requests that we have. So for example, when we send a post request to the slash book path, then we'll get a 200 if it's successful, and the 201 doesn't have a body. So we're going to say that the body in this case is optional, but it's going to be a never type. So you should never expect to have a body. Then we have the list books 200 response, which is a success response when you call get on the slash books path. So the status is 200, and you will get a body, and the body is going to be an array of books. Now that we have our types defined, then we can start using them in our book library definition. So let's start filling it out. Since we're calling a service, we're going to be getting promises. So let's start with promise. And then slash book get, list books 200 response, or it will get an error response in case there was an error. The post will also have a promise of add book 201 response or an error response. Put promise of update 201 response or the error response. Then this get with book AD will give you a promise, get book 200 response or an error response. And then the delete will give you a promise with delete 201 response or an error response. Now we can see that our delete function returns a delete book to a one response or an error response. Let's assign this to a variable so that we can explore the shapes of the results. So let's see what result has. Okay, so it has a catch in them because the operations return a promise. So we can await. Once we await, we should be able to see what's in there. So it has the general structure, body, headers, and status. Here we see status, we see that it can have 201 or any of the error status codes. If we explore the body, then it will tell us that it can be either an error body or an undefined. That's because at this point we didn't know what kind of response we got. We can be getting a 201 or we can be getting any other error response. So if we go back to responses, you will see that we're defining our status codes for each of the specific responses as string literal unions. And this will help our editor tools to be able to narrow down the types. So for example, here, if we were to say that we wanted to throw an error on a non-success result, we can say if result.status is not equals to 201, which is our success, then we will throw, we will throw the result body, which has the error properties. Through control flow analysis, that script is able to infer that inside this block the result is an error response and also since the rest of the code is not going to be executed, it's able to infer that the result is going to be the success result. So let's use the get method. The get method success is at 200, so let's update this. So outside of here, we should be able to see that the body is a book. So calling result.body has a shape of a book and it has all the properties of the author's color ID published here in the title. The last step is to add the input parameters to our functions. For that, I have already defined a few types for the input parameters and also for the book. Our book has an ID, a title, a list of authors, an optional published year and a set of colors that you can choose from. Now let's look at our request options. We have a general type that has a body, headers and query parameters. All of them are optional in the base. However, we're defining the specific shapes for each of the requests. For example, our get requests have an optional never body because they will never contain a body. However, our post input operations have a required body of shape book. And also the update operation has an optional attack that is passed into the headers. The same thing for the delete. So now let's fill them in. The get has an optional input of type list book options. Post has a required one of type create book options. Input also has a required input update book options. Here this will have also an optional one input optional of type get book options and finally the delete also has an optional input. With this we'll be able to help our users to build their request payload. Let's set the base book path and select the post method. We will be able to start building our request. If we create an empty object and hit control space we get the suggestions for the properties that we can pass. We see that the body is a required one. We select the body and we start with another empty object. We see that we get the required properties from the book. Authors, color, ID, title. And we can start building up our request payload. Thanks for joining me today. I hope this was helpful. Here's a link to the code and my handle on github. Stay healthy and see you later.