Introduction to Fastify with TypeScript
How's it going everybody? This is Weiss back with another video on the channel. Today I'm going to be showing you how to build an API using Fastify, especially using TypeScript. I've ordered a full course on Fastify using plain JavaScript. This video is going to be about Fastify setup with TypeScript. So yeah, without further ado, let's get started with Fastify and TypeScript.
Project Initialization and Dependencies
First, we need to set up a package. To be able to set up a package, you are going to, first of all, initialize it with npm init -y. I'm going to press enter and here I've got a package.json. Here the index.js file for our entry file.
Next, we're going to install Fastify. And once I install Fastify, you will see that we will have a dependency key here, and then it will have a Fastify installed.
Next, we're going to install TypeScript and Node types. I'll use this -D flag because I want to save that as a dev dependency, not as a project dependency.
Configuring TypeScript (tsconfig.json)
To set up TypeScript with this Fastify, I'll use tsc --init. If you have installed TypeScript globally, what you could do is use npx tsc --init. I'll just use this, and this will create us this tsconfig file.
Within this file, I'd like to change some changes. And here we would like to select ESNext, so we're going to use the next version of TypeScript. I'm going to uncomment this baseUrl and outDir. Let's find outDir... that's what we need. And I'm going to find moduleResolution and that would be node. So let's uncomment that line as well.
Okay, so we got this target set up, module to commonjs, node as moduleResolution, baseUrl. Next we go outDir, which will be our build. So actually instead of just using ./, I would like to use this dist. Okay. Next, we're going to go to esModuleInterop, make sure it's true. forceConsistentCasingInFileNames, we can make it true as well. strict true.
Setting Up NPM Scripts (package.json)
Next, let's create index.ts file. Let's go back to package.json and I'm going to be creating a couple of scripts here. So the first script is going to be the build script and then the start script. The script tag above here on line six to eight, I'm going to just get rid of that. Let's save package.json. If we run this npm run build script, what it's going to do, it's going to generate this index.js file. Now index.js file is going to be our main file, as you can see here.
And I'm going to install another global dependency: install -g nodemon.
We'll modify our start script. So I will just paste that script there and then I'll explain it. So here, tsc -w is basically going to compile the TypeScript file and watch for any changes. Then we've got this & symbol which basically tells that please run the previous command, after you run the nodemon. Now, nodemon is the dependency that we just installed which is going to run this index.js file.
Let's go to the terminal and I'm going to run the start command. Now it says starting compilation in a watch mode, that's TypeScript, and then we have this hello world. hello world in index.ts file. So if I remove one hello world and I save the file, you will see that our code is running now.
Building a Basic Fastify Server
Let's start by setting up the Fastify server. Start with the import. I'm going to import Fastify from 'fastify'. And then we're going to create a server. So I'll just use this, let's say app = fastify(). You can also pass in a configuration, for example, you can pass in logger and we can set that to true. Okay.
Let me go to the next line and now we have the server running. Now we'll go to app and we're going to register the first route, and we can just say this is going to be our first route, and the first route is going to take request and reply, and then we are going to reply to say hello world.
So we're going to use app.listen and we're going to use the port, which is going to be, let's say 3000. And then we can also do an address. And here this line is basically telling me that if there is an error, just log the error and kill the process.
Before we're going to run this code, I'd like to probably add some types. So the app is going to be a type of FastifyInstance. VS Code is giving me a suggestion and I'm going to import FastifyInstance from 'fastify' and add that as a type.
Debugging and Fixing Configuration Errors
Let's try to run this. I'm going to open Terminal and I'm going to type npm start. And here we're getting a couple of errors saying this tsconfig.json file doesn't have include and exclude keys. Let's go and fix those. So here I'm going to add a comma, and I'm going to paste those. Here I'm saying just include all of the TypeScript files and exclude node modules and type definition file and a spec file in the future we might want to do some testing for Fastify server.
Next, I'm going to change this outDir to dist. Save that as well. Next, we are going to go to package.json file and the main file is going to be dist/index.js. Okay, we're going to go into the start script as well and it would be dist/index.js. Okay, let's close package.json file and close this tsconfig.
I can see there are no errors, actually. Compilation part is done, there is no issue. But you notice there is no server running from Fastify because this command is not being executed. And the problem with that is if you don't have installed nodemon globally or as a dev dependency, then you will not be able to serve this JavaScript file, right? So what we need to do is stop the server, npm install -g and install nodemon. Once the nodemon is installed, then we will execute npm run start again. There is listening on port 3000. If you look at ports, you will see port 3000 has been port forwarded.
Now, if I go to the client and send a request, you'll see 'hello world' is being returned. Okay. Now you go and change this to let's say 'hello world modified'. Let's save it, and in the terminal you'll see our server will start again. And if we send the request now, you will see the result is 'hello hello world modified'.
So at this stage, we have successfully installed nodemon, TypeScript configuration and set up the Fastify server.
Using TypeScript Generics for Typed Routes
Let's talk about using generics. The type system in Fastify heavily relies on generic properties to provide the most accurate development experience. app.get implements two interfaces: RouteGenericInterface which contains five named properties: Body, QueryString, Param, Headers, and Reply. The interface Body, QueryString, Params, and Headers will be passed down to the route method into the method handler, into the request instance and the Reply interface to the reply instance.
So let's create an interface right above. So I'm going to actually, we can provide an interface as an object or you can also specify an interface above. So let's say interface, and I'm going to first of all specify a request interface or we can just name it custom, so IQueryInterface and it can have a couple of properties in Querystring. So let's say username that would be a type string, and let's say password type string. Okay, we can also create an interface for headers. So just say IHeaders, let me make H capital. And this will take, let's say, x-access-token.
Now to be able to use this, if you go above here, let me zoom in a bit, and we're going to use this generic symbol and pass in the object. And this object is going to take a QueryString first of all. So that's Q is capital... and then that would be IQueryInterface. Okay. And then it's going to have a Headers that will take IHeaders interface. Okay. So while you pass these, then what you can do now in the request, you could actually say const { username, password } = request.query;. So this bit will be passed down to the request object.
And you can also specify a reply. So if I do Reply and we can pass in an interface IReply. And here we can just say code, message, and we can just maybe a body: any. Okay. And then we can say IReply is going to be this type. Now this one is going to cause an error because the IReply is not properly done. So what I'm going to do now is, let's say, return reply.send() and it would have an object called a message: 'success' and body you can pass in a username or password, whatever you like. Okay, so let's close this. Done. Okay, now you won't get an error because you probably typed the reply that you're expecting from this request.
Now, this is how you can implement custom interfaces.
Exploring Fastify's Type Definition Files
Now this information can be found in the official documentation of Fastify, but I would like to take you to a declaration file that is provided by Fastify. So if you look at the node_modules/@fastify... actually not... you're going to go to fastify and then inside the source code you will see types. And then here all the types are available. For example, for route if you go to route.d.ts, you will see RouteGenericInterface and that's what we are using. It extends RequestGenericInterface and then the ReplyGenericInterface.
I will go and click on that and here you can see we use this QueryString, we used Headers and a Body. Okay, and a body is just like an unknown type, but you can pass in your custom types for that. They're all optional.
The same way, if we go back, and then here is a Reply and it needs a Reply and ReplyDefault would be I guess unknown as well, so you can pass in your own custom interface to it.
Here are all the type definitions that are available in Fastify source code. You can actually go ahead and then take a look at what's possible. So you can use all the interfaces that are provided by the source code, but also, using generics, you can override those properties as well.
Official Documentation and TypeScript Plugins
Now, this one is a very important one: instance.d.ts. This is like the main interface for Fastify. So anything Fastify can do can be found here. For example, if you want to call a decorate function, and the decorate function is something that will take a property or a symbol, and it will also have the value and extends args. If you understand TypeScript, then you should be able to understand these definitions, but it's very, very easy to follow what's available. Now, I don't want to go into detail because this is purely a setup video on how you get started with Fastify and TypeScript. Now we've achieved that bit.
But I'll take you to the documentation quickly. And if you go to the official documentation of Fastify, in docs, you will see a reference, and here is Fastify TypeScript documentation. Here it will talk about how you can set up and follow the same guide, but it showed you some generics that you can use. The guide here I want to show you is about plugins. You can go through this; it's very simple stuff that they're explaining. It's good to go through this. But I would like to point out here: creating TypeScript Fastify plugins. And here, if you don't read this passage, you will miss out on one thing and you'll find that something is not working. Take a look at this line. This is something special to TypeScript plugins versus JavaScript plugins. So here you see you have this export interface MyPluginOptions and MyPluginOptions is set that to string. Okay? And this gets implemented for all the plugins that you will be writing. In JavaScript you won't have to do that, but if you have a plugin you write with TypeScript, you will have to specify your plugin options.
Conclusion and Final Thoughts
So, this is it for this tutorial. If you have any questions or any issues related to Fastify and TypeScript, you can comment below and I'll try to answer because I've been using Fastify and TypeScript for some time now and I really like the combination of both Fastify and TypeScript. Alright, thank you so much for watching, and I speak to you guys in the next video. Cheers.