【Node.js実戦習得】第6回:Node.jsプロジェクトへのTypeScript導入とtsconfig.jsonの設定

社員ブログ

1. Node.jsにおけるTypeScript環境の「不都合な真実」

酒本先輩!

いよいよここからTypeScript化ですね。

フロントエンドの時は、最初から用意された環境で当たり前のように型チェックの恩恵を受けていたんですけど、Node.jsだと何か違いがあるんですか?

いい着眼点ね。

まず最初に、熊木くんにバックエンド開発者としての『不都合な真実』を伝えておかなくちゃいけないわ。

……実はね、現行のNode.js本体は、TypeScript(.tsファイル)をそのまま直接実行することはできないのよ

えっ!?そうなんですか?

じゃあ、サーバーを動かす時はどうするんですか?

Node.jsが100%理解できるのは、あくまで純粋なJavaScript(.js)だけ。

だから、TSで書いたコードを本番サーバーで動かすには、一度JavaScriptに『トランスパイル(変換)』してからNode.jsに渡す、という2ステップが基本になるの。

でもさ、開発中にコードを1行書き換えるたびに、手動でコマンドを叩いてJavaScriptに変換して、それを実行する……なんてやっていられないでしょ?

確かに……!

保存したら一瞬で反映されて、すぐに動作確認したいです

そこで実務の開発環境(ローカル環境)では、『メモリ上で超高速にコンパイルしながら、まるで直接TSファイルをそのまま実行しているかのように見せるラッパーツール』を使うの。

昔は ts-node というツールが定番だったんだけど、最近の現場では、Go言語製の超高速コンパイラ(esbuild)を内蔵した tsx(TypeScript Execute) というツールを使うのが人気なの。

今回はこの tsx を使って、TS環境をゼロから組み立ててみましょう!

2. コラム:フロントの「拡張子(.tsx)」とNodeの「ツール(tsx)」の罠

……あの、酒本先輩。

ちょっと引っかかったんですけど、Node.jsの世界で tsx を使うんですか?

tsx って、僕らがフロントエンド(Reactなど)でHTML風のコンポーネントを書くときに使う、あの画面用のファイルの拡張子ですよね?

バックエンドで画面のツールを使うんですか……?

あはは!

やっぱりそこに引っかかったわね。

結論から言うと、フロントエンドの『.tsx(拡張子)』と、今回Node.js環境に導入する『tsx(ツール名)』は、名前が同じだけで全くの別物なのよ!

紛らわしくて本当にややこしいんだけど、バックエンドの実務でなぜこのツールが使われるのか、表でスッキリ整理してあげるわね。

単語正体主な役割・意味
.tsx(拡張子)ファイル形式TypeScriptのコードの中に、HTML風の構文(JSX)を混ぜて書くためのフロントエンド専用ファイル。
tsx(ツール名)実行コマンド / CLIツールTypeScript Execute の略。Node.jsでTypeScriptファイルをコンパイルしながら即座に実行するためだけの実行エンジン。

つまり、これから設定するコマンドは、『Reactの画面ファイルを動かす』という意味ではなく、『バックエンドのTypeScript(.ts)を、tsxという高速なツールを使って実行する』という意味なのよ

ああっ!

ツールの名前が tsx だったんですね。

てっきりNode.jsでReactの構文を動かすのかと勘違いして、頭の中がパニックになっていました。めちゃくちゃスッキリしました!

昔は ts-node という『ts』から始まる名前のツールが定番だったから、余計に今のエンジニアは混乱するのよね。

でも、ts-node はモダンなES Modules(第5回でやったimport/export)と相性が悪くて設定が地獄だったの。

その点、この tsx ツールは設定不要で import 構文が爆速で動くから、現代の現場ではこっちが主役になっているのよ

3. 【ハンズオン】Node.js × TS 開発環境をゼロから構築する

スッキリしたところで、新しく完全にTypeScriptファーストな実験用ディレクトリを作って、必要なパーツをインストールしていきましょう!

cd ~/nodejs-sample
mkdir node-ts-demo
cd node-ts-demo

# プロジェクトの初期化
npm init -y

次に、TypeScript本体と、今話した開発環境用の実行ツール tsx、外部型定義ファイルをインストールするわよ。

これらはアプリケーションが本番サーバーで動く時には不要で、開発する時にだけ必要なツールだから、--save-dev(または -D)オプションをつけて、開発時限定の依存関係(devDependencies)としてインストールするのが実務の鉄則よ!

npm install -D typescript tsx @types/node

お、@types/node ですね!

これ、フロントで外部ライブラリの型が見つからない時に入れていた @types/xxx の仲間ですか?

その通り!

これを入れないと、Node.js標準の機能(fspath、今後触る http などのコアモジュール)の型をTypeScriptが認識できなくて、コードを書いた瞬間に赤線(型エラー)だらけになっちゃうのよ。

