fastify with TypeScript — production ready integration

Complete guide to building production ready application using fastify and TypeScript.

Pavlo Poliakov
SHARE NOW TECH
8 min readJan 21, 2019

--

TLDR: here is the repo with a complete example.

Here at car2go we don’t have any framework or technology lock, we always try to use what fits and resolves one’s team particular issues the best. In my team (customer’s backend), our current web framework of choice is fastify. We are very happy with it and can confirm that it’s a good choice for microservice applications. But we always believe, that things can be even better.

The problem, which I, as developer, sometimes face is, that returning to the development of certain service after some time can be difficult. You are just out of context and often need some time to understand (again) what’s going on there. Working assumption — TypeScript can help.

According to the recent top programming languages of 2018 review (and not only) it’s obvious that TypeScript continues to gain its momentum. So I’ve decided to try out TypeScript and fastify and build a complete integration, which covers my day to day needs:

⚙️ local development setup
📚 integration with a database
🔍 testing
🐛 debugging
🤖 deployment
📋 error logging

In this article you’ll find out the results of my findings. Let’s crack these points one by one.

⚙️ Local development setup

What I understand behind the “local development setup” is that I can actually develop the application on my local machine. This shouldn’t be complex, isn’t it?

Let’s initiate the project and install all the dependencies which we’d need on this step:

Disclaimer: in this article all dependencies are installed like prod ones, this doesn’t mean that you need to do the same, when developing production ready applications.

Besides obvious libraries, like typescript,fastify and fastify-plugin. We’ve also installed a secret sauce ts-node. ts-node would allow us to execute TypeScript files without the additional build step upfront. It’s just what we need for the development.

Let’s specify how we want to use TypeScript in our project and create tsconfig.json file in the root folder:

With this we, basically, declare that we will store our code in the src folder, when compiled, the output should be put into dist folder. We want to compile it into es2016. Please, include es2016 libraries while compiling and create source maps.

Now let’s create a simple fastify server src/index.ts:

And declare our first route src/modules/routes/status/index.ts:

The only problem is, that in the fastify typings there is no definition for fastify-blipp.

fastify.blipp no definition

This small module does nothing, but prints the endpoints which we’ve registered. In TypeScript we can augment existing definitions using declarations merging feature. Let’s do that in src/@types/augmentation.ts:

This will make the things sorted out. Now we just need to configure additional script in package.json which’ll start our app, let’s add there the next definition:

After we can launch our project for the first time: npm run dev.

first run

We can try out the /status endpoint if we want: curl 0.0.0.0:3000/status.

This makes us ready with the point number one.

📘 All the changes which were done during this step can be found in this commit.

📚 Integration with a database

For the database example we’d use MongoDb. Let’s imagine, that we want to develop a simplest CR application, which is going to store information about the Mercedes-Benz Formula One vehicles.

On this step we need some additional libraries installed:

Our infrastructure will be provided with the help of docker compose.

First let’s create docker-compose.yaml file:

From this very moment — don’t forget to run docker-compose up before you run our application.

Now let’s define the model for our MongoDb document src/db/models/vehicle.ts:

So information which we are going to store will contain year and name of the vehicle, as well we will dynamically add createdDate for each document.

Now let’s create a fastify plugin, which will expose our database access and models list through the fastify server instance src/modules/db/index.ts:

As you see, our plugin decorates fastify with the new property db. This property in its turn contains all the models available. On top of the file we’ve declared new interfaces Db and Model. Again, according to the default definitions, fastify server instance does not contain any property called db, so let’s use augmentation again and update src/@types/augmentation.ts:

Now it’s time to declare the new routes, which actually will insert new value to the database and read it from there, src/modules/routes/vehicles/index.ts:

Last step — wire everything together with `fastify` and test it manually. To the src/index.ts, please, add the next part:

Here are the results, let’s run it:

integration with a database run

And manual test:

integration with a database test

Integration with a database is established, let’s go further.

📘 All the changes which were done during this step can be found in this commit.

🔍 Testing

In our team jest is a test runner and assertion library. It is relatively easy to make jest work together with TypeScript.

Let’s install new dependencies:

And create an appropriate jest.config.js:

At last — update our test script in package.json:

That was it, now we can add a simple test, which we also write using TypeScript, src/__tests__/simple.test.ts:

That’s it, now we can “test” our tests with npm test.

Here are the results:

