Help us understand the problem. What is going on with this article?

TypeScriptでExpress.js開発するときにやることまとめ (docker/lint/format/tsのまま実行/autoreload)

やること

ステップごとに分けてあるので、導入したい箇所だけつまみ食いすることも可能です。
気力が残っていれば、追記で本番環境の運用についても書きたいです…

  1. express.jsをDockerで起動
  2. TypeScript導入
  3. TSLint + Prettier導入
  4. node-ts導入: dev環境はトランスパイルなしでtsファイルのまま開発
  5. nodemon導入: dev環境はファイル変更を検知して自動リロード

記事ではnode.js 8.12を使用しています。
8系から、npm install時に自動的に--saveオプションが付くようになっていますので、
記事内で「あれ?--saveしてなくない?」とならないように一言補足。

まずは最小構成でexpress.js起動

準備

(追記)
エディタ補完を効かせるには結局MacにNode.js入れないといけなかったりしましたが、
VSCodeがDockerコンテナのリモート開発に対応したりして潮目も変わってきたので
過激派用の設定も追記しました。
Dockerで立ち上げた開発環境をVS Codeで開く!

A. MacにNode.js入れている場合

PJフォルダ(仮にts-express)を作成し、とりあえずMac環境でinit

npm init -y

B. MacにNode.js入れない硬派な場合

PJフォルダ(仮にts-express)を作成し、空のjsonを作成
(何はともあれpackage.jsonがないとDockerイメージのビルドに失敗するので)

echo "{}" >> package.json

Docker開発環境整備

Dockerfile 作成

dockerのなんやかんやは素晴らしい記事がたくさんあるので、簡潔な説明に留めます…

Dockerfile
# ベースイメージはお好みで普通のでもalpineでも。
FROM node:8.12.0-slim
ENV APP_ROOT /app/

WORKDIR $APP_ROOT

# package.jsonとpackage-lock.jsonを先にコピー。
# package*.jsonだけを先に個別コピーすることで、パッケージ変更時は`RUN npm install`が走るが
# それ以外のファイル変更時は同コマンドにはキャッシュ利用で飛ばされるため、ビルド時間を短縮できる。
COPY package*.json $APP_ROOT
RUN npm install

# その他ファイルをコピー。
COPY . $APP_ROOT

docker-compose.yml 作成
dockerコマンド叩くときに毎回引数つけるのが面倒なので、docker-compose作ります。

docker-compose.yml
version: '2.1'
services:
  web:
    build:
      context: .
      dockerfile: ./Dockerfile
    command: npm run dev
    environment:
      NODE_ENV: development
    ports:
      - '3000:3000'
    volumes:
      - .:/app

入る

docker-compose run --rm web bash

以降、別段コメントがなければbashコマンドはdocker内で実行します。

Mac側でnpm initしていない場合はここでやる

npm init

expressインストール

npm i express

テスト用src/index.js作成

src/index.js
const express = require('express');
const app = express();
const port = 3000;

app.get('/', (req, res) => res.send('Hello World!'));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));

package.jsonにdev実行コマンドを追加

package.json
"scripts" {
 "dev": "node src/index.js"
}

起動テスト

# Macの別窓terminalで実行
docker-compose up --build web
# バックグラウンド実行したいときは-dオプション
# docker-compose up -d --build web

ブラウザでhttp://localhost:3000/を開くと Hello World! が出ます :tada:
出たら一度 docker-compose down web で止めましょう。
-dオプション無しで起動してる場合はCtrl+cでも止められます。

(追記)
docker-compose upは、2回目以降は実は毎回ビルドしなくても良かったりします。

docker-compose.ymlファイルのvolumesに書いてある通りファイルの変更は
dockerの中でいじろうがホスト側でいじろうが常に双方向に更新されます。
なので、キャッシュが効いて高速にビルドされるとはいえ、数秒の待ちが煩わしい人は
普段は--buildなし、何か挙動がおかしくなったら--build付けて再ビルドが
お手軽かなと思います。堅実に生きたい人は常に--buildで。