Node.js環境でTSをやるなら、何よりも最初に入れるべき必須パーツね

4. 実務でそのまま使える!tsconfig.json の最適モダン設定

パッケージのインストールが終わったら、次はTypeScriptの挙動をコントロールする最重要ファイル tsconfig.json を生成するわよ

# tsconfig.jsonの雛形を自動生成
npx tsc --init

初期状態のファイルはコメントアウトだらけで読みづらいから、中身を全部消して、第5回で学んだ『ES Modules(import/export)』を前提とした、現代のNode.js開発におけるベストプラクティス設定に書き換えてみて!

{
  "compilerOptions": {
    /* コンパイル後のJavaScriptのバージョン */
    "target": "ES2022",
    /* Node.jsのモダンな仕様(ESM/CJS自動判別)に対応 */
    "module": "NodeNext",
    /* 外部モジュールの依存関係を正しく解決する設定 */
    "moduleResolution": "NodeNext",
    /* 昔のCommonJSライブラリもimport構文でスムーズに読めるようにする */
    "esModuleInterop": true,
    /* ファイル名の大文字小文字を厳密に区別(MacとLinuxの差分での事故防止) */
    "forceConsistentCasingInFileNames": true,
    /* 厳格な型チェックを全て有効化(安全性の最大化) */
    "strict": true,
    /* node_modules内の外部型定義ファイルのチェックを省いて高速化 */
    "skipLibCheck": true
  },
  /* srcフォルダ配下のTSファイルだけをコンパイル対象にする */
  "include": ["src/**/*"]
}

多くの入門記事だとここが "module": "commonjs" になっていることが多いけれど、それだと最新のライブラリが読み込めなくてエラーになるわ。

現代のNode.jsでは、モジュール周りの挙動を最新仕様に完全適合させる **NodeNext のコンビを指定するの!

5. package.json のアップデートと「tsx」での起動確認

最後に package.json を開いて、全体をモダン環境(ES Modules)に切り替える設定と、tsx ツールを使った起動スクリプト(npm scripts)を登録しましょう!

{
  "name": "node-ts-core",
  "version": "1.0.0",
  "type": "module",
  "scripts": {
    "dev": "tsx src/index.ts"
  },
  "devDependencies": {
    "@types/node": "^26.x.x",
    "typescript": "^6.x.x",
    "tsx": "^4.x.x"
  }
}

全ての設計図が整いましたね!

さっそくコードを書くための src フォルダと、本体となる index.ts を作ってみみます

mkdir src
touch src/index.ts

src/index.ts を開いて、簡単なTSコードを書いてみます。

型定義がちゃんと動いているか試したいので、型アノテーションを明示的に付けて、標準コアモジュールのOS情報をインポートしてみます!

// src/index.ts
const greeting: string = 'Hello, TypeScript × Node.js!';
console.log(greeting);

import os from 'node:os';
console.log('現在使用中のOSプラットフォーム:', os.platform());
npm run dev

実行結果

Hello, TypeScript × Node.js!
現在使用中のOSプラットフォーム: linux (※環境によります)

一瞬でコンパイルされて実行されました!

フロントエンドの時の開発体験と全く同じスピード感です!

6. 実践トラブルシューティング:本番ビルドで牙を剥く「TS2591」エラーの罠

酒本先輩!

開発用の tsx での起動はあんなにスムーズだったのに、本番用のテストとして公式コンパイラを直接叩く npx tsc を実行したら、エラーが出ちゃいました……!

src/index.ts:5:16 - error TS2591: Cannot find name 'node:os'. Do you need to install type definitions for node? Try `npm i --save-dev @types/node` and then add 'node' to the types field in your tsconfig.

そのままコードをコピーしたはずなのに、import os from 'node:os'; の場所で『Node.jsの型定義が見つからない』ってコンパイラに怒られます。

なぜですか?

あはは、さっそく踏んだわね!

これぞバックエンドTypeScriptの最初の洗礼、エラーコード TS2591 よ!

通常は moduleResolution: "NodeNext" があれば自動で型を探索しに行ってくれるんだけど、環境の微妙なパスの解釈の違いや、プロジェクトの階層のわずかな差で、コンパイラ(tsc)が @types/node を見落としてパニックを起こしちゃうことがあるの

これ、どうすれば直るんですか……?

エラーメッセージの中に最大のヒントが隠れているわよ。

and then add 'node' to the types field in your tsconfig.(tsconfigのtypesフィールドに’node’を追加してみて)って書いてあるでしょ?

今すぐ tsconfig.json を開いて、compilerOptions の中に types の指定を明示的に書き足してみて!

/* tsconfig.json の修正 */
{
  "compilerOptions": {
    "target": "ES2022",
    "module": "NodeNext",
    "moduleResolution": "NodeNext",
    "types": ["node"],  /* 👈 これを明示的に追加して強制ロック! */
    "esModuleInterop": true,
    "strict": true,
    "skipLibCheck": true
  },
  "include": ["src/**/*"]
}

