Create a simple server with Node.js, Express and Typescript
Setup
Create the project's folder:
shmkdir node-app
cd node-app
Generate a default package.json file with yarn:
shyarn init -y
Install Typescript:
shyarn add -D typescript tslint ts-node-dev ts-node @types/node
Install Express, Jest, Pino and some middleware:
shyarn add express cors pino pino-pretty yarn add -D @types/express @types/cors @types/jest @types/supertest jest ts-jest@next supertest
Create a file named tsconfig.json and paste:
json{
  "ts-node": {
    "transpileOnly": true
  },
  "compilerOptions": {
    "module": "commonjs",
    "moduleResolution": "node",
    "pretty": true,
    "sourceMap": true,
    "target": "ES2020",
    "outDir": "./dist",
    "removeComments": true,
    "esModuleInterop": true,
    "baseUrl": "./",
    "strict": true,
    "lib": [
      "ES2020"
    ],
    "typeRoots": [
      "node_modules/@types"
    ]
  }
}
Create a file named jest.config.js with:
jsmodule.exports = {
  verbose: false,
  testMatch: [
      "**/?(*.)+(spec|test).+(ts|tsx|js)"
  ],
  transform: {
      "^.+\\.(ts|tsx)$": "ts-jest"
  },
}
Then, open package.json and add:
json,"scripts": {
    "prod": "tsc",
    "dev": "tsnd --respawn src/main.ts",
    "lint": "tslint 'src/**/*.ts'",
    "audit": "npm audit fix",
    "test": "jest"
  }
Hello World
Create the Hello Module
Create the following files & folders structure:
...
/src/
  modules/
    hello/
      hello.service.ts
      hello.controller.ts
      hello.test.ts
      hello.routes.ts
...
Add the code that will say hello in hello.service.ts:
jsclass HelloService {
  public static sayHello(name: string): string {
    return `Hello ${name}`
  }
}
export default HelloService
Create the controller in hello.controller.ts:
jsimport { NextFunction, Request, Response } from 'express';
import HelloService from './hello.service';
export class HelloController {
  public hello(req: Request, res: Response, next: NextFunction): void {
    res.json({ message: "Hello World!" })
  }
  public getHello(req: Request, res: Response, next: NextFunction): void {
    const name = req.params.name
    const message = HelloService.sayHello(name)
    res.json({ message })
  }
  public sayHelloTo(req: Request, res: Response, next: NextFunction): void {
    const to = req.body.sayHelloTo
    const message = HelloService.sayHello(to)
    res.json({ message })
  }
}
Then add the routes in hello.routes.ts:
jsimport express from 'express';
import { HelloController } from './hello.controller';
export class HelloRoutes {
  private readonly _router = express.Router();
  private controller = new HelloController();
  constructor() {
    this.routesSetup()
  }
  get router(): express.Router {
    return this._router
  }
  private routesSetup() {
    this._router.get("/hello", this.controller.hello)
    this._router.get("/helloTo/:name", this.controller.getHello)
    this._router.post("/hello", this.controller.sayHelloTo)
  }
}
Add a test in hello.test.ts:
jsimport app from '../../main';
import request from 'supertest';
import HelloService from './hello.service';
describe("demo test", () => {
  it("should be equal", () => {
    expect(1).toBe(1)
  })
})
const testTarget = app // OR: 'http://localhost:3000'; 
describe('Test REST Endpoint', () => {
  it('Request /hello should return Hello World!', async () => {
    const result = await request(testTarget).get('/hello').send();
    expect(result.status).toBe(200);
    expect(result.body).toMatchObject({ message: "Hello World!" })
  });
  it('Request /helloTo/test should return Hello test!', async () => {
    const result = await request(testTarget).get('/helloTo/test').send();
    expect(result.status).toBe(200);
    expect(result.body).toMatchObject({ message: "Hello test" })
  });
  it('POST Request /hello should return Hello test2!', async () => {
    const result = await request(testTarget).post('/hello').send({ sayHelloTo: 'test2' });
    expect(result.status).toBe(200);
    expect(result.body).toMatchObject({ message: "Hello test2" })
  });
});
///
describe("Test Hello Service", () => {
  it("should say Hello World", () => {
    const result = HelloService.sayHello('World')
    expect(result).toBe('Hello World')
  })
})
afterAll(done => {
  done();
});
Main app
Create a custom error message in src/custom-error.ts:
jsexport enum HttpStatusCode {
  OK = 200,
  BAD_REQUEST = 400,
  NOT_FOUND = 404,
  INTERNAL_SERVER = 500,
}
class AppError extends Error {
  public readonly statusCode: HttpStatusCode
  public readonly message: string
  public readonly type: string
  constructor(message: string, statusCode: HttpStatusCode, description: string = '') {
    super(description)
    this.message = message
    this.statusCode = statusCode
    this.type = description
    Error.captureStackTrace(this)
  }
}
export default AppError
And the server's entry point in src/main.ts:
jsimport cors from 'cors';
import express, { Application, NextFunction, Request, Response } from 'express';
import AppError, { HttpStatusCode } from './custom-error';
import pino from 'pino'
import { HelloRoutes } from "./modules/hello/hello.routes"
export const Logger = pino({
  transport: {
    target: 'pino-pretty',
    options: {
      colorize: true,
      levelFirst: true,
      translateTime: "yyyy-mm-dd, h:MM:ss TT",
    }
  }
});
const app: Application = express();
app.use(cors());
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
//
// 
// Setup routes
const routes = [
  new HelloRoutes()
]
routes.forEach(item => app.use('/', item.router))
//
// 
// return error for all routes that are not matched 
app.all('*', (req: Request, res: Response, next: NextFunction) => {
  next(new AppError(`Can't find ${req.originalUrl}`, HttpStatusCode.NOT_FOUND))
});
// 
// 
// Error Handling Middleware
app.use((err: Error, req: Request, res: Response, next: NextFunction) => {
  let statusCode = 500
  if (err instanceof AppError) {
    statusCode = err.statusCode
  }
  Logger.error(err.message)
  res.status(statusCode).json({
    type: 'error',
    message: err.message
  });
});
const server = app.listen(3000, '0.0.0.0');
// for tests
export default server
Run tests with yarn test
Start the server with yarn dev then open a browser and visit http://localhost:3000/hello`