TypeScriptのExpressアプリケーションを作る

## 動作環境

- macOS Mojave
- node v12.14.1
- npm 6.13.4
- express 4.16.4
- typescript 3.8.2

## 最終的なコード

最終的なコードは以下に上げてあるので、先に見たい方は御覧ください。
https://github.com/jumperson/quick-start-express-typescript

## 作成手順

### JavaScriptのExpressアプリケーションを作成する

[Express のアプリケーション生成プログラム](https://expressjs.com/ja/starter/generator.html) に従って作成していきます。

グローバルにexpressをインストールします。

```
$ npm install express-generator -g
```

インストールしたコマンドで作業ディレクトリに `quick-start-express-typescript` という名のExpressアプリケーションを作成します。

```
$ express quick-start-express-typescript --git --no-view
```

npm installを実行し、Expressアプリケーションを起動します。

```
$ cd quick-start-express-typescript
$ npm install
$ DEBUG=myapp:* npm start
```
http://localhost:3000/ にアクセスし、動作していることを確認します。

### 不要なViewファイルを削除する

今回はTypeScriptにするファイルを減らすためにViewファイルは削除します。

```
$ rm -r ./public/
$ rm ./routes/index.js 
```

上記を参照しているコードも修正します。

```diff
diff --git a/app.js b/app.js
index d187f73..3aecdc0 100644
--- a/app.js
+++ b/app.js
@@ -3,7 +3,6 @@ var path = require('path');
 var cookieParser = require('cookie-parser');
 var logger = require('morgan');
 
-var indexRouter = require('./routes/index');
 var usersRouter = require('./routes/users');
 
 var app = express();
@@ -14,7 +13,6 @@ app.use(express.urlencoded({ extended: false }));
 app.use(cookieParser());
 app.use(express.static(path.join(__dirname, 'public')));
 
-app.use('/', indexRouter);
 app.use('/users', usersRouter);
 
 module.exports = app;
```

### TypeScriptに書き換える

初めに、以下のコマンドを実行し、TypeScriptに必要なモジュールを追加します。

```
$ npm install --save-dev typescript @types/node @types/express @types/debug
```

次に、以下のコマンドで、 `tsconfig.json` を作成します。

```
$ ./node_modules/.bin/tsc --init
```

次に、JavaScriptファイルを以下の通りTypeScriptに書き換えます。

#### `./routes/users.js` => `./routes/users.ts`

```typescript
import { Request, Response } from "express";

/* GET users listing. */
export const index = (req: Request, res: Response) => {
  res.send('respond with a resource');
};
```

#### `./app.js` => `./app.ts`

```typescript
import express = require('express')
import * as usersRouter from "./routes/users";

const app = express();
app.use('/users', usersRouter.index);

export default app;
```

#### `./bin/www` => `./bin/www.ts`

```typescript
#!/usr/bin/env node

/**
 * Module dependencies.
 */

import app from '../app';
import * as http from 'http';
import * as debugModule from 'debug';
var debug = debugModule.debug('quick-start-express-typescript:server');

/**
 * Get port from environment and store in Express.
 */

const port = normalizePort(process.env.PORT || '3000');
app.set('port', port);

/**
 * Create HTTP server.
 */

const server = http.createServer(app);

/**
 * Listen on provided port, on all network interfaces.
 */

server.listen(port);
server.on('error', onError);
server.on('listening', onListening);

/**
 * Normalize a port into a number, string, or false.
 */

function normalizePort(val: string): number | string | boolean {
  const nport = parseInt(val, 10);

  if (isNaN(nport)) {
    // named pipe
    return val;
  }

  if (nport >= 0) {
    // port number
    return nport;
  }

  return false;
}

/**
 * Event listener for HTTP server "error" event.
 */

function onError(error: any): void {
  if (error.syscall !== 'listen') {
    throw error;
  }

  const bind = typeof port === 'string'
    ? 'Pipe ' + port
    : 'Port ' + port;

  // handle specific listen errors with friendly messages
  switch (error.code) {
    case 'EACCES':
      console.error(bind + ' requires elevated privileges');
      process.exit(1);
    case 'EADDRINUSE':
      console.error(bind + ' is already in use');
      process.exit(1);
    default:
      throw error;
  }
}

/**
 * Event listener for HTTP server "listening" event.
 */

function onListening(): void {
  function bind() {
    const addr = server.address();
    if (addr === null) {
      return '';
    }

    if (typeof addr === 'string') {
      return 'pipe ' + addr;
    }

    if ('port' in addr) {
      return 'port ' + addr.port;
    }
  }

  debug('Listening on ' + bind());
}
```

### TypeScriptをJavaScriptにトランスパイルする

まずは `src` ディレクトリを用意し、上記の `.ts` ファイルをすべて移動します。

```
On branch master
Changes to be committed:
  (use "git restore --staged <file>..." to unstage)
	renamed:    app.ts -> src/app.ts
	renamed:    bin/www.ts -> src/bin/www.ts
	renamed:    routes/users.ts -> src/routes/users.ts
```

次に `dist` ディレクトリを用意し、 `tsconfig.json`を以下のように変更します。

```diff
diff --git a/tsconfig.json b/tsconfig.json
index 54d53fd..b88125a 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -12,7 +12,7 @@
     // "outFile": "./",                       /* Concatenate and emit output to single file. */
-    // "outDir": "./",                        /* Redirect output structure to the directory. */
+    "outDir": "./dist",                        /* Redirect output structure to the directory. */
     // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
```

次に、トランスパイルのコマンドを追加します。

```diff
diff --git a/package.json b/package.json
index 29eaa22..48a62e0 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,8 @@
   "version": "0.0.0",
   "private": true,
   "scripts": {
-    "start": "node ./bin/www"
+    "start": "node ./bin/www",
+    "build": "tsc"
   },
   "dependencies": {
     "cookie-parser": "~1.4.4",
```

追加したコマンドを実行し、 `dist` ディレクトリにJavaScriptのファイルが作成されることを確認します。


```
$ npm run build
```

また、 `dist` ディレクトリはgit管理不要なので、以下の修正を行います。

```diff
diff --git a/.gitignore b/.gitignore
index d1bed12..a031e35 100644
--- a/.gitignore
+++ b/.gitignore
@@ -59,3 +59,5 @@ typings/
 
 # next.js build output
 .next
+
+/dist
```

### トランスパイルされたファイルでExpressを起動する

Express起動をコマンドを修正してExpressを起動できるようにします。

```diff
diff --git a/package.json b/package.json
index 48a62e0..ee55eb6 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
   "version": "0.0.0",
   "private": true,
   "scripts": {
-    "start": "node ./bin/www",
+    "start": "npm run build && node ./dist/bin/www.js",
     "build": "tsc"
   },
   "dependencies": {
```

### ホットリロードの設定

最後に、開発時のコード修正時にホットリロードされるようにします。

以下のモジュールを追加します。

```
$ npm install --save-dev ts-node nodemon
```

次にルートに以下のファイルを追加します。

```json
{
    "watch": ["src"],
    "ext": "ts",
    "exec": "ts-node ./src/bin/www.ts"
}
```

次にコマンドを追加します。

```diff
diff --git a/package.json b/package.json
index f602cdd..c56097d 100644
--- a/package.json
+++ b/package.json
@@ -4,6 +4,7 @@
   "private": true,
   "scripts": {
     "start": "npm run build && node ./dist/bin/www.js",
+    "start:dev": "nodemon",
     "build": "tsc"
   },
   "dependencies": {
```

これで以下のコマンドでExpressが起動し、またコードを修正した際に自動で再起動されるようになります。

```
npm run start:dev
```

## 最終的なコード（再掲）

冒頭でも記載しましたが、最終的なコードは以下に上げてあります。
https://github.com/jumperson/quick-start-express-typescript


## 参考にしたページ

以下はこの記事の参考にさせて頂いたページです。

- [microsoft/TypeScript-Node-Starter: A starter template for TypeScript and Node with a detailed README describing how to use the two together.](https://github.com/microsoft/TypeScript-Node-Starter)
- [TypescriptでExpressを設定する - Qiita](https://qiita.com/kubocchi/items/0e9bf6ee9eab98905d77)
- [nodebestpractices/separateexpress.md at master · goldbergyoni/nodebestpractices](https://github.com/goldbergyoni/nodebestpractices/blob/master/sections/projectstructre/separateexpress.md)
- [Express のアプリケーション生成プログラム](https://expressjs.com/ja/starter/generator.html)
- [NodeJSでTypeScriptのホットリロード - Qiita](https://qiita.com/ckoshien/items/1a8b15fe5cc3bfc15199)