Editorβs note: This article was updated on 24 November 2023 to include a section on using ts-node for executing TypeScript directly in Node.js, as well as a section that covers the process of transpiling TypeScript code to JavaScript.
Creating a server with JavaScript using Node.js and Express is pretty easy. However, as your application grows in complexity or when collaborating with a distributed team of developers from across the globe, TypeScript emerges as a great alternative to JavaScript.
TypeScript improves code strength and clarity with static typing, making collaboration smooth and aiding project scalability. Its advanced tooling, comprehensive IDE support, and compatibility make it a great choice for a smoother development experience, especially in evolving projects.
In this article, weβll explore a beginner-friendly way to configure TypeScript in an Express app, gaining an understanding of the fundamental constraints that accompany it.
To maximize the benefits of this tutorial, ensure that you have the following:
Iβve set up a GitHub repository for beginners covering this project. You can fork the repo and access all that weβll discuss here. The main branch has the TypeScript project, and the javascript branch has the JavaScript version.
package.json
fileStart by creating a new directory in your local development environment, and within it, use npmβs initializer command to create a package.json
file. If you use a package manager other than npm, consider adhering to the init
command provided by that specific package manager:
mkdir ts-node-express cd ts-node-express/ npm init -y
When initializing a package.json
file in this manner, the --yes
or -y
flag utilizes the default settings configured by npm, bypassing the repeated questions asking for project details. The resulting package.json
file might look similar to the one shown in the following image:
As the entry point of our application will be src/index.js
, which we will address in the upcoming sections, you should update the main
field in the package.json
file from index.js
to src/index.js
:
{ "name": "ts-node-express", "version": "1.0.0", "description": "", "main": "src/index.js", "scripts": { "test": "echo \"Error: no test specified\" && exit 1" }, ... }
After initializing the package.json
file, add the Express
and DotEnv
packages to the project. In the terminal window, run the following command, where npm i
is an alias to npm install
:
npm i express dotenv
The DotEnv
package is utilized to read environment variables from the .env
file. Instead of hardcoding environment-specific variables directly into the app, include them all in this file and utilize the DotEnv
package to manage them.
For instance, to specify the port number for your server, create a file named .env
in the root of the project directory. Inside this file, define an environment variable for PORT
and set its value to 3000
. Consider adding more environment-specific variables in this file in the future as needed:
# Add all of the environmental variables here instead of # embedding them directly in the app and utilize them # with the `DotEnv` package. PORT=3000
Then, create a directory called src
at the projectβs root to keep our application source files organized. Add a new file named index.js
to it and populate it with the following code, incorporating the previously defined environmental variable:
// src/index.js const express = require('express'); const dotenv = require('dotenv'); dotenv.config(); const app = express(); const port = process.env.PORT; app.get('/', (req, res) => { res.send('Express + TypeScript Server'); }); app.listen(port, () => { console.log(`[server]: Server is running at http://localhost:${port}`); });
The above code covers the essential steps to set up a minimal Express server using plain JavaScript. For a more detailed explanation, refer to the documented version of this snippet.
To launch the server, execute the command node src/index.js
in the terminal. This will execute the code that we just added to the index.js
and should start a new server, as illustrated below:
The Express server is now up and running, offering a foundational setup for development with Express on Node.js. Next, letβs enhance it by incorporating TypeScript in the next section.
We will begin by installing TypeScript as a development dependency. Additionally, weβll install the @types
declaration packages for Express and Node.js, which offer type definitions in the form of declaration files.
Declaration files, typically with the .d.ts
extension, serve as predefined modules that outline the structure of JavaScript values or the types present for the TypeScript compiler. These declaration files are available for libraries originally written in JavaScript, not TypeScript.
The DefinitelyTyped GitHub repository maintains the TypeScript type definitions for direct use in Node.js and other JavaScript projects, sparing you the effort of defining these types from scratch. To incorporate types or declaration files for a specific library or module, seek packages starting with the @types
namespace.
Launch the terminal and install the packages described above using the following command:
npm i -D typescript @types/express @types/node
The -D
or the --dev
flag directs the package manager to install these libraries as development dependencies.
Installing these packages will add a new devDependencies
object to the package.json
file, featuring version details for each package, as shown below:
{ ... "devDependencies": { "@types/express": "^4.17.21", "@types/node": "^20.10.3", "typescript": "^5.3.2" }, ... }
tsconfig.json
Every TypeScript project utilizes a configuration file to manage various project settings. The tsconfig.json
file, which serves as the TypeScript configuration file, outlines these default options and offers the flexibility to modify or customize compiler settings to suit your needs.
The tsconfig.json
file is usually placed at the projectβs root. To generate this file, use the following tsc
command, initiating the TypeScript Compiler:
npx tsc --init
Once you execute this command, youβll notice the tsconfig.json
file being created at the root of your project directory. This file contains the default compiler options, as depicted in the image below:
Upon opening the tsconfig.json
file, youβll notice several other commented-out compiler options. Among all these options, compilerOptions
is a mandatory field that must be specified. Hereβs a summary of all the default options that belong inside the compilerOptions
field:
target
: Enables the specification of the target JavaScript version that the compiler will outputmodule
: Facilitates the utilization of a module manager in the compiled JavaScript code, CommonJS is supported and is a standard in Node.jsstrict
: Toggles strict type-checking protocolsesModuleInterop
: Enables the compilation of ES6 modules to CommonJS modulesskipLibCheck
: When set to true
, bypasses type-checking of default library declaration filesforceConsistentCasingInFileNames
: When set to true
, enforces case-sensitive file namingOne crucial option you will need to enable is outDir
, which determines the destination directory for the compiled output. Locate this option in the tsconfig.json
file and uncomment it.
By default, the value of this option is set to the projectβs root. Change it to dist
, as shown below:
{ "compilerOptions": { ... "outDir": "./dist" ... } }
While there are probably other configuration options you can add to the TypeScript compiler, the options above are basic specifications that can help you get started.
You should now update the main
field in the package.json
file to dist/index.js
as the TypeScript code will compile from the src
directory to dist
.
.ts
extensionTransforming our JavaScript Express server code into TypeScript isnβt as complicated as it may seem. Begin by renaming the file from index.js
in the src
directory to index.ts
. The .ts
extension indicates a TypeScript file, and it will be compiled into JavaScript when we build the application later.
Now, open the index.ts
file and add the following modifications to make it TypeScript-compatible:
// src/index.js import express, { Express, Request, Response } from "express"; import dotenv from "dotenv"; dotenv.config(); const app: Express = express(); const port = process.env.PORT || 3000; app.get("/", (req: Request, res: Response) => { res.send("Express + TypeScript Server"); }); app.listen(port, () => { console.log(`[server]: Server is running at http://localhost:${port}`); });
No additional changes are made to the code except for including some TypeScript types. Refer to the documented version of the above code for a more detailed overview of whatβs happening.
Now, if you attempt to execute the index.ts
file using Node, similar to what we did with our index.js
file, you will encounter an error:
This is because Node doesnβt inherently support the direct execution of TypeScript files. The next section discusses running TypeScript files in the terminal using a Node package.
As previously discussed, executing a TypeScript file in Node is not supported by default. However, we can overcome this limitation by leveraging ts-node, a TypeScript execution environment for Node. Letβs first use ts-node with npx without installing it as a dependency and observe the output:
npx ts-node src/index.ts
As shown below, our index.ts
file executed successfully, and the server started running as expected:
The main advantage of using ts-node is that it eliminates the extra step of code transpilation and allows you to work with TypeScript code directly in a Node.js environment. It also comes in handy when working with standalone TypeScript files in the Node terminal.
To enhance the development workflow for Node.js projects, I often use nodemon, a utility library that automatically restarts a Node-based application upon detecting file changes in the specified directories.
Another useful package you might consider is concurrently, which facilitates the execution of multiple commands, such as nodemon, npx, tsc, etc., allowing you to combine different functionalities. However, for this simple app demonstration, we wonβt be incorporating it at this stage.
We will also install ts-node as a development dependency to further enhance the workflow. This way, nodemon automatically picks up ts-node to streamline the development process. Execute the following command to integrate nodemon and ts-node as development dependencies:
npm i -D nodemon ts-node
After installing these dev dependencies, update the scripts
in the package.json
file as follows:
{ "scripts": { "build": "npx tsc", "start": "node dist/index.js", "dev": "nodemon src/index.ts" } }
Referring to the added script modifications above, the build
command compiles the code into JavaScript and saves it in the dist
directory using the TypeScript Compiler (tsc). The dev
command is designed to run the Express server in development mode with the help of nodemon and ts-node.
Finally, return to the terminal window and execute npm run dev
to initiate the development server. It should show something like this:
There are no errors, confirming that the server is running successfully. As nodemon has identified changes, letβs try to edit the message sent from res.send()
while concurrently monitoring the terminal for any detected changes by nodemon:
Taking an extra step for a more refined setup, you may consider a nodemon.json
file in the project root, which serves as a configuration file for nodemon. This file lets you specify directories and extensions to watch and define commands to execute, while nodemon manages the reloading of the application upon changes:
{ "watch": ["src"], "ext": "ts", "exec": "concurrently \"npx tsc --watch\" \"ts-node src/index.ts\"" }
Itβs crucial to note that combining the TypeScript Compiler command in watch mode with ts-node or any other command, as demonstrated above in the nodemon configuration or with the bodemon command itself, may result in a loss of logging information.
This is due to both nodemon and TSC concurrently monitoring changes and potentially competing to display their respective logs on the screen.
In a TypeScript project, transpiling or building involves the TypeScript Compiler (TSC) interpreting the tsconfig.json
file to determine how to convert TypeScript files into valid JavaScript.
To compile the code, you must execute the command npm run build
. Upon successfully executing this command for the first time, a new dist
directory is created in the project root.
Within this directory, you will find the compiled versions of our TypeScript files in the form of valid JavaScript. This compiled JavaScript is essentially what is used in the production environment:
'use strict'; var __importDefault = (this && this.__importDefault) || function (mod) { return mod && mod.__esModule ? mod : { default: mod }; }; Object.defineProperty(exports, '__esModule', { value: true }); const express_1 = __importDefault(require('express')); const dotenv_1 = __importDefault(require('dotenv')); dotenv_1.default.config(); const app = (0, express_1.default)(); const port = process.env.PORT; app.get('/', (req, res) => { res.send('Express + TypeScript Server is running.'); }); app.listen(port, () => { console.log(`β‘οΈ[server]: Server is running at http://localhost:${port}`); });
If you designate any other directory as the value for the outDir
field in the tsconfig.json
file, that specified directory would be reflected here instead of dist
.
To improve this process further, set up TypeScript for reliability with strict type checking and configurations that adapt to your needs. Make the most of the tsconfig.json
file by specifying the best suitable production settings for your project. Improve performance with code splitting by utilizing tools like Webpack for efficiency and shrink file sizes with tools like Terser.
As the project expands, ensure code stability through automated testing with tools like Jest and streamline the workflow from development to production with CI/CD pipelines.
Using TypeScript has its benefits, but it does come with a bit of a learning curve. You have to carefully analyze whether using TypeScript in your Node.js and Express backend projects is beneficial or not, which may depend on the requirements of your project.
Feel free to fork GitHub repo for this project, point out any issues you noticed, and ask questions.
I hope you enjoyed this project! Be sure to leave a comment if you have any questions. Happy coding!
LogRocket is a frontend application monitoring solution that lets you replay problems as if they happened in your own browser. Instead of guessing why errors happen or asking users for screenshots and log dumps, LogRocket lets you replay the session to quickly understand what went wrong. It works perfectly with any app, regardless of framework, and has plugins to log additional context from Redux, Vuex, and @ngrx/store.
In addition to logging Redux actions and state, LogRocket records console logs, JavaScript errors, stacktraces, network requests/responses with headers + bodies, browser metadata, and custom logs. It also instruments the DOM to record the HTML and CSS on the page, recreating pixel-perfect videos of even the most complex single-page and mobile apps.
Let’s explore Nx features, use cases, alternatives, and more to help you assess whether it’s the right tool for your needs.
Explore the various security threats facing React Native mobile applications and how to mitigate them.
The warp web framework for Rust offers many enticing features. Let’s see when and why you should consider using warp in your projects.
In this tutorial, you’ll learn how to integrate Next.js and SignalR to build an enhanced real-time web application.
37 Replies to "How to set up TypeScript with Node.js and Express"
What is your terminal theme? The package and nodejs icon are really cool additions
Hey Jay, the theme is called Snazzy.
why compile?
So that the typescript files are converted to js as node cannot execute ts files
short and sweet
You saved my life π
What about that ‘media’ command early on in the article ?
It says :
media server
cd server/
Hey Gunnar,
Thanks for catching up that. It should mkdir — to create the server directory.
What’s the best command setup for deploying and running something like this in production? There are now compiled JS files in build/, but other files (e.g., package.json, static HTML, etc.) are all in their original locations. And, we haven’t created a script in package.json to run the built app.
okayy,, then how can i use other plugin to work with my express typescript. am i should install plugin that support in typescript or not ?
You would need something like WebPack to handle the build requirements.
Choose any middleware you need, but make sure DefinitelyTyped has the corresponding Typescript @types/* mappings available. Most popular NodeJS middleware packages are supported in the DefinitelyTyped project.
great article
I had to run npm install ts-node -g to install ts-node globally so that nodemon found it
Same here
Fix `https` to `http` in console.log() statement:
“””
console.log(`[server]: Server is running at https://localhost:${port}`);
“””
Hey, if you are running localhost, you do not require “https” until you want to intentionally use HTTPS.
You have a typo: `dotnev`
Can you point me towards which typo?
“outDir” should be “./dist”
In the article above, the “outDir” is referring to the config option. The “./dist” is the value.
Are you referring to something specific? Can you please point me to that?
instead of ‘/dist’, should be ‘./dist’ — otherwise it goes to machine root
Understood. Thanks for catching that Kelvin!
when rename `index.js` to `index.ts`, `package.json` `mian` field must change to `index.ts` too.
You just saved me. Thank you!
The `main` poperty in the `package.json` indicates the entry point and it should point out to Javascript compiled output. So it should be “main”:”dist/index.js” as we are using typescript here, if it was just just Javascript we can just use the index.js.
Thanks. Ok just so everyone is 100% clear. package.json should be changed to:
“main”: “dist/index.js”,
Excellent tutorial! One minor note is that the protocol should not be `https` since express is not secure by default, e.g, this line should be changed to just `http` — `console.log(`β‘οΈ[server]: Server is running at http://localhost:${port}`)`
This is a minor but crucial fix. Thanks for pointing it out!
had to remove “module”: “commonjs” from tsconfig.js as the error “ReferenceError: exports is not defined in ES module scope”
I had to remove `”type”: “module”,` from the `package.json` file to fix the issue you mention @Dey
thanks bro
anyone has a sample github repo for this? please include an example repo so we know where to place the files at. im guessing all the files should be in an dist folder?
https://github.com/D-Lite/express-ts
This is a starter template following the article above
Thanks for creating a starter template for this!
Nice. Antidote to the framework madness!
Thanks, this is really helpful!