05 October 2022
TypeScript Node.js is a match made in web development heaven! Learn how to migrate to TypeScript with this tutorial
You’re using Node.js to create efficient apps? Good for you! But is your development process just as efficient? TypeScript can go a long way to improving your Node.js project! I was able to realize it first-hand during a recent project of mine. I’m going to use it as an example to review the TypeScript Node.js relationship and show you why and how to move from JS to TS in a Node application.
By the time I tell you all about my project, you’ll come to a striking realization:
While I’m on it, I’m going to answer a couple of questions:
- How to start a new Node.js project with TypeScript?
Before I go there, it might be encouraging to take a look at the present state of TypeScript in 2022.
TypeScript popularity in 2022
And yet here we are in 2022 – at a time when TypeScript is a powerhouse in the world of web development. Just how much of a powerhouse, you may ask?
Well, according to our State of Frontend 2022 report, as many as 84,1% of all developers surveyed used TypeScript over the last year.
These are mostly frontend developers, though. What about a TS Node dev?
Ryan Dahl, one of Node’s original founders, is now promoting Deno – a TypeScript-first Node.js alternative. However, it’s still light years behind Node.js in terms of popularity and community support.
And that’s just too bad because I constantly come across Node.js projects that could use some TypeScript! Why? Let me tell you something about a project I recently completed that really succeeded… in annoying me.
Ryan Dahl is one of the creators of the Node.js runtime. He continued to work on it until 20212. Eventually, he went on to create a Node.js alternative – the TypeScript-based Deno. He explained his views on the development of Node in the lecture 10 Things I Regret About Node.js.
From one of our customers, I inherited a legacy application written in Node.js. It had Express.js on the backend and React on the frontend. What’s more, the application had no unit tests whatsoever. You can imagine that learning the codebase was pretty painful without TypeScript.
Every time I wanted to be sure what a given argument is, I had to dig into the code and analyze it thoroughly. And even when I did just that, I still experienced unexpected behavior, especially when the variable in question had a complicated structure full of various dependencies.
I think that every dev had to face a situation when you have to go six functions backward to analyze how certain parameters would behave in the last function. As I was performing this thankless job, I could not stop thinking how much easier it would have been if it had been written in TypeScript.
In what ways would TypeScript make it better exactly? There are at least four areas I’d like to mention:
Let’s take it from the top.
Another time, seemingly out of nowhere, a field that has always been an array magically transforms into an integer.
Sure, there are some solutions to that. But it gets really time-consuming when you have to run a check with hasOwnProperty or wonder what’s the best way to find out if a variable is an array every time you want to use it.
TypeScript ensures that you know what to expect from variables you work with. Admittedly, setting up the interface for objects takes a lot of time in the beginning, but it saves much more during debugging.
When you have to delete an object field, you need to use a destructor. That way, TypeScript forces you to think about what you’re doing. As a result, TypeScript does its best to prevent you from shooting yourself in the knee every time you change your code.
TypeScript continues to get better and better! Don’t believe it? Check out the new TypeScript features article to get the latest information.
When to move to TypeScript
Having said that, the sooner you turn to the TypeScript compiler the better. Bigger apps will surely take a lot more time to migrate to TypeScript so you should make your decision quickly, early in the lifespan of your Node.js application. Maintaining a growing Node.js app without static types from TypeScript is going to get increasingly hard. It is just impossible to remember what every variable does in the code.
When to migrate your Node.js app to TypeScript ASAP?
- When your application handles data of great value and importance, especially money.
- When the application moves from the proof-of-concept state into the real world.
- When you know that the application is going to get support for several years going forward.
- When you are going to keep adding new features to your application.
- When the application codebase is still relatively small.
The positive side of the migration process is that you will learn a lot more about your code and probably encounter a lot of refactoring opportunities.
Before you can start moving the codebase of your Node.js application to TypeScript, you need to add some parameters to your TypeScript configuration file tsconfig. In particular:
- allowJs so that you can import .js files into .ts files,
Now, try to get an error-free compilation while using any types. Then, solve error issues incrementally and replace any type with interfaces. In the beginning, you don’t need to have types for everything. You can add types to core functionalities of your Node.js application step by step. Remember to install types for packages.
The file sends an invitation email and a request email depending on the emailType parameter obtained from the request. Out of the file above, I created multiple TypeScript files using OOP (Object-oriented programming) rules such as the single responsibility rule and encapsulation principle.
Now, I need to create a generic EmailService class.
The EmailServiceConfig interface I imported defines the structure of dependencies that this class takes upon constructing. In a situation like this, it’s a good idea to use some kind of dependency injection container such as Awilix. It helps manage objects with a lot of dependencies. I skipped it here for simplicity’s sake.
I also created a nodemailer transporter in the constructor. Then, I made an interface to define the sendMail method parameters. You can see that the EmailService class is more self-described and error-proof and that it is only responsible for sending emails. To that end, it needs to create nodemailer. It also uses the following config file:
When you create an EmailService object, you need to pass it to it as a dependency like this:
const emailService = new EmailService(emailServiceConfigFactory(process.env));
That way, you have validated your config and you know for sure that you have everything necessary to create a transporter for nodemailer.
Splitting responsibilities between classes
Now, I’m going to show you a more specific class. It’s responsible for sending invitation emails.
Apart from sending an invitation email, this class creates a JSON Web Token (JWT) in order to make a magic link. As a dependency, it takes the aforementioned EmailService. It also uses the InvitationTemplate, which is simply a collection of exports:
How can you use these classes? Consider the following example:
It would have looked prettier with a dependency injection container, but TypeScript will remind you about necessary dependencies anyhow. With those classes, you split a lot of responsibilities between them. Now, whenever you get into a class, you are able to understand what is going on much more quickly. The classes are easier to test as well.
Moving an existing app to TypeScript is difficult.
Hire devs that have already done this time and time again.
Building a TypeScript app in Node.js from scratch
If you want to start your TypeScript Node.js project from scratch, you need to install Node.js, and then initialize the project with npm init. Then, you go through the wizard and fill in the necessary data.
Once the initialization is complete, install TypeScript with the npm install typescript –save-dev command. Add types for Node with the npm install @types/node –save-dev command.
Now you can create an index.ts file that has your starting “Hello World!”. Then, run the npx nodemon ./index.ts command.
It’s a good moment to create some environment variables. Since every project has a ‘.env’ file, create one and set some variables such as STAGE to indicate if you are in the production or development environment. Then, set APP_NAME and PORT.
At this point, the .env file should look like this:
The next step would be to add some validations for these environment variables. First, you need to install the required packages using the npm i celebrate ts-pipe-compose command. That would make the appConfig.ts file look like this:
What happened here? Firstly, I installed celebrate, which is middleware for joi. The celebrate package will be also useful later for validating parameters from HTTP requests to your Node.js application. I also installed the ts-pipe-compose package. It makes it possible for me to perform pipeline(loadConfig, validateConfig). With that, whenever I call appConfigFactory(args), it results in validateConfig(loadConfig(args)). To put it in different words, it first loads the config and then validates it against your defined joi schema.
You can now use appConfigFactory in your index.ts file, in which I will also define the routes of the application. For that, I need to install express and dotenv packages first. The relevant commands are npm i express dotenv and npm i –save-dev @types/express respectively.
You can now create your app.ts file, which has the class that initializes your Node.js app.
Create the index.ts import app in it and run the init method:
To sum it all up, the structure of directories in your project should now look as follows:
In order to run the project, just use the npx nodemon ./src/index.ts command. When you send the get request with the arg param and a string value to localhost:3337, you should get the following response: “Nodejs, TypeScript, Express App”. You should also get the string value of your param. Otherwise, you will get an error that says: “Validation failed”.
When everything is alright, you can proceed to extend the project, creating some models, and controllers with adequate routes for each.
Last but not least, I want to mention the project directory structure. Out there in the wild, there are so many projects with empty directories, created out of habit and for no good reason. Do not force yourself to fit a new class into an existing directory in your Node.js project. Create a new one only if it is really necessary.
Express-boilerplate by The Software House
To help you start with your new TypeScript Node.js application, TheSoftwareHouse has created an express boilerplate. It helps a ton in setting up new projects. The whole boilerplate is based on docker containers so that you can start developing in no time. Apart from that, it also has plenty of useful tools. Be sure to check this out.
Node.js TypeScript combo – trends & predictions
TypeScript is getting more and more attention and rightfully so.
After all, it helps you keep big projects at bay. It makes it easier to create good clean code. As a result, more and more new and existing tools choose to support TypeScript or even use it as its foundation.
But whether NodeJs is ever going to be TypeScript-first is highly doubtful due to the huge amount of baggage that Node.js has generated over the years.
Deno and Bun both start with clean sheets and offer native support for TypeScript. However, developers are still hesitant to use either of them in a production environment. It seems that Deno is much closer to being ready for some production projects.
The best devs keep tabs on the latest innovations in their fields.
As part of our internal workshops, our Node.js and frontend developers learn all about the latest techniques and approaches. Perhaps they can use their knowledge to your advantage?
Conclusions & lessons learned
As you can see, TypeScript really does make a difference, especially in a big project.
- Due to its benefits for both simplicity and maintainability, TypeScript is a huge help for both developers who are only starting with a project and those who have been working on it for years.
- TypeScript takes away a lot of the project overhead, giving developers more spare time and brain capacity to focus on different aspects of coding than maintenance and testing, especially the development of new business logic.
- In order to make up your mind about migrating to TypeScript, you need to answer questions such as: How long will the project be maintained? How big is it? How many of our developers already know TypeScript?
When the answers indicate that implementing TypeScript will be relatively easy and provide its benefits in the long run, you should definitely go for it!