TypeScript導入

TypeScriptと型情報を追加

npm i --save-dev typescript @types/express

src/index.jssrc/index.tsに名前変更
とりあえずトランスパイル出来ることだけを確認したいので、中身は変更しない。

/distディレクトリを作成

mkdir dist

tsconfig.json を作成

requireの記法を使うなら "module": "commonjs"
importの記法を使うなら "module": "es6" (TSなら普通こっちですかね。)

tsconfig.json
{
    "compilerOptions": {
        "outDir": "./dist",
        "sourceMap": true,
        "module": "es6",
        "target": "es5",
        "moduleResolution": "node",
        "removeComments": true
    },
    "include": [
        "./src/**/*"
    ]
}

モジュールのインポートがcommonjsの場合はsrc/index.tsの1行目を書き換え

// const express = require('express'); // これを
import * as express from "express";    // こう

package.jsonのscriptsを編集

package.json
"scripts": {
  // distから走るように修正。
  "dev": "node dist/index.js",
  // 追加。TypeScriptをグローバルではなくローカルインストールしているため、npm run tscでコンパイルがちゃんと走るようにする
  "tsc": "tsc"
}

トランスパイルする

npm run tsc

これでdistにトランスパイル後のファイルが出来上がるので、改めて走らせてみる

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。
docker-compose up --build web

TSLint + Prettier導入(入れたい方のみ)

(2019/11/11追記)

こちら の記事にあるように、現在はTSLintからESLint TypeScript Pluginへの移行が推奨されています。

TSLint 開発チームは ESLint のプラグインとして TSLint の機能を統合していく typescript-eslint プロジェクトを開始し、TSLint は 2019年中に非推奨となる予定である ことが発表されました。

よって、長期的にメンテナンスが見込まれる開発の場合はLinter/Formatter導入について VSCodeでESLint+@typescript-eslint+Prettierを導入する(v2.0.0修正版) 等を参考にして下さい :bow:

---------- 以下、既存のTSLint + Prettier導入記事 ----------

