Node.js and Express.js with TypeScript

Amir Mustafa
11 min readAug 11, 2021

--

Server-side JavaScript has grown very popular in recent times and is mostly used with Node.js or Express.js with JavaScript.

The development of Node.js has marked JavaScript as an emerging server-side technology.

Node.js is an open-source, cross-platform, JavaScript runtime environment that executes JavaScript code outside a web browser.

TypeScript is an open-source language that builds on JavaScript, tools, by adding static type definitions. It has many additional features and run-time checks which are used in many projects.

We will understand how we can use Node with TypeScript easily.

At the end of this blog, we will know how to write TypeScript with Node.js and Express.js

Prerequisites:

Basic familiarity of Node.js and Express.js

Installation

npm install -g npm                  // node install
npm install typescript --save-dev // typescript install

→ Let us create a new project directory and create an app.ts file and write some code on the page.

src/app.ts

console.log(‘Something Went Wrong’);
Regular JS code in .ts file works fine

For executing node.js script, we write

node app.ts

From the above screen, we see, console successfully prints fine in the terminal.

Q. Is Node JS compatible with TypeScript?

Let us check by writing basic TypeScript code on the same page

src/app.ts

var age:number;age = 25;console.log(age);
TypeScript code has thrown an error

Node throws an error in the terminal. From the above screen, it is clear, that Node.js is not compatible with TypeScript directly.

Let us execute the TypeScript running the below command.

tsc app.ts

An app.js code will be generated.

→ JavaScript file runs fine.

→ This is really important to understand Node does not execute TypeScript.

→ There is a package that will help Node run with TypeScript, in the background all compilation will happen

→ This will combine tsc and node in one step

Package Name: ts-node

Package: https://www.npmjs.com/package/ts-node

GitHub: https://github.com/TypeStrong/ts-node

→ This package might be nice in development, but for production for really serving your file in web server this way is not ideal because having this extra compilation step every time code executes adds overhead which will cost optimization.

→  So here we will see node-express setup with Typescript compiler and Vanilla Node JS.

Setting up Node and TS environment:

  1. Node environment:

Open the project in the terminal and execute the below commands.

npm init --yes    // creates package.json file
npm install express body-parser nodemon // dependent packages
Node — package.json

2. TypeScript environment:

tsc --init.    // creates tsconfig.ts file

tsconfig.json is the heart of the TypeScript file

We need to make the below configurations:

