Strongly-Typed Node.js applications
Using Typescript with Node.js
Enable the power of types in your Node.js application with Typescript
Hey people!
Let’s explore the powerful combination of TypeScript and Node.js for building robust and maintainable server-side applications.
The article delves into the benefits of using TypeScript, a statically typed superset of JavaScript, in the Node.js environment.
It covers key concepts, configuration, and practical examples to help developers harness the full potential of this dynamic duo, making the development process more efficient and codebases more resilient.
Whether you’re a seasoned developer or just starting with Node.js, this article serves as a valuable guide to elevate your coding experience and enhance the scalability of your projects.
Let’s get started
To start, let’s initialize our NPM project with:
npm init -y
You can press ENTER
to go through the configuration steps.
Now, let’s first install typescript
in our project to enable the tsc
command:
npm install --save typescript
Now, let’s set up the npm script to enable running the typescript commands. Add the tsc
command to your package.json
scripts:
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.2.2"
}
}
With this, we can run the following command to initialize our TypeScript project and generate our configuration file tsconfig.json
:
npm run tsc -- --init
This will generate a tsconfig.json
file for you:
{
"compilerOptions": {
"target": "es2016",
"module": "commonjs",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"skipLibCheck": true
}
}
If you’d like to have it with some pre-configuration, you can pass some options to the init
command. Below is the one I generally use:
Linux:
npm run tsc -- --init --target esnext --module nodenext \
--moduleResolution nodenext --rootDir src \
--outDir dist --noImplicitAny --noImplicitThis --newLine lf \
--resolveJsonModule
Windows:
npm run tsc -- --init --target esnext --module nodenext `
--moduleResolution nodenext --rootDir src `
--outDir dist --noImplicitAny --noImplicitThis --newLine lf `
--resolveJsonModule
This will generate the following file:
{
"compilerOptions": {
"target": "esnext",
"module": "nodenext",
"rootDir": "src",
"moduleResolution": "nodenext",
"resolveJsonModule": true,
"outDir": "dist",
"newLine": "lf",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"strict": true,
"noImplicitAny": true,
"noImplicitThis": true,
"skipLibCheck": true
}
}
For a complete list of the compiler options, check here.
tsconfig.json
The tsconfig.json file has the configurations that the typescript compiler will use when generating your build files.
Once you run the tsc
command, it initializes it with the compiler options, but it also has some other configurations aside from compilerOptions
that you can customize to your needs. These are:
files
— Tells the compiler which files are part of your build. It is mostly useful when you have just a few files in your project.include
— Tells the compiler which files/patterns should be included in the buildexclude
— Tells the compiler which files/patterns should be excluded from the build- extends— Tells which
tsconfig.json
file to use as a reference. It will give you the configurations from it. (we’ll talk about it later, as it is super useful) watchOptions
— Configures the behavior of thetsc --watch
command.references
— Lets you split yourtsconfig.json
file into multiple pieces and reference it in a central one.
If you have set rootDir
in the compileOptions
, files
, includes
, and excludes
will take files relative to that directory. If not, it takes the files relative to the directory the tsconfig.json
file is on.
For a complete look at all the configurations, check the tsconfig
documentation here.
Using existing typescript configurations
As mentioned before, TypeScript has the possibility of letting you use someone else's existing configuration in your project.
For that, you can make use of the extends
by passing the path of the configuration file you want to use. It can be relative to your project or a URL reference.
Like:
{
"extends": "./configs/tsconfig",
"compilerOptions": {
"strictNullChecks": false
}
}
Or you can use a TSConfig base, like:
{
"extends": "@tsconfig/node12/tsconfig.json",
"compilerOptions": {
"preserveConstEnums": true
},
"include": ["src/**/*"]
}
For a list of the recommended bases, check the documentation here or in this repository.
Getting into action
So, now that we know how to configure typescript, let’s set up a small project.
First, create a folder src
that will hold our code. This is the same folder I defined in rootDir
. Now add a index.ts
file. You’ll be able to see that you can use and see types.
Cool! We managed to enable and use typescript to write our code, but how do we run it?
To do so, you can run npm run tsc
, which will compile the code into JavaScript. If you define the outDir
options in the compilerOptions
, the command will send the JavaScript files in there.
Now I can just run node dist/index.js
and it will run my index.js
file
Optimizing it
Now that we know how to run it, we can optimize the build steps.
You notice that if you add other files and recompile it, they will be added to your build folder, but if you delete one, it won’t be deleted once you run npm run tsc
. To fix this, let’s add the package rimraf
to assist us in deleting the old files.
npm i -D rimraf
And then, we create a dedicated build
step in our package.json
:
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"build": "rimraf dist && tsc",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.2.2"
},
"devDependencies": {
"rimraf": "^5.0.5"
}
}
Now, when you run npm run build
, it will automatically delete the old files before generating new ones.
Start
And you can add the start
script to automatically run your code:
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"build": "rimraf dist && tsc",
"start": "npm run build && node dist/index.js",
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.2.2"
},
"devDependencies": {
"rimraf": "^5.0.5"
}
}
We can optimize the start
step even more by using the package ts-node
. It will allow you to run TypeScript
code without the need to build it first.
So let’s install it with:
npm i -D ts-node
And then let’s modify the start
script:
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"build": "rimraf dist && tsc",
"start": "ts-node src/index.ts"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.2.2"
},
"devDependencies": {
"rimraf": "^5.0.5",
"ts-node": "^10.9.1"
}
}
Then run npm start
and see your code running.
Watch
There are many ways that you can enable watch
in your TypeScript code.
My preferred way of doing it is by using a combination of ts-node
and nodemon
.
You can install nodemon
with:
npm i -D nodemon
Then create a nodemon.json
file in the root level and add the following code to it:
{
"watch": ["src"],
"ext": ".ts,.js",
"ignore": [],
"exec": "ts-node ./src/index.ts"
}
Now, you add a watch
script in your package.json
file:
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"build": "rimraf dist && tsc",
"start": "ts-node src/index.ts",
"watch": "nodemon"
},
"author": "",
"license": "ISC",
"dependencies": {
"typescript": "^5.2.2"
},
"devDependencies": {
"nodemon": "^3.0.1",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1"
}
}
This will allow you to run npm run watch
and see something like the following:
Third-party libraries support
If you want to use a third-party library, like express
, chances are that the team also made available a package with the types for that package.
So, if you are seeing an error like the below:
For express, you can do
npm i -D @types/express
This will add express
types to your project.
{
"name": "typescript",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"tsc": "tsc",
"build": "rimraf dist && tsc",
"start": "ts-node src/index.ts",
"watch": "nodemon"
},
"author": "",
"license": "ISC",
"dependencies": {
"express": "^4.18.2",
"typescript": "^5.2.2"
},
"devDependencies": {
"@types/express": "^4.17.21",
"nodemon": "^3.0.1",
"rimraf": "^5.0.5",
"ts-node": "^10.9.1"
}
}
And now you can freely use TypeScript with express
.
Conclusion
In a nutshell, this article is like a friendly guide to using TypeScript and Node.js for building strong and easy-to-maintain server-side applications. It’s perfect for both experienced developers and those just starting with Node.js.
The guide takes you through the whole process, from starting a new NPM project to configuring TypeScript with a tsconfig.json
file. It even throws in some cool tricks like optimizing build steps using rimraf
and making your life easier with ts-node
so you can run TypeScript code without the hassle of building it first.
The article doesn’t stop there — it shows you how to set up a watch mode for automatic restarts during development, making your workflow super smooth. And hey, it even covers setting up TypeScript with popular libraries like Express
, showing you how to handle third-party goodies like a pro.
So, if you want to level up your coding game, make your projects more scalable, and have a blast with TypeScript and Node.js, this guide is your go-to buddy.
Happy coding! 💻