Create an Express App with TypeScript
Learn how to create an Express application with TypeScript.
Table of Contents 📖
- Why TypeScript with Express?
- Create a Node Project
- Installing Express and TypeScript
- Configuring TypeScript
- Creating an Express App
- Creating Package.json Scripts
Why TypeScript with Express?
TypeScript is a programming language that adds static typing to JavaScript. This makes it easier to work with large JavaScript projects as errors are caught early. Therefore, using TypeScript to write Express applications can be beneficial. Express comes with a types npm package that contains all the type definitions for Express.
Create a Node Project
To begin, lets create a directory to hold a Node project. Lets name this directory express-and-typescript.
mkdir express-and-typescript
cd express-and-typescript
Now, to make this an ES6 npm project run the command npm init es6 -y.
npm init es6 -y
This command creates a package.json file with information about the npm project such as the name of the project, version number, and required dependencies. Specifying es6 allows us to use more modern ECMAScript import/export.
Installing Express and TypeScript
Next lets install Express with npm i express. Express is simply an npm package.
npm i express
Next lets install TypeScript. TypeScript is also an npm package. However, it should be installed as a development dependency as it is used for development, such as catching JavaScript errors early, and not out in production. We can install TypeScript with npm i typescript -D.
npm i typescript -D
Now lets install the Types package for Express. This is also an npm package that contains type definitions for Express. Here we will install it as a development dependency, but note that if this type appears in a declaration file then it should be installed as a regular dependency. We can install Express types with npm i @types/express -D.
npm i @types/express -D
Along with typings for Express, lets also install some typings for Node.
npm i @types/node -D
Configuring TypeScript
Next, we need to create a tsconfig.json file. The tsconfig.json file indicates the root of a TypeScript project and is used to configure the TypeScript compiler. We can create a tsconfig.json file using npx and tsc. Npx is an npm package runner that allows us to run executable JavaScript packages without installing them. Tsc stands for The TypeScript Compiler and it allows us to work with the TypeScript compiler. We can create a tsconfig.json file by using the command npx tsc --init.
npx tsc --init
Checking the contents of tsconfig.json, we can see the default configuration for our TypeScript compiler. Most of the contents of the file are commented out, uncommenting that line will enable that functionality. Note that most of the contents of tsconfig.json are ommitted here as there are a lot of options.
{
"compilerOptions": {
/* Language and Environment */
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
// "jsx": "preserve", /* Specify what JSX code is generated. */
...
/* Modules */
"module": "commonjs", /* Specify what module code is generated. */
// "rootDir": "./", /* Specify the root folder within your source files. */
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
...
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
...
/* Completeness */
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
"skipLibCheck": true /* Skip type checking all .d.ts files. */
}
}
Some examples of configuration options are setting outDir which specifies the output directory of the transpiled JavaScript files. For a demonstration, lets set our outDir to dist.
"outDir": "dist"
Now, when we transpile our TypeScript files they will be placed in a folder called dist. Therefore, we should update our package.json main key to point to the new index.js location.
"main": "./dist/index.js"
Lets also create a folder inside our TypeScript app called src.
mkdir src
Now lets use tsconfig.json to specify this folder as the location of our TypeScript files we want to compile. We can do this by using the top level key include.
"include": ["src/**/*"]
This tells the TypeScript compiler to compile every TypeScript file inside the src directory and its subdirectories. Now lets use tsconfig.json to specify the folder we want the TypeScript compiler to ignore. We want to ignore the node_modules folder and our output dist folder. We can ignore this folder using the top level exclude key.
"exclude": ["node_modules", "dist"]
Also, as we specified our project is using ECMAScript, we need to set the module key to NodeNext.
"module": "NodeNext"
The module key sets the module system for the transpiled JavaScript files. Setting it to NodeNext will allow us to transpile .mts files to .mjs files and .cts files to .cjs files. In other words, the transpiled files can either have ECMAScript import/export or CommonJS require/module.exports. We will be working with ECMAScript because we set the type key inside package.json to module when we ran npm init es6 -y.
Creating an Express App
Now all we need to do is create an Express application.
import express, {Request, Response} from 'express';
const app = express();
const PORT = 1234;
app.get('/', (req: Request, res: Response) => {
const headers = req.headers;
res.status(200).send('Hello world!');
});
app.listen(PORT, () => {
console.log(`Server listening on port ${PORT}`);
});
Notice the Intellisense that TypeScript provides while writing out code and how we can specify the Types of the request and response in the request handler.
Creating Package.json Scripts
Finally, lets create some scripts inside our package.json file that simplify working with this project. First, lets add clean command that removes the dist folder.
"scripts": {
"clean": "rm -rf dist"
},
Now lets add a build command that first runs clean, and then uses the npx tsc command to build a new dist folder.
"build": "npm run clean && npx tsc"
Now, running npm run build will build our project by converting our TypeScript code into JavaScript code using our tsconfig.json configuration. We should also add a command to run our project. To run this project, we will use the libraries nodemon and ts-node. Install them as development dependencies.
npm i nodemon ts-node -D
The ts-node library provides TypeScript execution and ECMAScript support. Nodemon is a library that automatically restarts Node applications when files are changed. We can use the exec option of nodemon to monitor other programs. We will use this functionality to have nodemon monitor our TypeScript application.
"start": "nodemon --exec node --loader ts-node/esm src/index.ts"
Here, we are running node with the ts-node/esm loader. This tells node to load modules with ts-node and ECMAScript and to register ts-node without using the CLI that ts-node provides. To run this command type npm start in the terminal. Any file changes will trigger a reload.
npm start
> start
> nodemon --exec node --loader ts-node/esm src/index.ts
[nodemon] 3.0.1
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `node --loader ts-node/esm src/index.ts`
(node:44756) ExperimentalWarning: Custom ESM Loaders is an experimental feature and might change at any time
(Use `node --trace-warnings ...` to show where the warning was created)
Server listening on port 1234
[nodemon] clean exit - waiting for changes before restart
Now send a curl to our Express server and look for the response.
curl localhost:1234
Hello world!