{  "compilerOptions": {    "target": "ES2018",                                "module": "commonjs",                              "moduleResolution": "node",    "outDir": "./dist",  /* js files lives here */    "rootDir": "./src",  /* ts file resides here */}

moduleResolution: “node”

By default when we run tsc command, its compiled js file is created in the same place. Standard practice is having ts and js codes in separate directories

i.e.

src — will contain all ts codes.

dist — will contain js code

tsconfig.ts

 → TypeScript code will be written in the src directory and after compilation, js code will be generated in the dist directory.

 → Let us now write the first node code in TypeScript:

src/app.ts (root file)

const express = require('express');const app = express();app.listen(3000);  // PORT

package.json is the heart of the Node.js file for handling packages.

→ After writing node.js code, TypeScript will throw errors when we run tsc -w command. Observe once above screenshot.

→ This is because require is used in node environment export/import. For typescript, we need to install definitely typed package.

Definitely types package is a typescript type-defined package. The code which works in vanilla js will also work here. We need to install two packages:

npm install @types/nodenpm install @types/express

If you want to know more on @types in TypeScript, you can visit here.

After installation, require error is gone.

Error is gone

→ There is one point to note, when we hover on app variable, its inferred type syntax is :any which will not be best understood by TypeScript for express.js

→ So one approach is instead of node.js’s require syntax, we will use ES6 modular syntax which TypeScript understands better.

Now app variable infers :Express as the type, which will work smoothly with Node and TypeScript

TRICK: When using Node and typescript together, ,always use ES6 approach for code splitting and modularization.

→ In the package.json file, we will add nodemon script. Nodemon is used for starting node server which need not require, server restart on every code change.

"scripts": {  "start": "nodemon dist/app.js"},

To start the server:

tsc --watch      // this starts TypeScript compiler
npm start // this starts Node server

Let us run the url in browser http://localhost:3000/

Getting this error indicates Node server is running

Creating ToDo App using RESTful API:

→ In this application, we will build a Todo application in Node.js, express.js and TypeScript.

→ We have already installed the express package above. Hope this app will be fun.

→ We will be using an array for storing data(and not database). The idea is we will write TypeScript with node.js. Connecting databases may complicate at present.

Let us begin the development.

STEP1: Creating routes endpoint. We will write the logic of routes below by creating controllers.

Create new directory routes as followed by todos.ts file. This is the routes file.

src/routes/todos.ts

import { Router } from 'express';// Old node way// const express = require('express');// const Router = express.Router;const router = Router();router.post('/');router.get('/');router.patch('/:id');router.delete('/:id');export default router;

NOTE: No routes controller is added at present, we will do it soon below.

STEP2: Connecting routes/todos.ts with app.ts

src/app.ts

import express, {Request, Response, NextFunction} from 'express';import todoRoutes from './routes/todos';   // Route connectedconst app = express();app.use('/todos', todoRoutes);// This means all route path preceed this path
// Below route is trigerred when any error is is thrown
app.use((err: Error, req: Request, res:Response, next: NextFunction) => { res.status(500).json({message: err.message});});app.listen(3000);

STEP3: Controllers

→ Here we will write the business logic of routes i.e. what should that route do. Let us create a new directory that will refer to the routes

→ Step by step we will create a controller per route

src/controllers/todos.ts

import { RequestHandler } from 'express';  export const createTodos:RequestHandler = (req, res, next) => {};

NOTE: RequestHandler is quick alternate to

((err: Error, req: Request, res:Response, next: NextFunction) which comes from Definitely Typed

→ The core logic will be simple, we have an array whose data will decide the to-do list.

→ Let us create an empty array (say todos)

→ From TypeScript, we know all classes can be used as types in variables. So let us create the blueprint of the array (i.e. variable: class). Blueprint will be in models

It will be a basic class that will accept the id and text of the type string.

src/models/todos.ts

export class Todo {   constructor(public id: string, public text: string) {}}

We have used a shorter syntax method, where id and text will be instantiated and assigned to the same name.

Alternate way (Regular way):

export class Todo {  id: string;  text: string;  constructor(id:string, text: string) {    this.id = id;    this.text = text;  }}

→ We will be creating handling four routes logics— CRUD operation i.e. createTodos, getTodos, updateTodos and deleteTodos

ROUTE1: createTodos route

Route name: createTodos

Request type: POST,

Controller: createTodos

This route is a POST request, where we will handle insert operation.

STEP1: MODEL

src/models/todos.ts

This is common for all four routes. It tells TODO array will have data in {id: __, text: __} format.

export class Todo {  constructor(public id: string, public text: string) {}}

STEP2: CONTROLLERS

src/controllers/todos.ts

In this controller text is received from the browser/postman and id is uniquely generated.

import { RequestHandler, response } from 'express';import { Todo } from '../models/todos';const TODOS: Todo[] = [];export const createTodos:RequestHandler = (req, res, next) => {  const text = (req.body as { text:string } ).text; // making it  string type  // const text = req.body.text; //typeScript is not allowing :any type  const id = Math.random().toString();  const newTodo = new Todo(id, text);  TODOS.push(newTodo);  res.status(201).json({message: 'Todo created successfully',   createdTodo: newTodo});};

createTodos is a function of type RequestHandler. Two request input will be received:

a. text — this will be received from request (in JSON format). This is the text of to do task

b. id — unique data. This can be done in many ways. For this project, we will use the basic Math.random() built-in function of JavaScript.

→ The next step is to connect this controller with the route.

STEP3: ROUTES

We will connect the controller with routes.

src/routes.todos.ts

import { Router } from 'express';import { createTodos } from '../controllers/todos';router.post('/', createTodos);   // CREATE Routeexport default router;

STEP4: REGISTERING JSON REQUEST:

→ The final step for the route is registering JSON in app.ts so that it can receive JSON requests from browser/postman.

→ This is done once for all the routes.

src/app.ts

import express, {Request, Response, NextFunction} from 'express';......const app = express();app.use(json());  // registering this middleware for accepting json requestsapp.use('/todos', todoRoutes); // All route must preceed with this path
...
app.listen(3000);

→ All the routes will start with <host path>/todos/<route_name> (written in line 8 above screenshot).

→ As this route is post request, we will use postman. Postman has capabilities to accept all types of requests — post, get, patch, get

STEP5: RUNNING REQUEST IN POSTMAN

→ On hitting the above URL, we have successfully saved the data.

Response is Successful

Data for postman:

url: http://localhost:3000/todos/body:{
"text": "Tech meetups" // Your ToDO data to save
}
// body type raw in json format

→ Create Route development is done. Now to check all the saved data, we will start with getRoute.

ROUTE2: getTodos route

Route name: getTodos

Request type: GET,

Controller: getTodos

This route is a GET request, where we will handle read operation. Let us create the controller for this route.

STEP1: MODELS

This is the same as above

STEP2: CONTROLLERS
src/controllers/route.ts

Get controller will return all the data saved in the Todo array.

export const getTodos:RequestHandler = (req, res, next) => {   res.status(200).json({"todos": TODOS});};

STEP3: ROUTES

We will connect the controller with routes.

src/routes/todos.ts

router.get('/', getTodos);

STEP4: RUNNING REQUEST IN POSTMAN

→ On hitting the above URL, we have successfully fetch all the saved data.

ROUTE3: updateTodos route

Route name: updateTodos

Request type: PATCH,

Controller: updateTodos

This route is a PATCH request, where we will handle update operations. For this request, we need to pass the unique id, which from get route for updating.

Let us create the controller for this route.

STEP1: MODELS

This is the same as above

STEP2: CONTROLLERS

src/controllers/todos.ts

In the postman, we will pass id whose data need to be updated. We receive id from the URL and new text to update from the request body.

export const updateTodos: RequestHandler<{id: string}> = (req, res, next) => {  const todoId = req.params.id;  const updatedText = (req.body as { text:string }).text;  const todoIndex = TODOS.findIndex(x => x.id === todoId);  if(todoIndex < 0) {    throw new Error('Could not find todo!');  }  TODOS[todoIndex] = new Todo(TODOS[todoIndex].id, updatedText);  res.status(201).json({message: 'Todo updated successfully', updateTodo:  TODOS[todoIndex]});};

STEP3: ROUTES

We will connect the update controller with routes.

src/routes/todos.ts

router.patch('/:id', updateTodos);

STEP4: RUNNING REQUEST IN POSTMAN

Development for update route is done let us test in postman

Get request — taking id whose data need to update
Update request — passing id in url to update and data to update in body
Get Request — Running it again to see the updated result

ROUTE4: deleteTodos route

Route name: deleteTodos

Request type: DELETE,

Controller: deleteTodos

Delete operation is similar to update request, with some differences.

STEP1: MODELS

This is the same as above

STEP2: CONTROLLERS

Delete controller will find the id passed and remove it from the Todo array.
src/controllers/todos.ts

export const deleteTodos: RequestHandler<{id: string}> = (req, res, next) => {  const todoId = req.params.id;  const todoIndex = TODOS.findIndex(x => x.id === todoId);  if(todoIndex < 0) {    throw new Error('Could not find todo!');  }  console.log( TODOS[todoIndex].text, typeof todoIndex );  const deletedData = {id: TODOS[todoIndex].id, text:   TODOS[todoIndex].text};
TODOS.splice(todoIndex, 1);
res.status(201).json({message: `${deletedData.id}: ${deletedData.text} deleted successfully`})}

STEP3: ROUTES

We will connect the update controller with routes.

src/routes/todos.ts

router.delete('/:id', deleteTodos);

STEP4: RUNNING REQUEST IN POSTMAN

Let us execute our delete route.

There is another package with which we set up Node with TypeScript i.e. Nest.js. Try exploring them as well.

Summary:

We have learnt two important concepts:

a. Way to write Node.js and Express.js with TypeScript.

b. Way to use 3rd party vanilla JS packages in TypeScript i.e. @types/node, @types/express.

Github:

Closing thoughts:

Node.js and TypeScript independently are so powerful. Many new projects are moving to TypeScript because of its rich features.

I hope you learnt something new today.

Thank you for being till the end 🙌 . If you enjoyed this article or learned something new, support me by clicking the share button below to reach more people and/or give me a follow on Twitter to see some other tips, articles, and things I learn and share there.

--

--

Amir Mustafa
Amir Mustafa

Written by Amir Mustafa

JavaScript Specialist | Consultant | YouTuber 🎬. | AWS ☁️ | Docker 🐳 | Digital Nomad | Human. Connect with me on https://www.linkedin.com/in/amirmustafa1/