How to set up an express server with typescript.
When you're developing a server using Node.js and Express, the development experience is usually smooth and efficient. However, additional challenges may arise as your application scales or when collaborating with a globally distributed team of developers. This is where TypeScript can come to the rescue.
In this tutorial, we will explore an approach suitable for beginners to set up TypeScript in an Express application, understanding the underlying limitations and benefits it offers.
To make the most of this tutorial, ensure that you have the following prerequisites:
Node.js version ≥ v12.x installed on your local development environment
Access to a package manager such as npm or Yarn
Basic understanding of Node.js and Express concepts
By following this tutorial, you can leverage TypeScript's advantages in terms of code maintainability, improved collaboration, and catching errors at compile time to enhance your Express development workflow.
Create a package.json file.
Start by creating a new directory wherever you keep your side projects in your local development environment. Inside that directory, use yarn or npm’s initializer command to create a package.json
file:
mkdir express-ts
cd express-ts/
yarn init -y
If you prefer npm
, then do this:
npm init -y
When you initialize a package.json
file, the -y
flag uses the default settings you have set up from yarn config. The newly created package.json
file might look something like the following code:
{
"name": "express-typescript",
"version": "1.0.0",
"description": "Setting up express server with ts",
"main": "index.js",
"scripts": {
"test": "echo \\"Error: no test specified\\" && exit 1"
},
"type": "commonjs",
"keywords": [],
"author": "",
"license": "ISC"
}
Change the "type"
in the package.json
from module to commonjs
.
Install dependencies**.**
After initialising the package.json
file, let’s add an Express package. From the terminal window, run the command below:
yarn add express dotenv
npm i express dotenv
Installing TypeScript
We’ll install TypeScript as a development dependency in our project. Additionally, we'll install the @types
declaration packages for Express and Node.js, which provide type definitions as declaration files.
Declaration files serve as pre-defined modules that describe the structure and types of JavaScript values for the TypeScript compiler. These declaration files typically have a .d.ts
extension and are available for JavaScript libraries that don't natively include TypeScript support.
The official DefinitelyTyped GitHub repository maintains a collection of TypeScript type definitions for various libraries and modules. By leveraging these declaration files, you can avoid manually defining types from scratch.
To install the necessary packages, execute the following command in your terminal:
yarn add -D typescript @types/express @types/node
npm i --save-dev typescript @types/express @types/node
This command will install TypeScript as a development dependency and include the @types
declaration packages for Express and Node.js.
By setting up TypeScript and importing the appropriate type definitions, you'll benefit from enhanced code analysis, autocompletion, and catch potential type errors during development in your Express application.
The -D
flag, also known as the --dev
flag, is a specification for the package manager to install these libraries as devDependencies
.
Once these libraries are installed, go to the package.json
file where you’ll see a new devDependencies
object:
{
"devDependencies": {
"@types/express": "^4.17.17",
"@types/node": "^20.4.2",
"typescript": "^5.1.6"
}
}
Generating tsconfig.json
Now, we have a TypeScript project compiled with some default configuration options. The tsconfig.json
file provides these default options and allows us to tweak or customize the compiler options.
Typically, the tsconfig.json
file lives at the root of the project. To generate it, we’ll use the tsc
command:
npx tsc --init
The command above will generate a new file called tsconfig.json
with the following default compiler options:
target: es2016
module: commonjs
strict: true
esModuleInterop: true
skipLibCheck: true
forceConsistentCasingInFileNames: true
If you open the tsconfig.json
file, you’ll see many other compiler options that are commented out. In tsconfig.json
, compilerOptions
is a mandatory field that needs to be specified. The options used in the config above are:
target
: Allows us to specify the target JavaScript version that the compiler will outputmodule
: Allows us to use a module manager in the compiled JavaScript code. CommonJS is supported and is a standard in Node.jsstrict
: An option that enables strict type-checking optionsesModuleInterop
: Allows us to compile ES6 modules to CommonJS modulesskipLibCheck
: If set totrue
, skips type-checking of default library declaration filesforceConsistentCasingInFileNames
: When set totrue
, enables case sensitive file naming
One option you will have to enable is called outDir
, which specifies where the output will be located after the compilation step. You can search for this option in the tsconfig.json
file and uncomment it.
By default, the value of this option is set to the root directory. Change it to dist
:
{
"compilerOptions": {
"outDir": "./dist"
// rest options remain same
}
}
If you wish to set a directory as your rootDir
, add an additional field besides the compilerOptions
, thus the include
option.
{
"compilerOptions": {
// everthing remains here
},
"include": ["/src"]
}
After adding include
field, let me create the src
dir in my root dir.
While there are probably other configuration options you can add to the TypeScript compiler, the options listed above are basic specifications that can help you get started.
Create an Express server with a .ts extension
Now, inside our src
dir, let’s create anindex.ts
file and create our server.
Open the index.ts
file and type this inside it, as shown below:
import express, { Express, Request, Response, NextFunction } from 'express';
import dotenv from 'dotenv';
dotenv.config();
const app: Express = express();
const port = process.env.PORT;
app.get('/', (req: Request, res: Response, next: NextFunction) => {
res.send('Hello World!!');
});
app.listen(port, () => {
console.log(`[⚡]: Server is running at <https://localhost>:${port}`);
});
DevDependencies I call helpers.
Another development-related utility library that is very useful when working on Node.js projects is nodemon. nodemon is a tool that helps develop Node.js-based applications by automatically restarting the Node.js application when file changes in the directory are detected.
We’ll also install another dev dependency called Concurrently, which will allow us to run multiple commands like nodemon to watch file changes and the tsc
command to compile the code:
yarn add -D concurrently nodemon
npm install -D concurrently nodemon
After installing these dev dependencies, update the scripts
in the package.json
file:
{
"scripts": {
"build": "npx tsc",
"start": "node dist/index.js",
"dev": "concurrently \\"npx tsc --watch\\" \\"nodemon -q dist/index.js\\""
}
}
The build
command will compile the code in JavaScript inside a dist
directory. The dev
command is used to run the Node.js server in development mode.
Now, go back to the terminal window and run npm run dev
to trigger the development server:
There are no errors, indicating that the server is successfully running. You can go to the browser window to see the result.
Since nodemon detects changes, you can test it by changing the message sent from res.send()
and simultaneously take a look at the terminal to see if nodemon detects any file changes or not:
To compile the code, you need to must command npm run build
. There is a new dist
directory created after this command executes successfully. Inside this directory, the TypeScript code is compiled to valid JavaScript:
"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 || 3000;
app.get('/', (req, res, next) => {
res.send('Hello World!');
});
app.listen(port, () => {
console.log(`[⚡]Server is running at <http://localhost>:${port}`);
});
If you specify any other directory as the value of the property outDir
in the tsconfig.json
file, that directory would be reflected here instead of dist
.
Conclusion
In conclusion, we have reached the end of this tutorial on building an Express server with TypeScript. I hope you found this guide helpful in understanding how to set up TypeScript in your Node.js and Express backend projects.
TypeScript brings several benefits, including improved code maintainability, enhanced collaboration among team members, and catching potential errors at compile time. While it requires a learning curve and some initial setup, its advantages can be valuable, especially for larger projects or teams.
When deciding whether to use TypeScript in your Node.js and Express projects, carefully consider your application's specific requirements and complexity. TypeScript may be particularly beneficial when you have a large codebase, multiple developers working on the project, or need robust type checking and documentation.
As you continue your journey with TypeScript and Express, I encourage you to explore additional TypeScript features, such as decorators, interfaces, and generics, to enhance your development experience further.
Remember to stay current with the TypeScript and Express communities, as new features and improvements are constantly being introduced. Leverage online resources, official documentation, and community forums to expand your knowledge and tackle any challenges you may encounter.
Thank you for following along with this tutorial. We hope you found it insightful and wish you success in your future endeavours in building express servers with TypeScript!