(ESLintの場合はこのブログ記事が参考になります。 ESLint(あるいはTSLint)とPrettierを併用する

リンターとフォーマッターの導入です。
両方入れる方法はいくつかあるのですが、ここではTSLintにPrettierの処理もまかせる方式で行きます。
この方式だと、設定ファイルがtslint.json一箇所にまとまって見通しが良くなります。

npm i -D tslint prettier tslint-config-prettier tslint-plugin-prettier

〜簡潔に説明〜

  • tslint リンター本体
  • prettier フォーマッター本体
  • tslint-config-prettier TSLintとPrettierの重複処理を無効化
  • tslint-plugin-prettier ESLintのルールとしてPrettierを読み込む

(optional)ベースルールを使用したい人は導入します。僕の場合はeslintのstandardが好きなので、
そのルールをTSに合うようにカスタマイズされたtslint-config-standardを使用します。

npm i -D tslint-config-standard

tslint.jsonファイルを作成し、設定を書きます。
extendsのtslint-config-standardは上記で導入したルールに読み替えてください。
入れなかった場合はtslint-config-prettierだけ記述すればOKです。

tslint.json
{
  "rulesDirectory": ["tslint-plugin-prettier"],
  "extends": ["tslint-config-standard", "tslint-config-prettier"],
  "rules": {
    // TSLintに任せる方法の場合、prettier設定もここに書くことになります。
    // 設定内容はstandardだとsingleQuoteとsemiだけ気をつければ多分大丈夫です。
    // また、完全に僕の好みですが行あたり80文字は短すぎるので長めにしてあります。
    "prettier": [
      true, {
        "singleQuote": true,
        "semi": false,
        "printWidth": 120 // defaultの80って短くないですか?
      }
    ]
  }
}

上記を動かせるように、lintコマンドを追加します。

package.json
"script" {
  "lint": "tslint --exclude **/*.d.ts --project . --fix 'src/**/*.ts' 'test/**/*.ts'"
}

--project .は付けないと、rule requires type informationの警告が大量に出るため付けています。

テストディレクトリを含めるかどうかはお好みで。
またlintコマンドのオプションは色々な指定方法があるので、PJに合わせて適宜変更してください。

では、作成したlintコマンドテストをテスト。

npm run lint

自動修正できないものは無理ですが、修正できるものはこれで一発修正されます。
例えば、standardルールなら、最初に作ったsrc/index.tsの;が全部吹っ飛んだはずです。

(optional)VSCodeの人
TSLintプラグインを導入し、設定で "tslint.autoFixOnSave": true にすれば
保存のたびにLint+Prettierしてくれます。

ts-nodeを使ってトランスパイルなしで開発

ここまでくればTSでコードを書いていけるようになりましたが、コードを変更するたびに
npm run tscしてdocker-compose up webし直さなければなりません。それは流石にキチィ。

というわけで、ts-nodeを入れます。こいつを入れることで、nodeコマンドでjsファイルを
実行するのと同じようにts-nodeでtsファイルを実行できるようになります。

インストール

npm i -D ts-node

設定

npm run devコマンドでnodeではなくts-nodeを使用し、srcのtsファイルを直接読むようにします。
また、本番環境ではトランスパイル後の生jsを走らせると思うのでdistからの実行コマンドを別で用意します。
ts-nodeは例によってローカルインストールのため、パスが通るように手動でscriptに追加します。

追記: ts-nodeの本番環境運用も試しており、結論から言うと起動時間が若干長いという以外の
オーバーヘッドは殆ど無いので、本番実行も ts-node ./src/index.ts でも良いと思います。

package.json
"scripts": {
  "start": "node dist/index.js",    // 本番実行用のコマンド。これの前にtscも走らせること。
  "dev": "ts-node ./src/index.ts",  // 開発環境用のコマンド。
  "ts-node": "ts-node"
}

テスト

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。
docker-compose up --build web

トランスパイルしなくても、直接tsファイルを読み出すようになりました。

変更検知して自動リロード

tsファイルを直読めるようになったとはいえ、まだこのままではファイルに変更が加わるたびにサーバーを止めて
'npm start'し直さなければなりません。それは流石にキチィ。

というわけで、nodemonを入れます。こいつを入れることでコードの変更を検知して
サーバーをオートリロード出来るようになります。

インストール

npm i -D nodemon

設定ファイルnodemon.jsonを新規作成

nodemon.json
{
  "watch": ["src"],
  "ext": "ts",
  // テストファイル等、リロード対象にしたくない物があればここに追加
  "ignore": ["tests/**/*.ts"],
  // 実際に実行するコマンド。先程のts-nodeのコマンドです。
  "exec": "npm run ts-node ./src/index.ts"
}

devコマンドでnodemonが起動するように修正

package.json
"scripts": {
  "dev": "nodemon -L"
}

テスト

# Macの別窓terminalで実行。すでに走っている場合はCtrl+Cで一度止める。
docker-compose up --build web

試しにsrc/index.jsのconsole.logのコメントをちょっといじって保存してみて下さい。
docker-composeの実行画面にリロード状況が出て、リロード後ブラウザでlocalhost:3000を
開き直すと変更が適用されているのが分かると思います。

おわり

これで、快適なTypeScript + Express.js開発環境を手に入れることができました。

Dockerfile, docker-compose.yml, その他lint, prettier等の各種設定は
プロジェクトにより色々変わってくると思うので、この記事はあくまでベースとして使用して
修正加えてください。

以下もお供にどうぞ

コントローラー
- TypeScriptでExpress.jsのコントローラー部分をクラスベースで書く

モデル
- TypeScriptのORマッパーならTypeORM
- TypeORMのテストで楽をする

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした