18年07月24日

輕量版NodeJS Express TypeScript Starter

這是一個我常用的 TypeScript project 設定,當中包括了一些基本功能:

  • Express Server + body parser
  • TypeScript Compile
  • 比較寬鬆的 Tslint
  • Nodemon 自動重啟伺服器
  • Module Alias 方便 import 的工具
  • Mocha Unit Testing
  • Production 和 Development 的 Build Script

安裝與運行

先下載項目跟模組

Copy Code

git clone https://github.com/auphone/node-typescript-starter.git
cd node-typescript-starter
npm install

打開兩個 terminalcmdshell,各自執行

Copy Code

npm run watch-ts

以及

Copy Code

npm serve

最後打開瀏覽器測試 API

http://localhost:3000/api/env

全部指令

指令功能
npm watch-ts監測並compile src 內的TypeScript原碼
npm run serve執行Nodemon,與 npm watch-ts 同時使用
npm start直接運行 dist/ ,需要先進行Compile
npm run testUnit test所有 src/**/*.spec.ts
npm run buildDev環境的Build
npm run build-prodProduction環境的Build

Notes

Tslint

這是我的 tslint.json 設定,我覺得用 tslint --init 產生出來的預設設定太嚴緊了,所以修改了一下

Copy Code

{
  "rules": {
    "class-name": true,
    "comment-format": [true, "check-space"],
    "indent": [true, "spaces"],
    "one-line": [true, "check-open-brace", "check-whitespace"],
    "no-var-keyword": true,
    "quotemark": [true, "single", "avoid-escape"],
    "semicolon": [true, "always", "ignore-bound-class-methods"],
    "whitespace": [
      true,
      "check-branch",
      "check-decl",
      "check-operator",
      "check-module",
      "check-separator",
      "check-type"
    ],
    "typedef-whitespace": [
      true,
      {
        "call-signature": "nospace",
        "index-signature": "nospace",
        "parameter": "nospace",
        "property-declaration": "nospace",
        "variable-declaration": "nospace"
      },
      {
        "call-signature": "onespace",
        "index-signature": "onespace",
        "parameter": "onespace",
        "property-declaration": "onespace",
        "variable-declaration": "onespace"
      }
    ],
    "no-internal-module": true,
    "no-trailing-whitespace": true,
    "no-null-keyword": true,
    "prefer-const": true,
    "jsdoc-format": true
  }
}

Paths 設定

在 TypeScript 設定 paths 就可以解決這個問題…

Copy Code

import { app } from "../../../app";

這是我的 tsconfig.json,因為我同時有開發 Angular 所以盡量使用相似的設定

Copy Code

{
    "compilerOptions": {
      ...
        "paths": {
            "@app/*": ["*"],
            "@env/*": ["environments/*"]
        }
    }
}

設定好了以後…

Copy Code

// 以前
import * as someModule from '../../modules/someModule';

// 現在我們可以寫成這樣
import * as someModule from '@app/modules/someModule';

然後我還會在每層資料夾設定一個 index.ts(barrel),再在裡面 export * 所有需要的模組

Copy Code

export * from './server.model';

雖然好像比較多工序,但是以後我只要這樣寫就可以取得所有子 function 了,但這也有一個缺點就是不能有重覆的命名…

Copy Code

import * as fromModels from '@app/models/';

Module Alias

雖然 TypeScript 有 paths 的功能,但是在 NodeJS 我們不會用 WebPack,所以在 compile 的時候還是要自行添加一些設定,這邊我就用了 module-alias 這個模組,為了方便在不同環境和 unit test 的時候使用,我還會把它分拆成一個獨立模組,而設定就跟 TypeScript 的 paths 指到一樣的地方

Copy Code

import * as alias from 'module-alias';
import * as path from 'path';

export const init = () => {
  alias.addAliases({
    '@app': __dirname,
    '@env': path.resolve(__dirname, './environments')
  });
};

Unit Testing

Unit Testing 通常有幾種結構,我個人比較喜歡把 .spec 與模組放到同一個目錄亦即 src 內,也有人喜歡把它們全放在 test/ 內,都是因人而異,mochachai 也是個人喜好而採用的

Copy Code

import { expect } from 'chai';
import * as path from 'path';

// Init Alias
import * as alias from '../alias';
alias.init();

import * as fromServer from './server.model';

describe('GET Environment', () => {
  it('should be equal to dev', () => {
    return expect(fromServer.getEnv()).eq('dev');
  });
});

Build

預設有兩個指令,Dev 伺服器的 npm run build 和 Production 伺服器的 npm run build-prod,這邊只做了一個複製 environment.prod.tsenvironment.ts 的動作,但這可以說是必需要的

後記

由於我經常會做很多 Tech 的試驗,現在 TypeScript 已經不能像以前 JS 那麼簡單了,所以 Scripting 和不小 POC 我都還是會用 CoffeeScript 編寫,而真正有發展的項目才會用到 TypeScript…

(*゚ー゚)