TypeScript, Node.js and Express.js ultimate project setup | Tigran in Tech

Github Repository: https://github.com/tigranbs/node-typescript-starter-kit

Node.js release after release tries to become more similar to ES6 standards using some modern definitions of classes and new export types, but TypeScript is out there already and it is absolute no brainer to use it with Node.js coding.

Especially if you want to make a way better bug free codebase with more expressive syntax. TypeScript will give you a lot more language features than you have for a plain Node.js JavaScript.

Getting Started

Node.js application setup starts with a basic npm init which is just a basic process to follow for almost all kind of JavaScript projects now-days. At the end we are getting file which similar content

package.json

{
  "version": "0.0.1",
  "license": "MIT",
  "author": {
    "name": "Tigran Bayburtsyan",
    "url": "https://tigran.tech"
  },
  "scripts": {
    "start": "nodemon --watch 'src/**/*.ts' --exec 'ts-node' --files src/index.ts",
    "build": "rm -rf build && tsc -p ."
  },
  "dependencies": {
    "body-parser": "^1.19.0",
    "cors": "^2.8.5",
    "express": "^4.17.1",
    "typescript-rest": "^3.0.1"
  },
  "devDependencies": {
    "@types/cors": "^2.8.6",
    "nodemon": "^2.0.4",
    "ts-node": "^8.10.2",
    "typescript": "^3.9.6"
  }
}

As you can see we have all dependencies needed to setup our project right now!

  • TypeScript - dependency to have an actual typescript compiler available for this project
  • express - an actual express.js which is just became a standard library to build Node.js API endpoints
  • cors, body-parser - just a supportive middleware for Express.js
  • typescript-rest - Express.js wrapper to use it over TypeScript specific syntax and fully OOP version of handling routes

That main dependencies are actually defining the way how we are going to build our tiny service.

TypeScript is using configuration file called tsconfig.json which defines how it is going to handle file parsing and compiling code to common JavaScript. Usually configuration is not changing from project to project, except if you have little bit different directory structure or your project requires some specific TypeScript compiler configurations.

tsconfig.json

{
  "compilerOptions": {
    "lib": [
      "es2018",
      "es5",
      "dom"
    ],
    "baseUrl": ".",
    "paths": {
      "@/types/*" : ["./src/types/*"],
      "*" : ["./src/types/*"]
    },
    "typeRoots": [
      "./src/types"
    ],
    "resolveJsonModule": true,
    "esModuleInterop": true,
    "target": "es5",
    "strict": true,
    "module": "commonjs",
    "moduleResolution": "node",
    "outDir": "./build",
    "emitDecoratorMetadata": true,
    "experimentalDecorators": true,
    "declaration": true,
    "sourceMap": false
  },
  "exclude": [
    "./node_modules/**/*",
    "./build/**/*"
  ],
  "include": [
    "./src/**/*"
  ]
}

For not getting deep into this configuration file, main things to consider is that this configuration is defined to have an src folder as a main entry point for all of your TypeScript codebase, and it excludes everything from node_modules and build directory, because they are basically in a read only state, where you are not making changes, so there is no point of having them available for TypeScript file watcher. I noticed that on my VSCode starts to eat a lot of CPU if I have a lot of dependencies and TypeScript is not disabled for node_modules, so it is a good thing to disable it from the beginning.

Setting up Express.js

After having basic project structure we now can make a src/index.ts file, which is going to be our main entry point and it will be a main startup file to run Express.js server.

There is no that match of a difference in how we are going to spin up our Express.js server, except using TypeScript-rest gives us opportunity to make more expressive directory and code structure.

The main reason I like typescript-rest library is that it picks up defined Route classes whenever you import them. So you don’t have to define routes as a separate middleware then attach it to main application, typescript-rest builds everything during app start using Server.buildServices(app); , which means that you are completely free to define your Route classes wherever you want, just remember import them.

import express, {Request, Response} from "express";
import cors from "cors";
import bodyParser from "body-parser";
import {Server} from "typescript-rest";

// Importing all handlers
import './handlers';

export const app: express.Application = express();

app.use(cors());
app.use(bodyParser.json());

Server.buildServices(app);

// Just checking if given PORT variable is an integer or not
let port = parseInt(process.env.PORT || "");
if (isNaN(port) || port === 0) {
  port = 4000;
}

app.listen(port, "0.0.0.0", () => {
  console.log(`🚀 Server Started at PORT: ${port}`);
});

This should be very simple to understand, because it looks really clean. If you noted there is a specific conditioning for PORT environment variable, checking if provided number is integer or not. The reason is that environment variables are by default string type, but we have a number type for our Express.js app.listen function. That’s where TypeScript comes handy, you can clearly see that you need a number only to listen on server connections.

I like using cors as an Express.js middleware because anyway for production you will want some control on domain requests or iframe integrations. So it is a good thing to have it from the beginning, for not getting errors from UI during first deployment.

typescript-rest

I discovered this library few months ago, and it is absolute killing with coding comfort and flexibility. Basically you have to just define a @Path decorator and @GET or @POST decorators to attache specific route to an existing Express.js app object as a separate route.

If you ever used Django Rest Framework, you will see very similar things also here. You can basically define a class under a specific path, then add create, update, delete and get member functions using specific decorators like @POST, @PUT, @DELETE, @GET so that everything related to a specific DB model will live under the same class using a simple decorator functions.

Example:src/handlers/health.ts

import {Path, GET} from "typescript-rest";
import {resOK} from '../helpers';
import {version} from '../../package.json';

@Path('/')
class Health {
  /**
   * Getting basic health status and current API version information with a simple GET request
   * This is mainly used for automated CI/CD deployments and basic service health checks over
   * Google cloud platform services. It is going to response with current service version
   * defined in package.json file
   */
  @GET
  index(): {status: string, version: string} {
    return resOK({
      status: 'ok',
      version: version,
    });
  }
}

Note that I’m using simple helper functions to get standard response types to make response object as generic as possible, because if you keep response structure the same across all your REST API Endpoints, there would be no major coding on UI to handle error messages!

That’s probably it! For this health.ts handler you just have to import it under src/handlers/index.ts to make it available over main file import, so that typescript-rest will pick it up during application initialization.

What’s next?

Next up this repository is going to be all in one starter kit with full PostgreSQL communication over TyoeORM and few other ignitions to make it as generic as possible for almost all kind of REST API servers using Node.js and TypeScript.

If you have any comments let me know what you will change in this Node.js + TypeScript setup, and don’t forget to subscribe to my YouTube channel 🙂