Testing the tests

📘 All the changes which were done during this step can be found in this commit.

🐛 Debugging

It’s important to be able to debug your app. Not that debug where you (and I) put tens of console.log and how that this time it will output something. But that one where you set the breakpoint and application stops there and wait till you think how did it end up there.

I, personally, use Visual Studio Code as my IDE, so my examples will work there.

debug the whole application

Sometimes you need to run the app and check what’s inside. Here is a suitable debug configuration for VS Code:

What I like about it, that it uses ts-node and there is no build step. Here is short video which shows the debug process:

debug particular test

From the other hand, it may be useful to debug test. Unfortunately, I didn’t find an easy way to do that, and debug config for the test includes the build step.

Therefore, let’s explicitly add the build step into our package.json scripts:

Now we are back at the debug configuration definition. First we need to define a task in .vscode/tasks.json:

And as a second step, define a new debug configuration in VS Code, here is the definition:

As you see, first it will build the project. For the successful debug, project, not tests, needs to be built and available in dist.

For this purpose exclude part of tsconfig.json needs to be updated:

Pay attention, that the file, which you are going to debug must be open in your editor, since we use `${relativeFile}` in the debug configuration definition. Here is a short video, which shows the debug process for the test:

📘 All the changes which were done during this step can be found in this commit.

🤖 Deployment

I put “deployment” before “error logging” on purpose. The main reason is, that when we are developing, we use ts-node and ts-node will produce correct .ts stack traces as granted. So first, let’s see how we can build our application and prepare it for the production usage.

First change we need to do at this step — is to make our application configurable. Currently the address of the MongoDb is hardcoded, but we need it to be dynamic, so for “local” development we have one and for “production” — different one.

Let’s install new dependencies:

Create two configuration files, to store the connection string:

config/default.yaml

config/production.yaml

Now we need to update src/index.ts to use config instead of the hardcoded string:

Since we’re going to run our application from the dist folder, let’s update our package.json with the new script:

Luckily, nowadays, we do not deploy to the bare metal. Usually we deploy to one of the fancy cloud providers or to the Kubernetes. So, basically, we don’t deploy Node.js or TypeScript applications. We deploy docker containers.

Let’s define one for our application, main difference comparing to the conventional Node.js container is, that during the image build there is additional “build” step, Dockerfile:

We are almost there, at last, let’s create new Docker Compose file to emulate the production deploy. This file will contain both — our service and MongoDb service, docker-compose.production.yaml:

Now let’s launch it with docker-compose -f docker-compose.production.yaml up and check that our service works.

Docker Compose is able to launch successfully:

“deployment” with Docker Compose

Our service is live and kicking:

deployment app test

Let’s jump to the last point of our journey.

📘 All the changes which were done during this step can be found in this commit.

📋 Error logging

As you know, with TypeScript it’s not code you wrote is going to be executed. Before actual execution your .ts files are compiled into the good old .js files and they are going to be executed by Node.js. So when there will be a runtime error in your application — the stack trace will be referencing the code which is being run. This does not help much during your investigations and attempts to fix that issue.

Let’s define a new route, which will throw the error and test it using our “production” docker-compose.

src/modules/routes/error-thrower/index.ts:

Register this route in src/index.ts:

Let’s rebuild images and start production Docker Compose again:

Here is an error part from output of Docker Compose after we hit /error-thrower:

As you see — it points to the dist folder.

Let’s make it reference the actual code we wrote. This is possible with the module called source-map-support.

Install new dependency:

Integrate it right at the top of src/index.ts:

Test the /error-thrower, like we did it before. Here is an error part this time:

I bet you see the difference. Now you are ready to debug right after you saw an error stack trace.

📘 All the changes which were done during this step can be found in this commit.

Conclusion

From my perspective — that was mainly it. Of course, there is much to improve, for example, we are not going to develop any application without a proper linter, aren’t we? But today we set a solid base for the real production ready service.

During my experiment I’ve learned a lot about the TypeScript and current tooling available. Overall I’d recommend using TypeScript, since it is going to make your development and maintenance of the services more predictable in the long run.

Thanks for bearing with me till the end! I’m looking forward to hear about your experience of using fastify and TypeScript. Please, share it in the comments below.

--

--

Pavlo Poliakov
SHARE NOW TECH

Principle Engineer at SHARE NOW. I share my software engineer growth journey.