Node.js の Express で TypeScript 環境を構築するための手順を解説しています。TypeScript の開発環境を構築するためには複数のパッケージのインストールや設定が必要となります。一括で設定を行うのではなくそれぞれのパッケージの役割を確認しながら設定を行なっていくので Express に限定されてない TypeScript の基礎的な知識を深めることもができます。

プロジェクトの作成

Node.js の Express の開発環境を構築するためのプロジェクトの作成を行います。プロジェクトは任意の名前をつけることができますがここでは node-express-ts という名前をつけています。


 % mkdir node-express-ts

作成した node-express-ts に移動して npm int -y コマンドで package.json ファイルを作成します。


 % cd node-express-ts
 % npm init -y
Wrote to /Users/mac/Desktop/node-express-ts/package.json:

{
  "name": "express-ts",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "keywords": [],
  "author": "",
  "license": "ISC"
}

package.json ファイルを作成後、Express のインストールを行います。


 % npm install express

Express の動作確認

プロジェクトフォルダに src フォルダを作成して index.js ファイルを作成します。


const express = require('express');
const app = express();
const port = 3001;

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

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

ブラウザから localhost:3001 にアクセスして”Hello World”が表示されることを確認します。

JavaScript で動作することが確認できたので TypeScript で動作確認を行います。

TypeScript の設定

typescript パッケージをインストール

TypeScript から JavaScript にコンパイルを行う tsc コマンドが含まれている typescript パッケージのインストールを行います。


 % npm install typescript --save-dev

typescript のインストールが完了したら index.js ファイルの拡張子を js から ts に変更します。

tsc コマンドを利用して index.ts ファイルをコンパイルします。


 % npx tsc src/index.ts
