TypeScript Section 15 – Using Node.js & Express with TypeScript


TypeScript

Updated Mar 14th, 2022

Table of Contents

Chapter Details

Module Introduction

TS can be used to build Node.js application using the popular Express framework.

Executing TypeScript Code with Node.js

Add a simple “app.ts” file and if you run the TS complier and then run the JS file you can see an example console.log

tsc app.ts

// then

node app.js

Node doesn’t run TS code. You need to compile and then run the “.js” file.

There is a “TSNode” package into one step.

Setting up a Project

Node/TS application. Don’t need webpack here but will run “npm init.” And also run “tsc –init” to initialize this project as a TS project. Tweak the “tsconfig.json” file by setting th e”target” to “es2018”, the “module” to “commonjs” and the “moduleresolution” to “node” and “outDir” to “dist” and “rootDir” to “src”

Create a “src” folder where we will write our TS code.

install express

install body-parser

install –save-dev nodemon

Finished Setup & Working with Types (in Node + Express Apps)

In the “src” folder add an “app.ts” file.

const express = require('express')

const app = express()

app.listen(3000)

install –save-dev @types/node

instal –save-dev @types/express

We still get an error but we can use import/export syntax and TS will compile this back to a different syntax

import express from 'express'

const app = express()

app.listen(3000)

Open up a second terminal and run “npm run start” after creating a “start” script in the “package.json” file that runs “nodemon dist/app.js”

Adding Middleware & Types

Create a new “src/routes” folder and a “todos.ts” file.

import {Router} from "express"
// also import all the functions from the controller file

const router = Router()

router.post('/', createTodo)

router.get('/', getTodos)

router.patch('/:id', updateTodo)

router.delete('/:id', deleteTodo)

export default router

Connect the router to the “app.ts” file.

// in app.ts file

import express, {Request, Response, NextFunction} from 'express'

import todoRoutes from "./routes/todos"

const app = express()

app.use(json())

app.use('/todos', todoRoutes)

app.use(err: Error, req: Request, res: Response, next: NextFunction) => {
  res.status(500).json({message: err.message})
}

app.listen(3000)

Working with Controllers & Parsing Request Bodies

Create a “src/controllers/todos.ts” file

There is a way to avoid repeatedly setting the type for “req, res, and next” by using the “RequestHandler” from “express.”

// in controllers.todos.ts

import {RequestHandler} from "express"

import {Todo} from "../models/todo"

const  TODOS: Todo[] = []

export const createTodo: RequestHandler = (req, res, next) => {
  const text = req.body.text
  const newTodo = new Todo(Math.random().toString(), text)

  TODOS.push(newTodo)

  res.status(201).json({message: "Created the Todo.", createdTodo: newTodo})
}

Create a new “src/models/todo.ts” file to create a class so we know what a “todo” should look like. Use “type-casting” to tell TS what we expect to be on incoming request. Use “.push()” to add a new todo onto the TODO array.

// in models/todo.ts

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

  }
}

In the “controller/todos.ts” file, import “Todo” from “../models/todo” and continue building our “createTodo” function.

Note: A class automatically acts as a “type.”

In the “createTodo” function, instantiate a “new Todo()” object and pass in an “id” as the first argument, which we will create on the fly with “Math.random().toString(),” and as the secondary argument, a constant variable named “text” that is defined above as “req.body.text”

Use “type casting” to refactor the “text” variable to “(req.body as {text: string}).text” and then send a “json” response.

In the router file, for the .post(‘/’) request, point to our newly-created “createTodo” function.

Note: we need to import { json } from the “body-parser” package when setting up the “json middleware function” in the “app.ts” file.

Install postman so we can send a “post” request.

More CRUD Operations

TODOs are only managed in memory so do not persist when we restart the server but nonetheless.

Create a “getTodos” function for the “get(‘/’)” request.

// in controllers.todos.ts

import {RequestHandler} from "express"

import {Todo} from "../models/todo"

const  TODOS: Todo[] = []

export const createTodo: RequestHandler = (req, res, next) => {
  const text = req.body.text
  const newTodo = new Todo(Math.random().toString(), text)

TODOS.push(newTodo)

res.status(201).json({message: "Ceated the todo.", createdTodo: newTodo})
}

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

In the “routes/todos.ts” file, import {getTodos} from the controller and connect to the “.get(‘/’)” route.

Create a “updateTodo” function for the “patch” request. Need to get the dynamic id, with req.params.id and add a generic type. Use the “.findIndex()” method and then send a “json” response. In the “routes/todos.ts” file import from controller function and point to in the “.post(‘/’)” route. Test in postman. Test what happens if there is an “id” that does not exist.

// in controller/todos.ts

export const updateTodo: RequestHandler<{id: string}> = (req, res, next) => {
  const todoId = req.params.id

  const updatedText = (req.body as {text: string}).text

  const todoIndex = TODOS.findIndex(todo => todo.id === todoId)

  if (todoIndex < 0) {
    throw new Error("Could not find todo.")
  }

  TODOS[todoIndex] = new Todo(TODOS[todoIndex].id, updatedText)

  res.json({message: "Updated", updatedTodo: TODOS[todoIndex]})
}

Create a “deleteTodo” function for the “delete” request. Copy code from the “updateTodo” function to get started. Connect this function to the “routes/todos.ts” file. Send a “delete” request with postman, with no data attached, to test.

// in controller/todos.ts

export const deletTodo: RequestHandler = (req, res, next) => {
  const todoId = req.params.id

  const todoIndex = TODOS.findIndex(todo => todo.id === todoId)

  if (todoIndex < 0) {
    throw new Error("Could not find todo.")
  }

  TODOS.splice(todoIndex, 1)

  res.json({message: "Todo deleted.")
}

Wrap Up

More examples checkout nest.js, NodeJS framework that gives you TS support out of the box. Heavily uses decorators, interfaces, and so on.

Useful Resources & Links