TypeScript で Express サーバを実装するためのボイラープレートを作った
サーバサイド JS であれば、個人的には TypeScript がなくても書けるのだが、複数人で開発するとなると、型チェックを導入できた方が安全な実装になる。
ということで今回は、Express サーバを TypeScript で実装するためのボイラープレートプロジェクトを作ってみた。
目次
コード全量
いきなりだがコード全量は以下に置いた。
ご自由にご利用ください。
プロジェクト作成の流れ
だいたい次のような流れでプロジェクトを作っていった。
package.json
を生成する
$ npm init -y
TypeScript 環境を作る
$ npm i -D typescript @types/node
# 以下で tsconfig.json を生成する。あとで内容を編集する
$ npm run tsc -- --init
Express を入れる
$ npm i -S express
$ npm i -D @types/express
# コードを書いていく
$ mkdir src
$ touch src/index.ts
# コードをビルドする時はこんな感じ (後で npm run build にする)
$ npm run tsc
# ビルドしたコードを実行する時はこんな感じ (後で npm start にする)
$ node dist/index.js
ts-node : トランスパイルなしで動作させるためのツール
$ npm i -D ts-node
# 使う時はこんな感じ (後で npm run start-ts にする)
$ npm run ts-node src/index.ts
ts-node-dev : ts-node のライブリロード開発対応版
$ npm i -D ts-node-dev
# 使う時はこんな感じ・ファイルの変更監視をしてくれる (後で npm run dev にする)
$ npm run ts-node-dev src/index.ts
ESLint : Linter
$ npm i -D eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
# .eslintrc.js を生成する。あとで内容を編集する
$ npm run eslint -- --init
# 使う時はこんな感じ (後で npm run lint にする)
$ npm run eslint -- --ext '.js,.ts' ./src/**
# Lint + 自動修正を行う場合はこんな感じ (後で npm run lint-fix にする)
$ npm run eslint -- --fix --ext '.js,.ts' ./src/**"
Jest : ユニットテスト
$ npm i -D jest ts-jest @types/jest
# Express API をモック化するために入れておく
$ npm i -D supertest @types/supertest
# 設定ファイルを作成する・内容は後述する
$ touch jest.config.js
# テストファイルを書く
$ mkdir test
$ touch test/index.spec.ts
# 使う時はこんな感じ (後で npm test にする)
$ npm run jest -- --coverage
# coverage/ ディレクトリにカバレッジレポートが出力される
# ライブリロードでテストを行う場合はこんな感じ (後で npm run test-watch にする)
$ npm run jest -- --coverage --watch
supertest
を使う時は、ソースコードとテストコードをそれぞれ次のように記述すること。
src/index.ts
import * as express from 'express';
const app = express();
app.get('/', (req, res) => { res.send('Hello World'); });
// ユニットテスト時の呼び出しでなければサーバを起動する
if(!module.parent) {
app.listen(process.env.PORT || 8080, () => {
console.log('Listening');
});
}
// ユニットテストのために app をエクスポートする
export default app;
test/index.spec.ts
import * as supertest from 'supertest';
import app from '../src/index';
describe('サーバのテスト', () => {
it('Path [/] のテスト', async () => {
const res = await supertest(app).get('/');
expect(res.text).toBe('Hello World');
});
});
こんな感じ。app.listen()
をユニットテスト時に実行しないようにしておく。以前も Jasmine を使う際の解説に書いたが、Jest でも同様だった。
各種設定ファイル
本プロジェクトで必要になる設定ファイルは以下のとおり。
package.json
tsconfig.json
.eslintrc.js
jest.config.js
それぞれの内容を記載していく。
package.json
前述のコマンドを叩いていったあと、scripts
セクションを書いていく。
{
"name": "typescript-express-boilerplate",
"private": true,
"scripts": {
"eslint": "eslint",
"jest": "jest",
"ts-node": "ts-node",
"tsc": "tsc",
"?start": "echo 'トランスパイル済の Express サーバを起動する・事前に npm run build を実行し ./dist/ ディレクトリにコードを生成しておくこと'",
"start": "node ./dist/index.js",
"?start-ts": "echo 'ts-node で ./src/ 配下のコードを直接実行する'",
"start-ts": "ts-node ./src/index.ts",
"?dev": "echo 'ライブリロード開発を開始する・ts-node-dev で ./src/ 配下のコードを直接実行する'",
"dev": "ts-node-dev ./src/index.ts",
"?lint": "echo 'ESLint を実行する (型チェックはできないので npm run build を利用する)'",
"lint": "eslint --ext '.js,.ts' ./src/**",
"?lint-fix": "echo 'ESLint を実行し、自動修正できる箇所は修正する'",
"lint-fix": "eslint --fix --ext '.js,.ts' ./src/**",
"?test": "echo 'ユニットテストを実行する・結果は ./coverage/ ディレクトリに出力される'",
"test": "jest --coverage",
"?test-watch": "echo 'ユニットテストを監視実行する'",
"test-watch": "jest --coverage --watch",
"?build": "echo 'TypeScript をトランスパイルする・設定は ./tsconfig.json を参照'",
"build": "tsc"
},
"dependencies": {
"body-parser": "1.19.0",
"express": "4.17.1"
},
"devDependencies": {
"@types/express": "4.17.6",
"@types/jest": "25.2.3",
"@types/node": "14.0.5",
"@types/supertest": "2.0.9",
"@typescript-eslint/eslint-plugin": "3.0.0",
"@typescript-eslint/parser": "3.0.0",
"eslint": "7.1.0",
"jest": "26.0.1",
"supertest": "4.0.2",
"ts-jest": "26.0.0",
"ts-node": "8.10.1",
"ts-node-dev": "1.0.0-pre.44",
"typescript": "3.9.3"
}
}
tsconfig.json
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"strict": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
// 以下、デフォルトから設定を変更したところ
// トランスパイル後のファイルを出力するディレクトリを指定する
"outDir": "./dist",
// モジュール解決の方法を指定する
"moduleResolution": "node",
// import * as を認識させるため false にする
"esModuleInterop": false
},
// トランスパイル対象ディレクトリを指定する
"include": [
"./src/**/*"
]
}
./dist/
にビルド後のファイルを出力するので、.gitignore
で dist/
を無視しておこう。
.eslintrc.js
TSLint は廃止予定なので、ESLint を使用するよ。
module.exports = {
env: {
es6: true,
node: true
},
extends: [
'plugin:@typescript-eslint/recommended'
],
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module'
},
plugins: [
'@typescript-eslint'
]
};
独自にルール設定したい場合は rules: []
を追加する。何のルールも設定しない空配列だとエラーになるので、その場合は記述しないでおく。
jest.config.js
module.exports = {
transform: {
'^.+\\.ts$': 'ts-jest'
}
};
Jest は --coverage
オプションを付けると ./coverage/
ディレクトリにカバレッジレポートを出力するので、.gitignore
で coverage/
を無視しておこう。
ひとまず OK
実装はこんな感じ。
あとは src/
配下にお好みで TypeScript を書いていくだけ。
Jest はデフォルトで src/
ディレクトリ内の .ts
ファイルに対応する .spec.ts
ファイルを探すので、テストファイルが1対1で揃っていないとワーニングが出る点に注意。
その他のオプション
ファイルの変更監視に ts-node-dev ではなく nodemon を使う
ファイルの変更監視、ライブリロード開発のために ts-node-dev
パッケージを使ったが、世間的には nodemon
の方が知られているかも。ただし、動作は ts-node-dev の方が格段に速い。
一応 nodemon
を使う方法も紹介。
$ npm i -D nodemon
$ touch nodemon.json
nodemon.json
{
"watch": [
"./src/"
],
"ext": "ts",
"exec": "npm run ts-node ./src/index.ts"
}
ESLint に Prettier を追加する
Prettier は、Linter というよりはコード・フォーマッタといえるツール。ESLint 越しに呼び出せるので連携する人も多いだろう。
強制力が強すぎるのが嫌で自分は入れなかったが、導入する際は前述の手順に加えて以下を行う。
$ npm i -D prettier eslint-config-prettier eslint-plugin-prettier
.eslintrc.js
- 次のように書く
module.exports = {
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/eslint-recommended',
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended',
'prettier/@typescript-eslint'
],
plugins: ['@typescript-eslint'],
env: {
node: true,
es6: true
},
parser: '@typescript-eslint/parser',
parserOptions: {
sourceType: 'module'
},
rules: {
// Prettier のルールを上書きする場合は次のように書く
'prettier/prettier': [
'error',
{
// シングルクォートを使う
singleQuote: true,
// 配列の最後の要素の後ろにカンマを置かなくて良い
trailingComma: 'none'
}
]
}
};
以上
ざっとコードで説明した。何のパッケージが必要なのか整理していかないと分かりにくいので、もうこうやってボイラープレートにまとめておくのが良さげ。
参考文献
- TypeScriptでExpress.js開発するときにやることまとめ (docker/lint/format/tsのまま実行/autoreload) - Qiita
- ts-node と ts-node-dev の違い - Qiita
- ESLint 関連
- ESLint + Prettier 関連
- Jest 関連
- Supertest の書き方