src/index.ts:1:17 - error TS2580: Cannot find name 'require'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node`.

1 const express = require('express');
                  ~~~~~~~


Found 1 error in src/index.ts:1

実行すると require 関数 に関するエラーが表示されます。Node.js の型定義のパッケージ@types/node のインストールについてのメッセージが表示されているので@types/node のインストールを行います。

TypeScript では CommonJS で利用する require 関数/exports 関数を認識することができませんが@types/node パッケージに含まれる型定義ファイル(.d.ts ファイル)によって require 関数/exports 関数を認識してエラーを回避できるようになります。


 % npm i --save-dev @types/node

再度 tsc コマンドでコンパイルを行います。コンパイルに成功して src フォルダに index.js ファイルが作成されることが確認できます。

index.js ファイルの中身は下記のように記述されています。


var express = require('express');
var app = express();
var port = 3001;
app.get('/', function (req, res) { return res.send('Hello World!'); });
app.listen(port, function () { return console.log("Example app listening on port ".concat(port, "!")); });

node index.js コマンドを実行してブラウザから localhost:3001 にアクセスすると”Hello World”が表示されます。

@types/express のインストール

Node.js についての型は@types/node でインストールしましたが express の型定義は含まれていないので@types/express のインストールを行います。


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

@types/express のインストールを行いましたが require 文では app を型を確認すると any となっています。

エディターには VScode を利用しています。
fukidashi
型がany
型がany

require 関数から import 文に変更を行います。


import express from 'express';
const app = express();
const port = 3001;

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

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

import 文に変更後、app の上にカーソルを合わせると any ではなく Express となり型推論が行われていることが確認できます。

app に Express の型
app に Express の型

TypeScript の型が効いているので get メソッドのスペルを got に変更するとエラーメッセージが表示されます。

TypeScript によるエラー表示
TypeScript によるエラー表示

@types/express のインストールによる型定義の確認ができたので tsc コマンドによるコンパイルを行います。


 % npx tsc src/index.ts
src/index.ts:1:8 - error TS1259: Module '"/Users/mac/Desktop/node-myapp/node_modules/@types/express/index"' can only be default-imported using the 'esModuleInterop' flag

1 import express from 'express';
         ~~~~~~~

  node_modules/@types/express/index.d.ts:136:1
    136 export = e;
        ~~~~~~~~~~~
    This module is declared with 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.

esModuleInterop の値を設定した場合のみ default-imported が利用できるというメッセージが表示されましたが esModuleInterop は後ほど設定を行うので import の方法を下記のように変更します。


import * as express from 'express';

import 文を更新後にコンパイルを実行するとコンパイルに成功します。コンパイル後に作成される index.js ファイルの中身を確認すると以下のようなコードに変換されています。


"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var express = require("express");
var app = express();
var port = 3001;
app.get('/', function (req, res) { return res.send('Hello World!'); });
app.listen(port, function () { return console.log("Example app listening on port ".concat(port, "!")); });

import 文の変更ではなく esModuleInterop の設定を行うために tsc コマンドにオプションの esModuleInterop をつけて実行します。


 % npx tsc src/index.ts --esModuleInterop

コンパイルは成功し index.js ファイルには以下のコードが記述されています。“import * as express from ‘express‘“に変更した時とはコードの内容が変わっていることがわかります。


"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const express_1 = __importDefault(require("express"));
const app = (0, express_1.default)();
const port = 3001;
app.get('/', (req, res) => res.send('Hello World!'));
app.listen(port, () => console.log(`Example app listening on port ${port}!`));

esModuleInterop を設定すると ES モジュールと CommonJS モジュールの相互運用性をサポートすることが可能となり、esModuleInterop 設定することで CommonJS モジュールの Express のように default export を利用していないモジュールも default import を利用することができるようになりました。

型を明示的に設定した場合は型を import して設定することができます。


mport express from 'express';
import type { Express, Request, Response } from 'express';

const app: Express = express();
const port = 3001;

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

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

tsconfig.json ファイルの作成

tsc コマンドにオプションを設定しましたが設定ファイルである tsconfig.json を作成することでオプションを tsconfig.json ファイルの中で設定できるようになります。

tsconfig.json ファイルの作成は”tsc —init”コマンドで作成することができます。


 % npx tsc --init

Created a new tsconfig.json with:
                                        TS
  target: es2016
  module: commonjs
  strict: true
  esModuleInterop: true
  skipLibCheck: true
  forceConsistentCasingInFileNames: true


You can learn more at https://aka.ms/tsconfig

作成した tsconfig.json ファイルには esModuleInterop が true に設定されていることが確認できます。

tsconfig.json ファイルの設定が正しく読み込まれるのか確認するために src/index.ts ファイルのコンパイルを行います。実行すると esModuleInterop のエラーが再度表示されます。


 % npx tsc src/index.ts
src/index.ts:1:8 - error TS1259: Module '"/Users/mac/Desktop/node-myapp/node_modules/@types/express/index"' can only be default-imported using the 'esModuleInterop' flag

1 import express from 'express';
         ~~~~~~~

  node_modules/@types/express/index.d.ts:136:1
    136 export = e;
        ~~~~~~~~~~~
    This module is declared with 'export =', and can only be used with a default import when using the 'esModuleInterop' flag.


Found 1 error in src/index.ts:1

tsc コマンドでファイルを指定した場合には tsconfig.json ファイルが読み込まれませんが、tsc コマンドでファイルを指定しない場合は tsc コマンドがフォルダから ts ファイルを見つけてくれ tsconfig.json ファイルを利用してコンパイルを行うためコンパイルに成功します。


 % npx tsc

ts ファイルがフォルダが保存されている場所やコンパイル後に保存される js ファイルの場所を tsconfig.json ファイルで設定することができます。outDir には js ファイルを保存したい dist フォルダを設定し include には typescript ファイルを保存する”src/*/“を設定します。


{
  "compilerOptions": {
    /* Visit https://aka.ms/tsconfig to read more about this file */
//略
    "outDir": "./dist" /* Specify an output folder for all emitted files. */,
//略
  },
  "include": ["src/**/*"]
}

もし src フォルダに ts ファイルが存在しない状態で tsc コマンドを実行すると下記のエラーが表示されます。


 % npx tsc
 //略
error TS18003: No inputs were found in config file '/Users/mac/Desktop/node-myapp/tsconfig.json'. Specified 'include' paths were '["src/**/*"]' and 'exclude' paths were '["./dist"]'.

Found 1 error.

src フォルダに index.ts ファイルが存在する状態で”npx tsc”コマンドを実行すると dist フォルダが自動作成されその下に index.js ファイルが作成されます。

ts-node のインストール

ts-nodeのインストールを行います。


 % npm install -D ts-node

tsc コマンドを利用した場合は ts ファイルをコンパイルした後、動作確認を行うためには node コマンドを利用する必要がありました。ts-node を利用することで js ファイルを作成することなくファイルを実行することができるので開発効率を上げることができます。ts-node は tsconfig.json ファイルを読み込んで実行してくれます。


 % npx ts-node src/index.ts
Example app listening on port 3001!

nodemon のインストール

ts-node を利用することで ts ファイルを指定することでファイルを実行することができますがコードを更新しても再度 ts-node を実行する必要があります。nodemon はファイルの更新を検知して再読み込みを行なってくれるのでさらに開発効率を上げることができます。

nodemon のインストールを行います。


 % npm install nodemon --save-dev

インストールした nodemon にファイルを指定して実行します。メッセージにある通り nodemon を実行すると ts-node を認識して ts-node コマンドが実行されます。


 % npx nodemon src/index.ts
[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node src/index.ts`
Example app listening on port 3001!

index.ts ファイルを更新すると nodemon がファイルの更新を検知して即座に反映されます。

package.json ファイルの中の script にコマンドを追加します。本プロジェクトでインストールしたパッケージのバージョンも確認することができます。


{
  "name": "myapp",
  "version": "1.0.0",
  "description": "",
  "main": "index.js",
  "scripts": {
    "dev": "nodemon src/index.ts"
  },
  "keywords": [],
  "author": "",
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2"
  },
  "devDependencies": {
    "@types/express": "^4.17.17",
    "@types/node": "^20.4.1",
    "nodemon": "^2.0.22",
    "ts-node": "^10.9.1",
    "typescript": "^5.1.6"
  }
}

script に追加後は npm run dev コマンドを実行します。


 % npm run dev

> myapp@1.0.0 dev
> nodemon src/index.ts

[nodemon] 2.0.22
[nodemon] to restart at any time, enter `rs`
[nodemon] watching path(s): *.*
[nodemon] watching extensions: ts,json
[nodemon] starting `ts-node src/index.ts`
Example app listening on port 3001!

Express(Node.js)で TypeScript 環境を構築することができるようになりました。