😁set up TypeScript Node.js and Express (build,start,dev,outDir)
https://blog.logrocket.com/how-to-set-up-node-typescript-express/
Chú ý: Để chạy lệnh import express from "express";
app.ts
Tôi cấu hình như sau
tsconfig.json
package.json
Part 1
package.json
src\index.js
.env
Part 2
package.json
src\index.ts
Editor’s note: This article was updated by David Omotayo on 7 November 2024 to cover setting up path aliases using tsconfig.json
.
Creating a server with TypeScript using Node.js and Express is a good alternative to using JavaScript because it makes it easier to manage complex applications and helps when you need to collaborate with a distributed team of developers.
TypeScript offers benefits like:
Improved code strength and clarity when static typing
Enhanced collaboration and project scalability
Advanced tooling
IDE support
Broad compatibility
All of these benefits make TypeScript 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, as well as gain an understanding of the fundamental constraints that accompany it. To follow along, you should have:
Node.js ≥ v18.x installed in your local development environment
Access to a package manager like npm, pnpm, or Yarn
Basic familiarity with Node.js and Express
Check out the GitHub repository for the source code; the main branch has the TypeScript project, and the JavaScript branch has the JavaScript version.
Creating a package.json
file
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:
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:
Because 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
:
Creating a minimal server with Express
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
:
The DotEnv
package is used to read environment variables from a .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:
Then, create a directory called src
at the project’s root to organize our application source files. Add a new file named index.js
to it and populate it with the following code, including the previously defined environmental variable:
The above code covers the essential steps for setting up a minimal Express server using plain JavaScript. For a more detailed explanation, refer to the documented version of this snippet.
To start 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
file 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.
Installing TypeScript
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 denoted 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:
The -D
, or --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:
Generating the TypeScript configuration file: tsconfig.json
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:
Once you execute this command, you’ll notice the tsconfig.json
file is 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 of 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 totrue
, bypasses the type checking of default library declaration filesforceConsistentCasingInFileNames
: When set totrue
, enforces case-sensitive file naming
One 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:
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
because the TypeScript code will compile from the src
directory to dist
.
Creating an Express server with a .ts
extension
.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:
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.
Running TypeScript in Node with ts-node
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:
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.
Watching file changes
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. In this section, we’ll explore how concurrently can be used alongside nodemon to improve workflow.
Because nodemon doesn’t work with TypSscript files out of the box, we will also install ts-node as a development dependency. This ensures nodemon automatically picks up ts-node to hot reload the Node server when changes are made to TypeScript files, streamlining the development process.
Execute the following command to integrate nodemon and ts-node as development dependencies:
After installing these dev dependencies, update the scripts
in the package.json
file as follows:
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:
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 nodemon 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.
Building or transpiling the TypeScript files
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
. A new dist directory is created in the project root after successfully executing this command for the first time. 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:
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.
Setting up a path alias using ts.config
ts.config
Another option you might want to consider configuring in the tsconfig.json
file is the path
and baseUrl
options. The path
option allows you to define aliases, while the baseUrl
specifies the base directory for module resolution.
With these options, you can simplify import statements by defining custom paths or shortcuts for directories in your projects. This is particularly useful when managing large, complex codebases where long, complex relative paths can be replaced with short, easy-to-read ones:
Setting up a path alias is fairly straightforward, all you have to do is add or uncomment the baseUrl
and path
options in the tsconfig
file and add the following modifications:
In this example, I’ve set up two paths: services
and utils
, which contain the files userServices.ts
and logger.ts
, respectively.
In the excerpt above, we set the root directory as the base directory from which relative paths in import statements are resolved by using "./"
. You can set this to any directory depending on your project structure. In the path option, we define aliases that point to the specified directories, where @services
points to src/services
, and @utils
points to src/utils
.
Now, instead of using long relative paths to import userServices.ts
and logger.ts
in src/index.ts
:
We can use the aliases specified in the paths
option like so:
However, if you try to start the development server, you’ll get the following error:
This happens because Node.js doesn’t natively understand TypeScript path aliases, so the paths in tsconfig.json
aren’t recognized during runtime. To resolve those aliases, you need to install a runtime package like tsconfig-paths
.
Execute the following command in your terminal to install tsconfig-paths
:
Then, modify your dev
script in the package.json
file like this:
The -r tsconfig-paths/register src/index.ts
flag tells Node.js to use tsconfig-paths
to resolve aliases defined in the tsconfig
file. After saving the code and running the dev
script again, the server should spin up without issues:
Note: If you’re using nodemon in watch mode, you need to update the exec
script and add the -r tsconfig-paths/register src/index.ts
flag in the nodemon.json
file to avoid encountering the same error as before.
Next, we need to make sure the transpiler correctly resolves the TypeScript imports to relative paths in the JavaScript file in dist/index.js
. For example, after transpiling, the userServices
and log
imports should look like this in the compiled JavaScript:
This happens because the transpiler doesn’t automatically resolve path aliases, so they stay unchanged in the JavaScript output file:
This will cause the same error as before when you run the start
script, as the JavaScript runtime doesn’t recognize the path aliases.
To fix this, we’ll use a third-party tool like tsc-alias
to replace path aliases with relative paths after TypeScript is compiled. Go back to your terminal and install the package with the command below:
Once the installation is complete, open the package.json
file and update the build
script as follows:
This command ensures the TypeScript compiler follows the configuration provided in your tsconfig.json
file and rewrites the path aliases in the generated JavaScript files.
With these changes, the next time you run the build
script, the transpiler will correctly resolve the TypeScript aliases to relative paths, and the error should cease:
Conclusion
In this guide, we explored how to set up TypeScript with Node.js and Express, focusing on configuring key elements for a smooth development experience. We created a server, configuring ts-node, and using nodemon for hot reloading to streamline the workflow.
Additionally, we discussed setting up TypeScript path aliases using tsconfig.json
to simplify complex imports, a useful feature when working with large codebases. Finally, we covered how to use tsconfig-paths
and tsc-alias
to ensure path aliases are properly resolved during runtime. These tools — ts-node, nodemon, and TypeScript path aliases — can significantly enhance your Node.js development workflow.
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 the GitHub repo for this project, point out any issues you noticed, and ask questions. Happy coding!
LogRocket: Full visibility into your web and mobile apps
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.
Last updated