TypeScript in Express – TypeScript Tutorial

#TypeScript #Express.js #API Development #Type Safety #Middleware #Node.js #Programming
💬 Chat with this Video
Ask anything about this video…
Typescript makes Express servers safer, more scalable, and easier to maintain. In this hands-on tutorial, you'll go from setting up a TypeScript powered Express environment to structuring routes, controllers, and middleware with full type safety. By the end of the course, you'll have the skills to build production ready Express APIs with confidence. >> Hello. Hello and welcome to this course on Express with TypeScript. We're going to take Express, one of the more popular back-end frameworks for Node.js, and combine it with TypeScript. So, we get the speed of building APIs with the safety of strong typing. When I first learned Express, I loved how fast I could build something. But I also spent a lot of time guessing at types and fixing silly bugs. So, today I want to show you how to get the best of both worlds. Expresses speed plus Typescript's reliability. Over this course, you'll build a simple readonly but fully typed pet shelter API. This API can return a list of pets, a filtered list of pets, or a single pet based on ID. Along the way, you'll learn how to set up the environment, type your apps with express specific concepts, work with custom types to model real data, add more specificity to requests and responses, filter results with query parameters of different types, and even add your own middleware fully typed with TypeScript. Plus, as always, lots of challenges along the way to keep you on your toes. By the end of this course, you'll not only have a working API, but a strong mental model of how Typescript and Express work together. Now, one quick note. This course does assume you already know the basics of Express and TypeScript. If you are new to either of those, I recommend checking out the Scriber courses first. I've linked them both right here on the slide, so you can hop into them, go through it, and then come back when you're ready to dive in. I'm Rachel, and I'll be your teacher for this course on Express and TypeScript. I hope you're as keen as I am to learn something new. Let's dive in. Let's start off by installing Express and TypeScript as well as its developer dependencies. This starts off just like any other Node project with mpm init-y. And now we have a very familiar file package.json empty and ready to go. Let's install some dependencies. We're working with Express, so of course that will be our first one. So, let's install Express. Once Express is done installing, we'll see the node modules folder pop up, and we can also see Express listed as a dependency in package.json. This is all we would need to get ready for express. But now, it's time to add our TypeScript. Now, TypeScript is a dev dependency, meaning it's only needed while we're developing, not after the project is built and deployed. So, we'll make sure to install it with the D flag type script. Another dev dependency we'll need is a library of express types. Express itself is written in plain JavaScript. So, TypeScript doesn't automatically know about Express specific things like request or response. So, we'll also install as a dev dependency types express. Now, this package adds the type definitions for express so TypeScript can understand our code and give us proper intellisense. Now that we've got those two TypeScript related dev dependencies installed, we'll have to configure TypeScript itself. And we do this by creating a tsconfig.json file. Over here in the file tree, let's create a new file and call it tsconfig.json. Now, instead of writing a giant config file ourselves, we can extend a ready-made one. There is an mpm package called tsconfig that comes with sensible defaults. So, we're going to install it as a dev dependency. Once again, ts config and I'm after the one for node 20. Now I'm using node 20 because the scriber editor uses version 20 at the moment and we can check that by typing in node-v. In your dev roll, you'll want to install the tsconfig that matches your node version. Then in our tsconfig.json file, we can use this new package like this. Extends and we'll specify the name. So tsconfig node 20 and the file name is tsconfig.json. JSON. Now with our sensible defaults in, we'll also supply a few more instructions of our own. And these will go inside a compiler options object. The first option is root dur or root directory which I'm going to set to source. This line tells Typescript that all of our TypeScript source files and code will live inside of this source folder. And next is outdoor or out directory which I will set to dist. This is where Typescript will put the compiled JavaScript files. So the idea is this. We write TypeScript files inside of the source folder and when they're compiled, they'll go into the disc folder as JavaScript ready for Node to run. And that's it. Our environment is ready to roll. But I hope you've been watching and listening because you're going to be setting up your own Express and TypeScript environment in the next Scrim. I'll see you there. Welcome back and let's get right to it. In this scrimm, you're going to set up your own Express and TypeScript environment. Here in challenge.md, I've written down the steps for your challenge, which is to set up an Express and TypeScript environment. You'll first need to initialize the node project. Then you'll have to install express as a dependency. Then install TypeScript and Types Express as dev dependencies. And finally, you'll have to create and set up your tsconfig.json. If you want a refresher, you can always pop back to the previous scrim and watch the setup again. Otherwise, dive in and I'll see you on the other side. Welcome back. Let's step through the process together. First, we'll initialize the node project with npm init-y. Then, we'll install express as a regular dependency with npm install express. Next up are our dev dependencies which are typescript and types express. After that, we'll have to create our tsconfig.json which I'm going to do right here in the terminal using touch tsconfig.json. And just so we don't have to write our own config file, I'm going to also install the dev dependency tsconfig node 20. And now that all my terminal typing is done, I'll open up tsconfig.json and get typing. First, I'm going to extend the pre-made, which is tsconfig node 20 tsconfig.json. And then I'm going to set our compiler options with compiler options object. And the first one is our root directory, which I'll set to source. And the second one is our out directory, which I'm going to set to dist. And there we go. A solid base is super important for any project. After all, we can't write any TypeScript in any Express project if we haven't set it up. In the next script, we're going to start writing some actual Express content and lacing some TypeScript throughout. I'll see you there and create it. We'll come over here to the files and create a new folder. And we're going to call it source. Inside of the source folder, we'll finally create our first express file. Let's create a new file and we're going to call it index.ts. Notice that it's TS, not JS, because we're working with TypeScript. Now, there are some pretty standard Express stuff coming up. Let's see if you remember how to get a very simple Express app going. Here is your challenge. Can you build a basic Express app using vanilla JavaScript? You'll have to import Express and create an app instance. You have to listen on port 8000. And when the server starts, you can console log listening on port 8000. You won't have to create any endpoints or routes just yet. Give it a go and I'll catch you on the other side. Welcome back. How much Express could you remember off the top of your head? This is what I would have done to create a super basic express app skeleton. So, first I'm going to import express from Express, of course. Then I'm going to create the app instance and set it to an instance of express. Then using that app instance, I'm going to listen to port 8000. And when it's running, I'm going to console log listening on port 8000. Now, of course, you might want to separate this port 8000 into its own variable so you can better control it. So, you might have done something like this. const port equals 8,000. Instead of hard coding 8,000 here, we would use port. And same thing down here. Instead of hard coding this, you would specify the port here instead. But it's totally up to you. And there is our first express server code ready to go. Now let's sprinkle in some TypeScript. What we're about to do isn't strictly required, but this is a TypeScript course and the more you practice typing things, the more natural it will become. Up here after importing Express, I'm going to import a type called express from Express. Now, where do you think we could use this express type? And while we're on the topic of types, is there anywhere else in this file we could add a return type of void? Here is a mini challenge. Add some types. Use the express type where it makes sense and add a void return type where it might make sense as well. Have a hunt around and I'll see you on the other side. Welcome back. Let's type for the express type that we just imported. We can use that to type the app variable that holds our instance of express. And as for the second task, this callback function in the app.listen technically returns nothing. So we can type the return value with colon void. Now like I said before, these types aren't strictly necessary. In fact, they're implicitly typed by TypeScript already. But the more we practice, the more natural it becomes to think in Typescript. That's the habit we're building here. In the next scrimm, we'll actually run our Express and TypeScript project and see it in action. We've got our Express app written in TypeScript. But here's the problem. Express doesn't know what to do with TypeScript files. Let's see what happens if we try to run it directly with node source index.typescript. And sure enough, we get an error. A known file extension TypeScript, which makes sense. Node doesn't understand TypeScript out of the box. That means we need to compile our TypeScript into JavaScript before we can run it. And luckily for us, TypeScript comes with a built-in compiler. To use it, we just use npxtsc. Now, this will run the compiler. It looks at tsconfig.json, does some compiling, and spits out JavaScript files into this dist folder that just appeared. Remember back in our tsconfig, we set the out directory to dist. Now if we try to open the dist folder, we won't see anything. And that's because the scriber editor hides the files in there just like it does with node modules. But we can check out the files in the terminal. Down here in the terminal, we'll type ls dist. And sure enough, there is an index.js. Let's peek inside with cat dist index.js. Now, it does look a little different from what we wrote, but don't worry. That's just what compiled JavaScript looks like. It will work just the same. And now that we have that JS file, we can run it like normal with node dist index.js. And there it is down in the terminal. The message that our server is listening on port 8000. Now, if we send a request right now in the network tab up here to HTTP localhost port 8000, we'll just get a cannot get HTML page with a 404 error. And that's because we haven't added any endpoints yet. So, let's fix that with a quick challenge over here in index.ts. Here is your challenge. Send back a response. First, add a get route for the root path that responds with an empty JSON object. Then compile the app with npx tsc. And then finally run the compiled JavaScript with node dist index.js. Dig into that express knowledge and give it a go. Welcome back. Here's how to go about it. We'll tap into the app instance and create a get route. And this will be the root path. And then for the handler, we have the wreck, the res, and then inside of it, we will respond with JSON, an empty object. Now that we've written it, we can compile the TypeScript to JavaScript with npx tsc. And after that, we'll run the compile JavaScript with node dist index.js. And now over in the network tab, if we send this get request to the same endpoint, we'll get exactly what we expect, which is an empty object. All right, we've got our Express skeleton up and running, everything connected properly, and with the bonus of TypeScript. In the next scrimm, we'll spin up some simple data for our app, type annotate it, and return it from our API. An empty object is no fun. Let's change up our app so it actually returns some data to the client. Time to get your fingers busy again with a challenge. And this time, we're going to start mixing in some TypeScript. Your challenge is to respond with some data. Create an object called pet before the root route that includes the following properties. A name, species, adopted, and age. Then type your new pet object. You can do it in line or using a custom type. After that, update your existing get root route to return that pet object. You will need to dig up some basic JavaScript and TypeScript knowledge to work through this challenge. Take your time and don't be afraid to look stuff up if you need to. Welcome back. Let's get this data going. First, I'll create a pet variable, which will be an object, and it will have a name, Rubik, a species, which is cat, adopted, which is true, and age, which is a number. Now, we have to type this pet object. There are two ways to do this. We can either do it in line or using a custom type, but I'm going to show you in line first. We'll drop a colon after pet and then open a new object. So, we've got a name, which is a string. We've got species, which is also a string. Got adopted, which is a boolean. And age, which is a number. This works. Or we can take advantage of TypeScript's custom types. Before the pet variable, let's create a type called pet. And then we'll take this entire object and pop it here instead. And in its place after this colon, we'll simply refer to the pet type. Now that we've got that, let's respond to the API call with our new typed pet object. Instead of this empty object, we're going to get rid of it and in its place, we'll use pet. Okay, now that we've done that, let's test it. First, we'll compile the TypeScript into executable JavaScript with npxtsc. And then we'll run the compiled JavaScript with node dist index.js. And now if we send the request again, we get the full pet object back. Now, you're probably wondering, that's a lot of stopping, compiling, and restarting every time we make a change. In the next Grim, we'll learn about a way that handles that process a little better for us. Welcome back. After that last scrimm, you're probably wondering, do I really have to type npxtsc and node dist index.js every time I make a change? Well, no. Us programmers are lazy, and there are a few ways to make life easier. And one of those ways is to take advantage of npm scripts over here in package.json. Now, we won't need this default test script for this course because we're not doing any testing. So, I'm going to remove it and instead I'm going to add a build script that compiles TypeScript to JavaScript, which as we know by now is npxtsc. Now, anytime I want to compile TypeScript to JavaScript, all I have to type is mpm run build. But I've probably just given myself more typing. nvx TSC is only seven characters compared to npm run build. Now in reality, most of the time we'd want to compile and run the app in one go. Rather than typing two commands, we can chain them into a single script. And that is your challenge for this scrim. And since JSON doesn't take comments, I'm going to add your challenge text like this. Your challenge is to create a start script that runs the TypeScript compiler and then runs the compiled app in dist index.js. Give it a go. Welcome back. Let's add a start script. We're going to start with the name of the script, which is start. Now, the first thing we want our script to do is compile with mpxtsc. And then we have our double amperand, which will wait for the first command to finish executing. and then start the next. And the next command is, of course, node dist index.js. Now, let's get rid of this challenge text before node thinks that it actually means anything. Now, before we try running the script, let's make a small change so that we can see it working. Back in index.ts, it's time to stretch that express muscle once again. Your challenge is to respond with more data. First rename pet to pets and then change pets to be an array with two or more pet objects and then you'll have to adjust the type of pets which is currently a single pet. And then finally change out the res.json to send back our new pets variable. Have a go and I'll see you on the other side. All right, let's go for it. First, we're going to change pet to pets, so the variable accurately describes the content that it's about to have. And then we're going to wrap the existing pet object in square brackets, which will turn this into an array of objects. And I'm just going to clean up a little bit here. And now I'll just add one more pet with a name of pickle, a species of dog, adopted boolean of false, and age of five. And of course, TypeScript is now very unhappy because this is not a single pet type anymore. To remedy that, we'll just add some square brackets after our pet type here to denote that it's now an array of pets. And finally, down here, we'll change the res.json to pets. Nice. Now, we can test our new script with npm run start. You'll see the chained commands run. It will compile first and then it will use node to run the compiled JavaScript file. we get the listening on port 8000. And if we send this request in the network tab, we now get our array of pets. Much better. This already saves a bunch of keystrokes and it keeps our routine consistent with other node projects. Okay, we've warmed up with some sample pets. In the next scrimm, we'll load in the real data set for this project. And you better get those fingers ready because there's quite a bit of typing ahead. Before we dive deeper into Express and TypeScript, let's clean up our project a little bit. Right now, index.ts is doing everything. It's setting up the app. It's defining the type. It's holding the data. And it's handling the routes, even though we only have one right now. But that's going to get messy really quickly. So, let's move things around a little bit. Specifically, the data. This will feel pretty familiar if you took the express course here on Scriber. We're going to put our data in its own directory and file. Inside of the root of the project, which is source, we're going to create a new folder and we're going to call it data. And inside of that data folder, we're going to create a new file called pets.ts. Back in index.ts, we're going to cut everything from the pet type all the way down to the end of the pets array. And we'll pop it over here in pets.ts. Now, we still have to access this pets array back in index.ts. ts. So let's make sure we export it from here. Then back in index.ts, we can import it like this. import pets from dada pets. Now let's recompile and restart the server just to check with mpm run start. Everything looks good here. And let's try and send a request. And all good here as well. Now let's hop back into pets.ts. This is where I'm going to paste the real data that we'll use for this project. We now have an array of pet objects with a ton more properties. Some new properties are ID, breed, intake date, medical record, which in itself is an object, and photo. As you can see, our original pet type doesn't fit anymore. And you can see a bunch of TypeScript errors already. So, here is your challenge to fix the pet type. Look at the pet objects and note their properties. Then update the pet type so it matches and decide which fields should be optional. And to do this, you'll want to compare pet number one and pet number two and see what fields they have. You'll notice that the TypeScript errors will follow you as you go and use that as a guide. Take your time with this and make sure that your amended pet type properly fits the content that we have here. Welcome back. Let's go through these one at a time using pet ID 1 as a reference. So we've got first ID. Now ID is a number. Then we have the name and species followed by a breed. Now breed is also a string. Now next up in our data is the age. So I'm just going to take this opportunity to move age up here. And then we have the adopted boolean. And after that we have the intake date which is a date. Scrolling down a little bit, we have the medical record next which is in itself an object. So we'll do that here. Inside of the object we have vaccinations which is an array of strings. Then we have weight in kilos which is a number and microchip ID which if we have a look at ID 1 it is null. So we'll put that in here for now. After the medical record we have the photo which is a string. So photo string and just so we can compare I will get rid of the challenge text now. Okay, this works for the pet with an ID of one but it does not for the pet with an ID of two and there are two reasons for that. Milo here has an adoption date and its microchip ID is actually a string. So let's add both of these to our pet type. After intake date, we're going to add adoption date which is a date. But as you can see instantly, our pet with an ID of one is very unhappy because it doesn't have an adoption date property. So in our custom pet type, we're going to make sure that adoption date is optional by adding a question mark. And then we'll come down to the microchip ID and after null, we'll add a union. And we'll also say that it could be a string. And now our pet accurately matches the data that we're working with. Let's just make sure we haven't broken anything by running mpm run start, heading over to the network tab, and sending our usual get request. And now we're getting our real pet shelter data ready to go. In the next scrimm, we'll zoom in on something new. Typing our express request and response objects. I'll see you there. Now that we've got a simple app running and we've made it easier to recompile and run it, let's finally start typing some Express specific things. You might remember in an earlier scrim when we typed the Express app itself with Express. That was just the beginning. You've probably noticed by now that TypeScript is flagging some errors on w and res for our route handler down here. And if you hover over them, you'll notice that TypeScript has given them the type of any. And that's because right now TypeScript doesn't know what these objects are. It just sees them as untyped parameters. And we know Typescript isn't a fan of any because it basically throws away the whole point of having a type system. So let's fix it. Up here in the top of the file where we've already imported the express type, let's also import the request and response types. Request and response. And now it's your turn. Your challenge is to use the newly imported request and response types to type the rack and res parameters. Now, we haven't explicitly talked about this, but you can figure it out by looking at how we typed the express app earlier. It's the same idea. Just apply the types directly to the parameters w and res. Go for it. Welcome back. The solution here is pretty straightforward. We've already imported the types that we need. So now it's just a matter of using them. For the first parameter w, we'll add the type request. And for the second parameter of res, we'll add the type of response. And that's it. It's nothing too fancy. But now TypeScript understands exactly what these objects are. And that means if we try to use a property that doesn't exist on w or res, TypeScript will catch it right away. If you've worked with TypeScript before, you might know that we can get even more specific with these request and response types. And that's because they are generic types. Not every request has the same parameters or body, and not every response sends back the same kind of data. We'll get there soon in a few scrims time. But since we're here, let's also add a return type. Even though this function sends a JSON response, the callback itself isn't returning anything. So we'll explicitly type it with colon void. Now let's just make sure nothing is broken. Let's save and run with npm run start. And we'll send a get request. And all is good. And that's our wreck and res typed. It might feel like a small step, but it's a big win. TypeScript now understands exactly what these objects are. And we've set the stage for more explicit typing later on. In the next Grim, we'll make our API a little more realistic by adding cores. And of course, we'll look at how to bring TypeScript into the mix there as well. Welcome back. Let's make our app a little more real world by adding corores, which stands for cross origin resource sharing. You might know this from the Express course. It's a package that helps our server handle requests coming from another domain. Using cause is almost identical to how we would do it in vanilla express, but with TypeScript, there's just one extra step. First, we're going to install calls. So, npm install calls. And then we'll import it into our index.ts. import calls from course. And just like you've probably seen in a lot of vanilla express projects, we'll use it with app use course. But if we try to run the server now, we'll actually get a TypeScript error. So, npm run start could not find a declaration file for module course. And that's because Coors is a plain JavaScript package and TypeScript doesn't know what types it should have. So what can we do? And I'm going to stop here and let you have a think as a developer. If you ran into this error in the wild, what steps would you take? Maybe you do a quick Google search. Or maybe you'd ask your favorite AI assistant. Or you might just read the entire error message a bit more closely. Either way, here is your challenge. figure out why corors is causing an error and fix it. Okay, let's fix it. The solution here is actually to install a types package for cause just like we did with express at the beginning of this course. If you take a closer look at the error message, it actually gives us a hint. We want to install types cause. So, let's install it as a dev dependency. types course. And now let's try running the app again with mpm run start and no errors. And if we send a quick get request, nothing has changed and all is well. This is a great example of how Typescript sometimes adds an extra step to otherwise simple express code. Many JavaScript packages don't ship with types by default. So we pull in communitymaintained ones. The ones that we've seen so far in the course, Types Express and Typesc are both from the definitely typed repository. I've linked up this image in the slide so you can check out their GitHub if you fancy a peak. Now, it might feel like an extra step at first, but it's part of what makes Typescript so powerful. It encourages consistency and helps catch problems early. The good news is that Typescript usually points you in the right direction in its error messages. And if it doesn't, the internet is your friend. In the next scrimm, we'll set up a 404 catch all so our API can handle unknown routes gracefully. And of course, we'll type that as well. Another thing every real world API needs is a 404 catchall route. This route runs whenever the client's request doesn't match any of our endpoints and should send back a 404 error. Let's throw you right into a challenge to create a 404 catch all after the root route. Don't forget to type annotate everything. And there are three places to do this. If you need a little bit of help of what a 404 catch all route usually looks like, check out hint.md. Give it a go and I'll see you on the other side. Welcome back. This is what the solution looks like. We're using app do use for the catch. Inside of it, we'll create our handler first. The first callback parameter is w, which we're going to type as request. And the second is res, which we'll type as response. We're also going to add a return type of void to the function itself since it doesn't return anything directly. It just sends a response. And as for the response, we'll start with res. And we're going to set the status to 404 because we couldn't find a matching route. And we'll also send back some JSON, which is a message that says no route found. And now we can test it. Let's run it with mpm run start. No errors here. So over here in the network tab, let's try to hit a completely non-existent route. Let's do 1 2 3. And yep, we get our 404 status with a message of no route found. Exactly as expected. With our 404 catch in place, our API now has a proper safety net. every request is handled either by one of our routes or by the 404 catch all. Now, we're about to hit the halfway mark of the course, and you're probably being annoyed by something already. We've been stopping, recompiling, and restarting the server every time we make a change. Sure, it's easier now with mpm run start, but I'm sure it's getting repetitive. Inside Scriber, though, we've got a bit of magic waiting for us. In the next Scrim, we'll switch that on so the server restarts itself automatically whenever we save. Hello again. I'm sure you're getting a little tired of restarting the server manually every time we make a change, even if we've written a shortened script for it. So, let's make our life even easier with this short scrim here in Scribba. Down here next to the terminal and console, we have something called the runner. I've set it up by clicking on this gear icon on the far right. You can click on it too to see how I've set it up. But since that dialogue box that pops up doesn't get recorded in Scriber, I have added a slide to show you what it looks like. Here's how I've configured it. Every time the scrim loads, it runs mpm install and then it runs mpm run start. And every time we save a file, it will run mpm run start again automatically, which is exactly what we want. Now, I have waited until now to switch this on because I really wanted you to understand what's happening under the hood. the process of compiling TypeScript and running the resulting JavaScript code. If we change our 404 catch all error message to something else, let's say no end point found and save, you'll see that runner is already doing its job and we can go straight to the network tab and send a request and we'll see the updated no endpoint found. This is something special to the Scriber editor, so you don't need to set it up yourself. From this scrim onwards, runner will be enabled. You'll need to use this runner tab to check out things that you'd normally see in the terminal, like stuff that you've console logged. Otherwise, everything else works exactly the same way. You just don't have to restart the server manually anymore. All right, that's it for this quick one. Let's jump back into building our app. Welcome to the second half of the course. We're going to get super expressy now. I mentioned about four scrims ago that we can get a bit more specific with the response type. And now it's time to dig in. The response type is actually a generic. And here's the breakdown of what it actually looks like. It looks like a lot, but the main thing we care about is this first generic parameter, res body equals any. This is the one that lets us specify the type of the response body. By setting it, we can tell Typescript exactly what shape our JSON should be in and get super specific with our responses. So, I'm going to show you with this 404 catch all down here. After response, I'm going to open some angle brackets. And inside, I'm going to specify the type of the response body. In this case, it's just a JSON object with a message string. So, inside the angle brackets, we'll open the JSON object message. And the type of message is string. and that will tell Typescript exactly what we're supposed to be sending back with the JSON response. Now you try the same with this root path endpoint. Make the response type more specific for this root route. Give it a go and I'll catch you in a bit. Welcome back. If we think about what our root route is returning, it's all the pets from our data, which is an array of pets. And that means our response type should be an array of the custom pet type that currently lives in this data pets.ts file. So first we'll need to make sure that our pet is exported from this file so we can use it back in index. So export type pet. And now back in index we will import it at the top of the file. We'll do it after the pets. Import type pet from data pets. And now we can use it down here in the response. So I will open some angle brackets and the type that we're sending back is an array of the pet type. Easy peasy. Now runner didn't throw any errors. So let's try to send a request to that root route. And all is well. So now this endpoint's response is typed. Its request isn't very interesting. As you can see here, there's nothing really coming in. So the generic request type will do the job. But in the next scrimm, we'll spin up a new route with a slightly more interesting request, and we'll see how to type that properly. Right now, we only have one endpoint that returns the full list of pets, which is one of the most common API endpoints. Another super common one is to return one record by its ID. And that's what we're going to build next with TypeScript woven throughout it. I'm going to start us off by scaffolding the route handler after the root path route. And here it is. You might remember from express that this colon means that anything after it like this ID is a dynamic parameter. We're expecting a request that looks like this localhost 8,000 and then an ID like one where one becomes the value of this ID that we specified in the route. And your challenge is to complete the ID route. You'll have to type wreck res and the callbacks return value. We'll have to pull ID from the path parameters. Find the pet that matches that ID and send back said pet with res JSON. Don't worry about any non-existent IDs or other TypeScript that I haven't already mentioned. Now, after step one, which is mostly TypeScript, this challenge will draw from your knowledge of Express and JavaScript. Take your time and I'll catch you on the other side. Welcome back. How did you go with your challenge? This is how we can go about it. First, let's type wreck and res. So, request for w and response for res. And we will also type the return value of the callback, which is void. Inside of the function, I'm going to pull the ID parameter out of the request by dstructuring it. So, const id equals wc params. Then I use some vanilla JavaScript to find the pet with a matching ID. For this I use the find method. So const pet which will ideally hold a pet object. We're going to look through the pets array and we're going to find something. For each object in the pets array I'm going to call that a pet. And for each pet we're going to compare its ID which will force to a string and try to see if it's the same as the ID coming from the parameters. This will return either the matching pet object or undefined, but we'll handle undefined later. And finally, we'll send that found pet object back with the response using res JSON pet. Let's quickly test this by sending a request for the pet with an ID of one. And this should send back Bella the dog and nothing else. Okay, we've got that. Now, let's get typing. Right now, TypeScript is still treating this ID from wreck params a little loosely. We can get a bit more explicit with the request. Just like response, request is a generic type. And here's the shape you'll see in the docs. It looks pretty busy, busier than response. But for this route, we actually only care about the first parameter, P equals core params dictionary. We can tell Typescript that our params object has a single key ID, which is a string. After request, we'll open up the angle brackets. We'll specify our params object and it has an ID and the ID is a string. Now this ID is safely typed as a string. Easy win. Let's send an API request just to make sure that nothing is broken. Let's get the pet with an ID of three this time. And we get Thumper the rabbit who is not adopted. So we've created a new ID route and made our request type more specific by describing its path params. In the next scrimm, we're going to finish this route properly. We're going to handle bad IDs and tighten up a couple more TypeScript details. Welcome back. Last time we added an ID route and worked with the request generic. There are a couple more things to tighten up here. We're using find to look up a pet by ID, which means we should type everything in the callback, type the pet variable correctly, and handle the case where no pet is found. So here is a challenge for you which combines your knowledge of vanilla JavaScript express and TypeScript. Type and complete the entire ID route. You'll have to type everything in the find callback function. Type the pet variable. Handle what happens if we don't find a pet with the provided ID and finally type the response generic. This is certainly a chunkier challenge, but give it a good go and remember that you can take your time. I'll see you in a bit. Welcome back. This is what I did. First, let's type everything in the find callback. This pet here is a pet object. So, we'll surround it with brackets and we'll type it with our custom pet type. Now, the return value of this is a boolean. So, colon boolean. Then, we'll hop back here and we're going to type the pet variable. I mentioned in the last scrim that this find function will either return the pet object or undefined. So here we'll add the type annotation of pet and then a union and then undefined to cover both cases. Next up we'll handle the two cases of pet when it's holding a pet object and when it's undefined. So if pet is defined it's holding a pet object. So we can move res.json up here. Otherwise we didn't find a pet to respond with. So res status 404 because we didn't find anything and some JSON which is a message that says no pet with that ID. And finally now we know what we're sending back. We can handle the response generic. After response we'll open some angle brackets. We know that we're either going to send back a pet or we're going to send back a message that is a string. Okie dokie. Let's test it out in our network tab. So three will still send us Thumper the rabbit. But let's try some gibberish this time. And this time we get no pet with that ID. This gives our ID route some real world behavior. Specific params, a correctly typed lookup, and a clear 404 path. Next up, we'll stick with the request side of things and add filtering using query parameters. Hello again. We're going to hop back to our first route here, the one that grabs and responds with the full list of pets, which you can see over here. But what if a user only wants to see cats or only dogs? That's a very common use case in a real world API. Now, APIs often let users filter data using query parameters. And here's what it would look like. We've got the same API endpoint that we've been using, localhost 8000, and then we're going to add a question mark to start the query. After that comes a parameter name like species followed by an equal sign and then the value that we're filtering by. For example, cat. So let's set up our route to handle query parameters, filter the results and send back the filtered list. And that's going to be your challenge. Filter the pets by incoming species and respond with the filtered list. You have to grab the species query parameter, create a variable to house filtered pets, filter the pets by said parameter, and send the filtered data back via res.json. If you've taken the express course, you'll know exactly what to do here. If not, you can check out the express course or do a quick Google search. Go for it, but take your time and I'll catch you on the other side. Welcome back. [clears throat] This is how I would go about it based on Scribbler's Express course. First, we're going to pull the species query and value from recre query. And we're going to do this by dstructuring species equals wreck query. We'll use this in a second. Then we're going to create a new variable called filtered pets which has the type of pet array. This will hold the initial value of all the pets from our data. Now users don't actually have to provide the species parameter at all. So we should check whether or not species exists with if species. Now if species does exist, we will filter this new array of filtered pets using the handy filter method. Let's write up the syntax first. So filtered pets equals filtered pets filter and then the function inside of it. Here we're filtering through each pet inside of the filtered pets array which is of the pet type. And this callback function will return a boolean. And as for the filtering logic itself, we want to check if the pets species to lowerase matches the species from the query also to lowerase. And finally, instead of responding with all the pets, we're going to swap this out for our filtered pets array instead. Now, this is pretty straightforward except for this error that's popped up in the runner. What we've written here would normally work with vanilla express. But since we're using TypeScript, we get this error property to lowercase does not exist on type. And that's referring to the species value that we grabbed from the query. Now, to us, this is clearly a string, but to Typescript, it could be a bunch of different types as we can see here in the error. So once again, it's time to get more specific with our request type. And that's exactly what we'll tackle in the next scrim. In the last scrim, we set up vanilla express code to filter our pets by a query parameter like species equals cat. But we were left with this TypeScript error. Property to lowercase does not exist on type. Let's fix that by getting a little bit more specific with the request generic up here. Here's what the request generic looks like again, but this time we only have to look at the first four. P for params, which we already played with, res body, w body, and wreck query. Let's go through them one by one back in the editor. Let's open up those angle brackets and start to fill out the parameters for the request generic. The first one is params, and we've experienced that one already. Params are for path variables like the ID we worked with earlier. Our root path route doesn't have any, so we'll just use an empty object. The second slot is res body, which stands for response body, which might feel a little strange here. Why would a request need to know about the response? It's mainly for advanced middleware for us. We'll skip over this parameter by providing the unknown keyword. It basically says we're not using the slot, but also don't assume anything. Another one we can skip for now is the next one, w body or request body. This is for post, put, or patch requests when the client sends data to the server. Since we're only working with get routes right now, we'll skip it as well. And we can do that by providing an empty object. And that leaves us with the last slot, w query. Now, this is the one we care about. It lets us type the query string parameters like species equals cat. Now, this one is an object as well, but this time we'll have some content. We'll accept an optional species key typed as a string. And when we save and run a does its job, we can see that there are no errors. And if we come to the network tab and we send our request with species equals cat, we can see this in action with only cats being returned. There's Milo who's a cat, Luna who's a cat, Willow who's a cat, and so on. Let's try it out with species equals dog instead. and we only have dogs. Now, our request and response generics might start getting a bit chunky and falling off the page. So, we can separate them into their own lines like this. And this will just make it a little bit more readable. And we'll do the same with our ID route down here. Wreck on its own line, res on its own line, and closing it out. So, now we've got our first query parameter typed properly, and it's working as expected. In the next Grim, we'll add a boolean query parameter into the mix and learn how to type that. We've set up our first query parameter, species, as a string. Now, let's try a different type, a boolean. We're going to add this adopted as a query option so that users can filter for pets that are already adopted or still looking for a home. I'm going to start by cleaning up our request type here. It's getting a little bit long. So, I'm going to pull the query parameter here into its own type. This is only going to get longer and longer the more queries we add. So, up here, I'm going to create a new type called pet query params and pop everything from the query params section here. So, species is a string and it is optional. And instead of it being here, I'm just going to use pet query params which we just created up here. And now we can add more queries without making the line unreadable. And speaking of adding more queries, here comes a challenge. Allow users to filter by the adopted property. You'll first have to update pet query params and then grab the adopted param from wc query just like we did here and filter the filter pets array based on its value. Now do keep in mind that query strings come in strings but adopted is a boolean. So what can we do? Give it a go and use our existing species filter as a guide. You'll find that it's very similar with just a few tiny changes. Welcome back. Here's what we can do. First, I'm going to update pet query params to include the optional adopted property. Although this will be compared to a boolean later, we have to remember that query strings come as strings. Now, this would work, but you can take it an extra step here. Yes, the adopted query string will be a string, but it should only ever be one of two strings, true or false. So, we can go that extra step here and use both literal types and a union type and say true or false. Next, we're going to grab adopted from w.query down here. And I'm just going to tack it on after species adopted. And then we'll need to write a new if block similar to what we've already done for species. In fact, I'm going to take a copy of the species block and just change things out. First, we want to check if adopted exists. The filter method skeleton doesn't change, but we will change the filtering logic. We want to compare pet adopted from our original data against the adopted from our request query. Now, pet adopted is a boolean and we want the query string of true or false to also be boolean so they can be compared. To do this, we just have to pause it using JSON par. And that's it. We should be all good now. I've just saved it. So, let's check it out in the network tab. Now, adopted equals false should return all the pets that haven't been adopted yet. We can see Bella adopted false, Thumper adopted false, Luna adopted false, and let's try the opposite. Adopted equals true. And we have Oscar who has been adopted, Rocky who has been adopted, etc. And because of the way we've written our filtering, we can actually tack on multiple queries. If I want to filter just the cats that need adopting, I can go adopted equals false and species equals cat. And we get returned our options of Luna or Willow, both who are cats and not adopted yet. Great work. Next, we're going to add one more query parameter with a different type, just so you can see how this pattern scales. I'll see you there. We've played with string query parameters like species and boolean ones like adopted. Now let's play with numbers. We're going to let users filter pets by age. But in a real world setting, it's not very useful to ask for an exact age. Instead, people usually look for a pet that's under a certain age or over a certain age. So, we're going to add two query parameters, min. Now, max age, like max age equals 10, should respond with pets that are 10 or younger. And minage, like minage equals 2, should respond with pets that are 2 or older. And if we use both, like [snorts] max age equals 10 and minage equals 2, we should get pets that are between 2 and 10 inclusive. Now, this challenge is super similar to the last one. So, I'm going to drop the entire challenge right here with no steps. It's all up to you now. So your challenge is to allow users to query by min age and max age. And I've left you a few to test with. Have a look at how we added the adopted filter in the last scrim. What you have to do here is again very similar. Take your time and I'll see you on the other side. Welcome back. How did you go? Let's walk through it together. First, we're going to come up here and update pet query params. We're going to add min age, which is a string, and max age, which is also a string. And do note that these are both optional. And then we'll come down here and we'll dstructure minage and max age right alongside species and adopted. Min age and max age. Then we'll come down even further and we're going to add the two filters. We're going to take a copy of the adopted block and just make some changes. Let's swap out adopted for minage. And moving down, we're going to swap pet adopted here to pet age because we're looking at the age. And instead of parsing adopted, we're going to pass min age instead from a string to a number. And rather than looking for a direct equal comparison, we're going to check if pet's age is greater than or equal to the paused minimum age. And as for max age, we can take a copy of the min age block that we just wrote. We'll change minage to max age. And also here as well. and we'll reverse the comparison. Instead of greater than or equal to, it is now less than or equal to. Let's save and we'll let runner do its job. And then let's check it out here over in the network. We're going to use the two tests that I've put here. So this first one should return just Willow, which it does. And the second one should return just Rocky, which it does. Perfect. We've now handled query parameters of the three main types, strings, booleans, and numbers. and TypeScript has been part of that process every step of the way. But our index.ts is starting to look a little chunky. So in the next scrim, we're going to shift gears a bit and separate our concerns with Express Router and see how Typescript fits into that. Welcome back. Our app works nicely now, but you can probably imagine that as we keep adding more, index.ts is going to get messy very quickly. So before we go any further, let's take a bit of time to refactor our code into a more modular structure. This is pretty standard express practice. We'll separate our routes and controllers and then wire it all up with express router. And most importantly, we're going to make sure all the TypeScript typing we've done so far comes along with us. So first in the source folder, I'm going to create our routes folder and I'll call it routes. Inside of that, we'll create our routes file. We'll call it pets routes ts. And now it's just a matter of refactoring. Now we're going to start by moving everything into the routes file as a first step. After checking that everything still works, we'll pull the route handlers out into the controllers file in the next scrim. So back in index.ts, I'm going to pull everything from the pet query params type all the way down to the end of our ID route. And I'll pop it over here in pets routes.ts. And of course, there are plenty of errors. So, let's handle them. First, I'm going to tackle this error over app. Now, app used to refer to an express instance, but here we're going to be using a router instead. To get started with that, I'm going to import express. import express from express. And then I'm going to create a router and call it pet router equals express router. And at this point, we can actually type this router just to make sure we've got that TypeScript on the brain. I'm going to import the router type from express. So import type router from express and then I can use that to type our pet router. So after pet router, we do colon router. Now that we have our pet router, we're going to replace app with pet router up here as well as our second route down here. Okay. Now we have some very unhappy errors regarding the request and response types. And since they're TypeScript related, let's have you fix them. Here is your mini challenge. Import and use the request and response types to clear the relevant errors. We did these about 10 scrims ago. See how you go. I'll catch you in a bit. Welcome back. We're going to do this by importing the request and response types. And to do that, we can tack it on after we've imported the type of router. So request and response. And the errors are gone. But we do still have some errors left. And that's due to the pet type and pets data being missing from this file. And your next challenge is to handle all pet related errors by importing and exporting the relevant data or types. Give it a go and I'll catch you in a bit. Welcome back. To solve these errors, we have to import the pet type and pet data object from pets.ts in the data folder. You'll see that both of these are already being exported. So, let's get importing. Back in pets.ts, we're going to import the pet type and the pets data object. Import type pet from data pets. And import pets from data pets. Okay, our file looks error-free. Now, let's hook it up to index.ts and check it out. But first, let's make sure to export our pet router so we can actually import and use it. Export our const pet router. Now, let's head on over back to our index.ts. We'll first get rid of any unused imports. And then we'll import the pet router from routes pets [snorts] rats. And then we'll use it with app use. Down here after the cores, we'll do app use. We'll put pets as the prefix path here and we'll reference the pet router. Now, using the pets path may not mean much right now as the app is quite small, but it's always good to use a path that accurately describes the data that it's working with. Let's check it out. In the network tab, if we just send a request to the endpoint that we were using before, we're going to get a no endpoint found error message from our 404 catch. And that's because with our pet router, we've added the pets path. So we'll have to make sure to add the new pets prefix to our API calls. Pets, for example, should give us the entire unfiltered list of pets. Let's try another one. Let's do pets. And we'll start a query. Adopted equals false and species equals rabbit. And we'll get all the rabbits that are ready for a new home. Okay, we've now split our routes out of index.ts PS and into a dedicated router. That already makes things more modular and easier to manage. But there is one more step. Right now, the route handlers themselves are still sitting in the routes file. In the next scrim, we'll move these to separate controllers to finish cleaning up our structure. In the last scrim, we separated our routes into a dedicated router. That was a big step towards keeping our project modular. But we're not quite done yet. Right now, all of the root handlers, the actual logic for each endpoint are still sitting in the routes file. As our app grows, that will get messy very quickly. So, in this scrim, we're going to take the next step, which is moving our handlers into a controllers file. And since this is a TypeScript course, we'll also make sure all the types we've already set up come along for the ride. We're going to create a new directory in source called controllers. And inside of it, we'll create a new file called petscontrollers.ts. And this file will hold the root handlers or the controllers. Now, each controller should have a clear and descriptive name. Our first handler returns either the full list or a filtered list if query parameters were provided. So, I'm going to call it get pets. And now we just have to copy the entire route handler from pets. Starting from here all the way to the end. and we'll pop it in right there. And errors galore. Don't worry, these are all because of missing imports. So, I'm going to hand it over to you to fix these errors. You're going to have to move some code around and import some types and data to get us going again. So, your challenge is to fix all errors. Go for it and I'll see you in a little bit. Welcome back. Let's fix up these errors. We'll first import the types that we need which are request response and pet. So import type request and response from express and also import type pet from data pets. [laughter] Then we'll handle this pet query params error by moving it over from the routes. We'll come here and we'll grab the entire type. The routes file won't ever need this anyway and we'll pop it in here. And finally, we'll import the pets data from our pets file in the data folder. import pets from data pets. And this controller is ready to go. While we're here, let's handle the other controller as well. Now, this one returns one pet or none based on ID. So, we can call this one get pet by ID. And once again, we're going to grab everything from pets.routs routes from here all the way to the end. And we'll pop it into here. And this time we shouldn't get any >> [clears throat] >> errors. And just like that, we have our two route handlers as controllers. Now we need these to be accessible from pets.outes. So let's make sure we export them. So export the get pet by ID and then also export the get pets. And then over in pets.routs, routes. We'll first get rid of any unused imports thanks to the fact that we put them in controllers now. So those are the two pets and we also can get rid of request and response. And then we'll import our two controllers. So import get pets as well as get pet by ID from controllers pets controllers. And then we can pop them in our routes. So the first one is get pets and the second one is get pet by ID. And it looks like I spelled controllers wrong up here. So, let's fix it. There we go. No more errors in the runner. Let's make sure everything still works by sending a request over here. And all good. Now, this scrim and the last one were a little bit lighter on new Typescript concepts, but there was plenty of TypeScript refactoring going on. We had to make sure all of our types like pet, pet query, params, request, and response came along with us as we split things into a router and controllers. The big win here is that our app is now clean, modular, and still fully typed, ready to grow as we keep building. Hello again. We're almost to the end. So far, our routes have been pretty straightforward. You ask for something, the server sends it back. But in real APIs, we often need a step in the middle to check or modify requests before they hit the route handler. And that's where middleware comes in. Middleware is just a function that sits between the request and the response. You may know from the learn express course here that there are a few types of middleware. There's the built-in ones which are provided by express like express static. And then we have third party middleware like the cause function that we installed and used with app do use. And finally we have custom middleware. And that's middleware that we write that's specific to our use case. In Express, it takes three parameters, w res and a special one called next. And just like how Typescript lets us type annotate w and res with the request and response types, it also has one for next. It's called the next function type. We'll be writing a couple pieces of custom middleware, typing them as we go, and then attaching it to this ID route here in our routes file. So, we're going to create a new folder under source called middleware. And inside of it, we'll create a file called pets middleware.ts. And now we're ready to write some code. I'm going to write a piece of middleware that checks the ID path parameter and makes sure it's a number. If it's not a number, we'll respond with a 400 error and a message. But if it is, we're going to call the next function and let the request continue onto the route handler. I'm going to call it validate numeric ID and we'll just set up the handler first. Since it's a piece of middleware, it'll have three parameters w res and next. We already know how to type the first two. So let's import the request and response types from express. So import type request response from express. Now this last one has its own type which is next function. So let's import that as well. Next function. And now we can use them directly in the handler. And I'm going to preemptively separate these parameters into their different lines like I've done before for easier viewing. So rack has the type of request. Res has the type of response and next has the type of next function. Now, we can get a bit more specific with our request and response, but we'll save that for later when we have our logic sorted out. The body of the handler is all regular JavaScript now. So, I'm going to speed through this, but I'll explain as I go. First, we're going to dstructure the ID from w params. Then, we're going to test to see if ID is a number. So, I'm going to set up this if else block first. Now, the test I used for this was reax, but just know you could have done it a few other ways. So the regular expression here is that and we're going to test ID. Now I want to test to see if this isn't a number. So I'll put the exclamation mark there. Now if this isn't a number, I'm going to respond with a status of 400 and JSON which contains a message at id must be a number. But if it is a number, we'll call the next function to tell express to just keep going. Now we have that logic. We can be more specific with our request and response generic types up here. And for that, I'm going to hand it over to you for a challenge. Get specific with the request and response generics. Think about what params we might be getting in the request and consider what we might be responding with. We haven't explicitly done this before, but the logic and layout of a middleware handler is very similar to a regular route handler. So, give it a go. Welcome back. Let's get specific with request and response. Starting with request. Since this piece of middleware will be attached to the ID route, we know that we'll be expecting an ID parameter. So, we can specify that in the request type. We'll only need to worry about the first parameter, which is the path params. We're looking for an ID param, and it will be a string. And as for the response, we know that there's only one response type that might come out of the handler, which is the error message down here. So this will simply be message, which is a string. And now we just have to export it with export. And then we'll import it into the pets routes file. Import validate numeric ID from middleware pets middleware. And then we'll attach it to the ID route by tacking it on just before the get pet by ID route handler. So validate numeric ID. Now let's test it out over here in the network tab. Let's first test a regular ID like pets 4. Yep, that works. Now let's try an ID that isn't a number so we can see that middleware in action. And we get the message pet ID must be a number. And just to show that this is the middleware doing its job, let's try with a non-existent ID that is a number like this one. And we get no pet with that ID instead. And that's coming from the get pet by ID route handler. Hooray for middleware. In the next scrim, you'll get a chance to build your own middleware from scratch. Making sure we're lacing TypeScript throughout. I'll see you there. In the last scrim, we added middleware to validate pet IDs. And now it's your turn to build some middleware of your own. We're going to build a piece of middleware called please or off. The idea is simple. Before someone can access the ID route, they have to provide a query parameter called password like this. And it must equal please. If they don't, they will get a 401 unauthorized error. And here is your final challenge for this course. Create the please or middleware. Write a standalone function called please or. It should take recre res and next. If query password equals please then call next. Otherwise send a 401 with a message in JSON. Then type rec res and next and attach this middleware to the ID route. And I've also left you some tests to play around with. Now this last one is a biggie and combines all the new things you've learned along with some express concepts. Take your time and do a bit of research if you need to and I'll see you on the other side. Welcome back. How did you go with that big challenge? Let's walk through it together. First, let's actually define the function and call it please or and this takes a wreck a res and a next. We need to grab the potential password parameter from the query. So, we'll have to dstructure that from wc query. So const password from w query. Now we can launch straight into the conditional. So if password exists and is please then we'll call next. Otherwise we'll respond with a status code of 401 for unauthorized and some JSON which contains a message unauthorized. You must say please. Now that we know what we're requesting and responding with, let's come up here and type our res wreck and next. Next is easy. So, we'll do that one first. It's just next function. Let's work backwards. If we're responding from here, it's because we're sending back the error message in JSON. So, the response generic here is simply message and that is a string. And finally, the request. We're looking for an optional query with a name of password. Now the query parameter is the fourth parameter in the request generic. So we will have to step through all the others to get there. So first is the path params which we have none. And then for the two bodies it is unknown and also an empty object. And finally we get to the query parameters. And here we're looking for a password which is optional and it is a string. Okay. Now let's export it so we can use it in our routes file. Back in pets.outs routes. We'll import it alongside our other middleware. So after validate numeric ID, we'll add please off and then we'll simply tack it right here just before validate numeric ID. Please off. Now this route has two pieces of middleware please or and then validate numeric ID that a request has to pass through before it gets to the actual route handler of get pet by ID. Let's check out the two test cases in the network tab. So the first one was just a call to pets and then the ID. Now this will return a 401 error and the error text which is exactly what we want. In fact, anything we put here as the ID will result in the 401 error. And that's because we've put the please or middleware first. Even a non-numeric ID like this one will come back with the 401 error before it even gets validated. Now let's try with the password. So query password equals please. Now we're getting pet ID must be a number and that's coming from validate numeric ID instead because we've gotten past that first piece of middleware. And finally, let's actually get some data. We'll ask for the pet with an ID of 14. And we'll also pass in the query of password equals please. And that should get us what we want, which is Daisy the Rabbit. And there we have it, our own custom middleware fully typed with TypeScript. and working alongside our other checks. With both please or and the numeric ID validator in place, our project is definitely starting to feel like a realworld API. And that wraps up the last piece of functionality for this course. In the final scrim, I will give you a quick recap, share some parting thoughts, and point you to what you can do next. And that's a wrap on Express with TypeScript. Well done. You stuck with me till the very end. You started with nothing but an empty project and along the way you set up Express with TypeScript. You created routes and typed them end to end. You worked with real world data and query parameters. You made your project modular with routes and controllers. And you even wrote your own middleware all while keeping TypeScript in mind. Now you've got a fully typed starter API for a pet shelter and more importantly the skills to keep building. So, where can we go from here? You could connect your API to a real database or explore advanced patterns like async controllers and error handlers or build a front end that consumes your typed API. Most of all, keep practicing. The more you think and type in Typescript, the more natural it will become. So, thank you so much for joining me on this one and I will see you next time.

Generated algorithmically for Search Engine Indexing.

Summarize Another Video