Getting Started with NodeJS Express using TypeScript

Shaikh Zaki Mohammed
7 min readMar 21, 2021

TypeScript teaches some manners to write JavaScript code. A well-written JavaScript code (TypeScript) grabs much attention in the party; so why not simply include TypeScript to write Nodes Express APIs? Great idea! And both NodeJS and Express play very well with this decision.

Install and initialize Typescript

Without wasting much time will jump-start with this one. Lets us first globally install TypeScript if not already. The following command will help us to do so.

npm i -g typescript

Create a project folder where you want to start painting NodeJS Express with TypeScript. We have created a folder named tsc-express-app. Now let us initialize typescript configuration using the following command inside your project folder.

This will generate a configuration file named tsconfig.json. Update the file and add the following properties to the compilerOptions property if not present already.

{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"outDir": "./dist",
"rootDir": "./src",
"strict": true,
"moduleResolution": "node",
"esModuleInterop": true,
"forceConsistentCasingInFileNames": true,
"resolveJsonModule": true
}
}

Here we are specifying our rootDir as “src” folder which will hold the TypeScript files and the JavaScript files which will be generated after building the project will be stored in “outDir” folder.

Initialize NPM

Now initialize the project using npm initialize command as follows inside your project folder.

After initializing the project with default information we will start adding packages to our project.

Install dependencies

Add the following dependencies and dev-dependencies to your project.

npm i expressnpm i -D typescript
npm i -D ts-node-dev
npm i -D @types/node
npm i -D @types/express

The -D above is for dev-dependencies. The dependencies in package.json will be as follows after running the above command.

{
"dependencies": {
"express": "^4.17.1"
},
"devDependencies": {
"@types/express": "^4.17.4",
"@types/node": "^13.9.8",
"ts-node-dev": "^1.0.0-pre.60",
"typescript": "^3.8.3"
}
}

Here we have installed express as dependencies while the other packages as dev-dependencies, the reason behind this is that the dev-dependencies packages are only required for development and not when the project got build or deployed. So this includes typescript too.