追加して保存しました!

もう一度 npx tsc を叩いてみます……

あ、今度はエラーが出ずに、静かにコマンドが終了しました!

プロジェクト直下に、コンパイルされたきれいなJavaScriptのファイルが出力されています!

Okay!

この types: ["node"] は、TypeScriptに対して『何が何でも最初にNode.jsのグローバルな型定義を最優先で強制的に読み込みなさい!』と命令するものなの。

環境による型の見失いを発見したときは、この1行で強制ロックするのが実務の確実な回避策よ!

7. 実務での注意点①:本番環境では「tsx」は絶対に使わない!

これだけ快適なら、本番のサーバーを24時間動かす時も、開発時と同じ tsx コマンドのまま起動すればいいですね!

いいえ、本番環境では tsx は絶対に使ってはダメ(めっ、よ!)

これはあくまで『開発環境用』のツールよ

実務の運用フェーズ(Stage 7など)では、以下のような手順を踏むのが絶対の鉄則よ。

  1. 開発時(ローカル):
    tsx src/index.ts を使い、メモリ上で超高速コンパイルしながらガシガシコードを書く。
  2. 本番デプロイ時(ビルド):
    本番用に公式のコンパイルコマンド(npx tsc)を叩き、TypeScript(.ts)を本物のJavaScript(.js)ファイルとしてディスクに書き出す(←いま熊木くんがテスト成功させたやつね!)。
  3. 本番稼働(ランタイム):
    生成された純粋なJavaScriptを、Node.js本来のコマンド(node dist/index.js)で動かす。

tsx は実行するたびに毎回裏でコンパイルを行っているから、メモリを余計に消費するし、パフォーマンス的にも本番サーバーには向かないの。

本番は『事前にコンパイルし終えた純粋なJavaScript』をシンプルに node コマンドで動かすのが一番軽くて安全なのよ。

だからこそ、package.jsondevDependencies(開発時限定)のほうにインストールしたの

なるほど……!

だから本番用と開発用で依存関係が分かれていたんですね。

全ての点と線が繋がりました

8. 実務での注意点②:型アサーション(as)の乱用は「爆弾」を仕込む行為

環境の意図が分かったところで、最後にもう一つ。フロントエンド以上にデータに厳格でなければならないバックエンド開発において、初心者が一番やらかしやすい『TypeScript最大の禁忌』を教えておくわ。

熊木くん、フロントで型エラーを黙らせるために、コードの末尾に as any とか as User っていう『型アサーション』を乱用したこと、ない?

あ……。

APIから返ってくるデータの型がどうしても合わない時、使っちゃったこと、正直あります……

フロントエンドなら、最悪それでも自分のブラウザが落ちるだけで済む幕引きになるかもしれないわ。

でもね、バックエンドでそれをやるのは『時限爆弾』をソースコードに埋め込むのと同じ

 

型アサーション(as)は、コンパイラに対して『黙れ、私の言う型が絶対に正しいんだ』と脅迫する構文よ。

もし、外部のAPIやデータベースから予期せぬデータが飛んできて、中身の構造がスカスカだったとしても、as を使っているとTypeScriptはスルーしてしまう。

接着したフタを開けてみたら、実行時(ランタイム)に実体のないプロパティを参照した瞬間にエラーを吐き出して、稼働中のWebサーバー自体がその場で即死(クラッシュ)するわ。

 

Webサーバーが即死するということは、他のお客さん全員の通信も一瞬で遮断されるということ。

だから、バックエンドにおけるTypeScriptは、『コンパイルを通すための道具』ではなく、『動いているサーバーの命を守るための盾』

データの構造が不確定な場合は as で誤魔化すんじゃなくて、後々教える『Zod』のようなバリデーションライブラリを使って、実行時にも泥臭くデータチェックを行うのがバックエンド開発の鉄則なのよ。

本日のまとめ

  • Node.jsのTS実行: 本体はTSを直接実行できない。開発環境では、内部でメモリコンパイルを行う tsx ツールを導入して爆速で開発する。
  • 拡張子とツールの違い: フロントの拡張子 .tsx と、Nodeの実行ツール tsx は名前が同じだけの別物。
  • TS2591エラーの対策: tsc がNodeの型定義を見失う場合は、tsconfig.jsontypes["node"] を明示して強制読込させる。
  • 本番環境の鉄則: 本番環境では tsx は使わない。事前に tsc で純粋なJavaScriptにビルドし、node コマンドで安全・軽量に動かす。
  • バックエンドTSの覚悟: 型アサーション(as)でのエラー隠蔽はサーバー即死の引き金になる。型チェックは厳格に扱い、実行時エラーを徹底的に防ぐ!

次回:第7回「標準httpモジュールで自作する超シンプルWebサーバー(TS版)」に続く

タイトルとURLをコピーしました