The @types/* packages are the packages that are responsible for providing meta information for packages. There are many @types/* packages are available for most of the packages. So in order to avoid TypeScript errors when running the project, we must add these @types/* packages to our dev-dependencies. In our case, we are using only express and node so we added @types/node and @types/node.

The process of working with TypeScript involves building the project and then executing it. This will be done with the help of 2 sets of commands shown below:

For building the project (transpiling from TypeScript to JavaScript): For running the server:

node dist/server

In order to speed up our development time, we have added ts-node-dev package. It does transpile our TypeScript code to JavaScript and place them into dist folder and execute our project immediately when we do any TypeScript code changes. It simply provides a watch feature to our TypeScript code so we can see changes on the fly. The command for this package is as follows:

ts-node-dev --respawn --transpile-only ./src/server.ts

For simplifying things, we have created our own set of NPM commands to provide some ease in doing common operations like build, start and watch.

"scripts": {
"start": "node dist/server",
"build": "tsc -p .",
"watch": "ts-node-dev --respawn --transpile-only ./src/server.ts"
}

Here the start will be used to run the JavaScript file which can be obtained after executing the build command. Or we can avoid these 2 steps by directly executing the watch command which will run the server and watch for any TypeScript code changes in the background. The combination of such commands are shown below:

npm run build
npm start
// or
npm run watch

Setting up project

Below shows the folder structure of the project.

tsc-express-app
|-- dist
|-- node_modules
|-- src
|-- data
|-- employees.json
|-- models
|-- employee.ts
|-- routes
|-- employees.ts
|-- server.ts
|-- package.json
|-- tsconfig.json

The dist folder will be generated once we run the npm run build command. The node_modules holds the dependencies. The src folder contains the TypeScript files. The data folder holds the static employee data in .json format, which will act as a data source for the current example. The server.ts file will start the server while the routes/employees.ts file defines the routes for employee REST API endpoints. The model here defines the data type Employee for each object present in employees.json file.

Starting the server and creating API endpoints

Will first create the Employee model named models/employee.ts file using TypeScript interface as shown below.

export interface Employee {
Id: number;
Name: string;
Job: string;
Department: string;
Code: string;
}

Create the API endpoints for employees inside the routes folder. The routes/employees.ts file is shown below.

import express, { Router, Request, Response } from 'express';
import { Employee } from '../models/employee';
import employeesJson from './../data/employees.json';
const router: Router = express.Router();
const employees = employeesJson as Employee[];
// GET: api/employees
router.get('/', async (req: Request, res: Response) => {
try {
res.json(employees.sort((a, b) => b.Id - a.Id));
} catch (error) {
res.status(500).json(error);
}
});
// GET: api/employees/:id
router.get('/:id', async (req: Request, res: Response) => {
try {
const employee = employees.find(i => i.Id == +req.params.id);
if (employee) {
res.json(employee);
} else {
res.status(404).json({
message: 'Record not found'
});
}
} catch (error) {
res.status(500).json(error);
}
});
// POST: api/employees/
router.post('/', async (req: Request, res: Response) => {
try {
const employee = req.body as Employee;
employee.Id = Math.max(...employees.map(i => i.Id)) + 1;
employees.push(employee);
res.json(employee);
} catch (error) {
res.status(500).json(error);
}
});
// PUT: api/employees/:id
router.put('/:id', async (req: Request, res: Response) => {
try {
const index = employees.findIndex(i => i.Id === +req.params.id);
const employee = employees[index];
if (employee) {
employees[index] = { ...employee, ...(req.body as Employee) };
res.json(employees[index]);
} else {
res.status(404).json({
message: 'Record not found'
});
}
} catch (error) {
res.status(500).json(error);
}
});
// DELETE: api/employees/:id
router.delete('/:id', async (req: Request, res: Response) => {
try {
const index = employees.findIndex(i => i.Id === +req.params.id);
const employee = employees[index];
if (index !== -1) {
employees.splice(index, 1);
res.json(employee);
} else {
res.status(404).json({
message: 'Record not found'
});
}
} catch (error) {
res.status(500).json(error);
}
});
module.exports = router;

Let us talk about the imports first, here we are importing the default express object along with classes like Router, Request, and Response. The meta definition for these classes are visible due to the dev-dependency @types/express. Next we will include the Employee interface from the model folder. Finally, we will import the employees.json data and for importing the .json file like this we have added “resolveJsonModule”: true property in the tsconfig.json file; otherwise, it will show an error for importing .json file like below.

import express, { Router, Request, Response } from 'express';
import { Employee } from '../models/employee';
import employeesJson from './../data/employees.json';

We then created a router object using express.Router() method and obtained employees array from employees.json file. We have specified the type as Employee[] which will indicate the type of employees constant.

const router: Router = express.Router();
const employees = employeesJson as Employee[];

We have then added endpoints for REST methods GET, POST, PUT and DELETE through which we can perform CRUD operations. Let us finally create the server to define the entry point for the application. The server.ts file is shown below.

import express, { Application } from 'express'

const app: Application = express();

const PORT = process.env.PORT || 3000;

app.use(express.json());
app.use(express.urlencoded({ extended: false }));

app.use('/api/employees', require('./routes/employees'));

app.listen(PORT, () => {
console.log(`Server started running on ${PORT}`);
});

We have defined port 3000 and also our routes for the employee API endpoints.

Running the project

As discussed earlier we can run the project using either build and start command or watch. We can try both.

npm run build
npm start

For build and start, we need to, again and again, run these 2 commands whenever we change our code. So for this reason we can go with the watch command which will auto-detect change and build-run immediately for us.

npm run watch

NOTE: Once we are done with our development in order to deploy the project to production we must have to supply the dist folder which is generated after building the project (npm run build).

Additionally, we can hit the endpoints using the api-spec.http file present in the project root folder. If we open the project inside Visual Studio Code and installed the extension REST Client then we can hit those endpoints from api-spec.http file.

A more detailed explanation about NodeJS, ExpressJS with TypeScript is explained in the article (link: Building a Node.js/TypeScript REST API, Part 1: Express.js); this reading highlights the healthy typescriptive way of registering routes and maintaining the project structure.

Git Repository

Check out the git repository for this project or download the code.

Download Code Git Repository

Summary

By introducing TypeScript to NodeJS we can write our JavaScript code in a controlled way. Many NodeJS packages provide support for TypeScript and have their own @types/* packages. These will provide intellisense, type safety, and strict mode for writing JavaScript code.

Hope this article helps.

Originally published at https://codeomelet.com.

--

--

Shaikh Zaki Mohammed

Learner, developer, coder and an exceptional omelet lover. Knows how to flip arrays or omelet or arrays of omelet.