TypeScriptをバックエンドで使わない理由
はじめに
最近、以下の記事を見かけました。
記事の内容は、バックエンドも TypeScript で書きましょうという内容です。
たしかに、TypeScript は現代のフロントエンド開発においてデファクトスタンダードとも言えます。型安全性、補完の効きやすさ、そしてJavaScriptとの互換性。いずれも実務で使うには非常に便利です。フロントエンドではもはや必須とも言える存在です。
ただ、バックエンドという文脈においては、TypeScriptを選ばない理由もあるよね? と感じました。
TypeScript を否定したい訳ではなく、「あえて使わない理由」や「他の選択肢の方が合っている場面」について、バックエンドの性質や運用の観点から 考えてみたいと思います。
TypeScript自体は僕も好きですし、多くの現場で有効に活用されていることも理解しています。
正解がある話ではないと思うので、考え方の一例として読んでいただけたら嬉しいです。
TypeScriptのバックエンド活用に挙げられるメリット
まずは、TypeScript をバックエンドに使うメリットを整理してみます。
1. 型の共有ができる
バックエンドでTypeScriptを利用する利点の一つは、「そのままコードがスキーマになる」点だと思います。フロントとバックエンドの間でDTOなどの型を共通化できるのは大きなメリットです。
APIレスポンスやリクエストボディの型を共有する際、JSON SchemaやOpenAPI、GraphQLなどの形式のスキーマを定義せずに済むのは効率的です。
もちろん、OpenAPIやGraphQLでも、型定義のコードジェネレーターを使えば型共有は可能です。しかし、それらは別ファイルでの定義、コード生成の実行、同期の手間などが発生します。
Zod などのバリデーションライブラリと組み合わせれば、実行時バリデーションと型定義を統一できるのも便利です。
2. 同じ言語で開発できる
フロント・バックエンドを同じチームが開発する場合、同じ言語で開発できることでコンテキストスイッチの切り替えコストを削減でき、開発効率が向上します。
特にチームメンバーが TypeScript に慣れている場合、文法や構文に悩まずに開発を進められるのは大きなメリットです。
また、ツールを広く活用できるできる点も、TypeScript を選ぶ理由になると思います。
こうしたメリットは確かにあります。しかし、これは主に 短期的な開発効率や開発体験 の話です。
バックエンドに求められる性質
フロントとバックエンドでは、プロダクトライフサイクルに対する考え方が異なります。
観点 | フロントエンド | バックエンド |
---|---|---|
変更頻度 | 高い(UI改善・機能追加) | 低い(安定・堅牢性重視) |
技術トレンド | 流行に敏感 | 枯れた技術の採用が好まれる |
耐障害性 | 軽微な障害は許容されやすい | 障害は致命的になる |
運用期間 | 短〜中期 | 長期運用が前提 |
つまり、バックエンドにおいては「壊れない」「長く使える」「保守が楽」なことが非常に重要になります。
このような性質から、バックエンドでは「枯れている技術」が重宝されます。なぜなら、枯れている技術は次のような利点を持つからです。
- 運用実績が豊富
- 仕様が安定している
- ナレッジが多く、学習・トラブル対応がしやすい
- 時間が経っても壊れにくい
特に新規プロダクトでは、「新しい技術を使ったほうがいいのでは?」と思うこともあるかもしれませんが、「イケてる」技術より「枯れた」技術を選ぶことが最適な場合もあります。
「技術的に新しい=運用上の不確実性が高い」ことが多く、結果として開発チームの負担を増やすケースも少なくありません。
この観点に立つと、TypeScriptはバックエンドの求める性質と必ずしも噛み合っていないと感じます。
(型がない言語と比べれば TypeScript の型安全性は大きなアドバンテージで、堅牢であるとも言えますが。)
TypeScriptのつらみ
※ 追記:Node.js自体の後方互換性について、「LTS外れたら即使えないわけではなく、Node自体は後方互換にかなり配慮されている」との指摘をいただきました。
https://x.com/about_hiroppy/status/1927369914710413332
たしかに「Node.jsが壊れやすい」という印象の書き方は適切でなかったと思います。問題の本質はNode.js 本体ではなく、npmモジュール側のエコシステムであるという趣旨で修正しました。
1. ライブラリの後方互換性が壊れがち
TypeScript のエコシステムは進化が早く、新しいツール・ライブラリがどんどん登場しますが、その裏返しとして後方互換性を保たないメジャーアップデートが多く、バージョン管理がしんどいです。
例えば:
- ORM も頻繁に破壊的変更を含んだメジャーアップデートをする
- npm パッケージ群が Node.js のライフサイクルに強く依存しており、早いサイクルでの最低サポートバージョンの引き上げや、破壊的変更を含むアップデートが必要になる
これにより、バージョンを固定して放置しづらく、継続的に保守を強いられるという状態になります。
これらは、「一度作って長く動かす」ことが求められるバックエンドとは、やや相性が悪い印象です。
2. 長期運用に向かないエコシステム
npm エコシステムは流動性が非常に高く、依存パッケージも頻繁に更新されます。
TypeScript や Node.js 本体も頻繁にバージョンアップが行われていますが、これ自体は他の言語でもよくありますし、後方互換性を大事にしているので本質的な問題はそこではありません。
Node.js 本体は Express のような古いライブラリもサポートし続けており、互換性は意識されていています。つまり、Node.js そのものの安定性は比較的高いですが、問題の本質は npm のエコシステムに起因するケースが多いという認識です。
このような特徴の中で構築された TypeScript バックエンドは、放置することが難しく、継続的な保守が前提になりがちです。結果として、長期運用するバックエンドには不向きな側面があります。
参考として、様々な言語・環境のリリースサイクルや特徴を比較してみました。
言語・環境 | バージョンのリリースサイクル | 特徴 |
---|---|---|
TypeScript | 約2ヶ月ごと | 後方互換性を重視 |
Node.js | 約6ヶ月ごとのメジャーアップグレード | LTSは30カ月の長期サポート |
Go | 約6ヶ月ごと | 安定性重視、基本的に後方互換性の保証 |
Rust | 後方互換性を重視。edition で破壊的変更を制御 |
|
Python | 約1年ごと | LTSリリースは長期サポート対象 |
- https://devblogs.microsoft.com/typescript/typescripts-new-release-cadence/
- https://go.dev/doc/devel/release
- https://nodejs.org/ja/about/previous-releases
- https://devguide.python.org/versions/
3. 枯れたフレームワークが少ない
TypeScript で使えるバックエンドフレームワークは、以下のような傾向があります
- Express:軽量だけど型周りは微妙
- NestJS:重厚で学習コスト高め、抽象化が強め
- Hono:高速・軽量で思想も良いが、まだ若く事例も少ない
Ruby には Rails、Python には Django のような「枯れた選択肢」があるのに比べると、TypeScriptには「安心して選べるフレームワークやライブラリ」が少ないように感じます。
例えば、TypeScript 製 ORM の選定は悩ましいです。
結論: バックエンドにTypeScriptを選ばない選択肢もある
TypeScript をバックエンドでも使う選択は、開発体験やスピードを重視する開発にはフィットします。
一方、バックエンドでは「壊れないこと」が大事になります。その観点から見ると、TypeScript とその npm エコシステムは安定性や保守性を重視するバックエンドの価値観とは噛み合いにくいと感じます。
- 後方互換性・安定性
- メンテナンス負荷の少なさ
- フレームワークの信頼や脆弱性の問題
これらを重視するなら、TypeScript 以外の選択肢も十分に検討する価値があります。
技術選定は、目的やプロダクト特性・エンジニア組織に合った選択ができるようにしたいです。
まとめ
- TypeScript のバックエンド利用にはメリットがある(型共有、言語統一、開発体験など)
- 一方で、npm エコシステムの流動性やライブラリの保守性の観点から、長期運用・安定性には課題が多い
- バックエンドには「枯れてる」「壊れない」「保守が楽」なことが求められるが、TypeScript とその周辺環境はその価値観と必ずしも噛み合わない
この記事では「 TypeScript を選ばない」理由について観点を整理してみました。
もちろん、プロダクトの性質や開発チームのスキルセット、開発期間や体制などによって最適な選択肢は異なります。
とはいえ、TypeScriptを取り巻く環境は着実に進化しています。
最近では Hono のような高速で軽量なフレームワークや、Drizzle ORM のような型安全性に優れた ORM も登場しています。
また、Deno や Bun といった新しいランタイムも出てきています。
さらに、pnpm や Turborepo による効率的なモノレポ管理など、運用性や開発体験を改善するツールが登場し、実用性は高まっているように感じます。
個人的には、 TypeScript にとても期待しています。
いろいろな視点があると思うので、みなさんの考えや経験もぜひコメントなどで聞かせてもらえると嬉しいです。
Discussion
tsのバックエンドに求められる技術を客観的に定性的に、サポート期間などの定量的な事実を含めて淡々と書いてる記事見つけて安心しました。
あまりそういう記事無いんですよ。
感想ありがとうございます!
確かにそうかもしれません。よかったです。
「△ majorアップデートは基本的に後方互換がなくなっている可能性」
と
「◯ メジャーアップグレード以外は比較的安定」
は同じ話のような気がしました!
コメントありがとうございます!
そもそも不要な行だと思ったので表修正しました mm
自分はバックエンドはRails/Ruby一択です。
これは単に自分の開発、運用経験が長くエコシステムも整っているからです。
バックエンドはREST APIが提供できればいいので、運用経験があるもので実装するほうが良いと考えてます。なので、バックエンドこそ選択の自由があって良いなと思っています。
現状、フロントエンドに関してはReact+TypeScriptがベストと考えています。
自分一人でフロントエンドもバックエンドもやりますが、同じ言語で書きたいと思った事がありません。
それぞれに求められる特性が異なるためです。
もっと言うと、私はUnityでモバイルアプリを作っており、バックエンドはRailsで、公式ウェブサイトはReactで作っています。
本来であればコンテキストスイッチがどうこうって話ではないくらい大変ですが、昨今はChatGPTを始めとした生成AIもあるのでまったく問題ありません。
つまり、生成AIが存在する現代においてはコンテキストスイッチなんて存在しないのです。
なので、同じ言語で開発できるからコンテキストスイッチがどうこうってのは時代遅れであり、そこにこだわるのではなく、よりベストな開発、運用はなんだろうかというところに注力すべきかなと考えています。
また、エディタについてもReactだったらVisual Studio Codeがベストなのですが、RailsだったらRubyMineが最高なのでやっぱりプロジェクトごとに全然違うツール使うほうが良くて、なんでもかんでも同じ物を使いたいとはなりません。
※個人的な感想です😁
ちなみにRubyのライフサイクルはここで説明されてます。
グラフを見ると、ノーマルメンテナンス2年、セキュリティメンテナンスに移行してEOLを迎えるまでに3年くらいという感じなのかな。npmのエコシステムの何が悪いんだろう⋯?
yarnやbunで、package.jsonにresolutionを書けば(使っているライブラリが使う、それらが依存している同じライブラリのバージョンを固定すれば)、うまくいったり⋯?
例えば、言語のランタイムでもフレームワークでもライブラリでも脆弱性が見つかれば新しいバージョンに入れ替えるわけですが、バージョンあげる時に既存コードの修正が起きる頻度が高い傾向にあるか低い傾向にあるか、ですかね。
全くですね。フレームワークのバージョンアップも、フレームワーク次第ではあるもののメジャーアップデートでなければそうはコード修正が必要なケースも無いし、マイナーなライブラリでも使っているんじゃないか?と思ってしまいますね。
本文で言えばマイナーなライブラリを使って開発していたとしても、数年後にosのメインストリームが終わり、否応なしに言語のランタイムを入れ替える羽目になった際に、ほとんどのケースでランタイムだけ入れ替えれば良いような言語を「枯れてる」「壊れない」「保守が楽」と表現しているのだと思います。npmのエコシステムは、悪いという表現が適切かは微妙ですが、バックエンドでの利用が主となる言語と比べて枯れてないし、壊れやすくはあると思います
数年後となると、例えばJavaなどでもフレームワークのアップデートが必要になったりして、ランタイムの入れ替えだけでは動かないなんてことはザラにあるのですが、それでもですかね?
Goも然り。
Pythonなんて。。。ですし、どの言語もそこまで大きいと言えるほどの差はないように思えますけどね。
少なくとも5年間くらい、TypeScriptでバックエンドを記述してきましたけど、そこまで大きな壊れ方をした記憶がないですし、似た程度の壊れ方ならJavaなどでも起こる話なので、言うほどか?とすら思います。
観測範囲の違いでは?(少なくとも、そこまで一般化できるほどじゃないのではないかと思ってしまいます)
そうですね。一般論というわけではなく個々人の体験や観測した範囲の統計から私はこう思う、こういう考え方もあると思うといった類いの話かと
ですよね。
ただ、元の記事が少し主張が強いというか、そういうケース(というか考え方)もあるという話程度で、僕にはどの言語でも似たような話がありがちだなと思ってます。
ただ、書き方が難しいとはいえ、独り歩きし易い内容なので、とても引っかかるといったところです。
npmの問題ではなくNode.jsの標準ライブラリが薄すぎるのが問題なのです。このため他の言語では標準ライブラリでできることをいちいち誰かが作ったライブラリを入れなくてはなりません。
例えば最近にnative-fetchが追加されるまで Promiseベースの HTTPクライアントすら用意されておらず
たかがHTTPリクエストするだけで node-fetch やら axios やらサードパーティのライブラリを入れる人がほとんどでした。
(デフォルトのhttpクライアントを使うユーザはほとんどいませんでした。コールバックスタイルの primitive な実装で async/await で簡単に扱えないからです)
このようなことは他の標準ライブラリが充実したGoやC#といった堅牢な言語ではないことです。
他の言語ではランタイムを上げるだけで勝手に標準ライブラリも更新されますが、
Node.jsの場合1つ1つパッケージを更新する必要があるため、脆弱性対応においても非常に面倒なことになります。このことはエンタープライズ開発において非常に致命的です。
そのエンタープライズで5年間くらい使っているのですけどね…
APIが足らないとか、少し前のJavaと比べたら充実しているように思えますけどね。
本当に、観測範囲の違いで主語を大きくし過ぎじゃないですか?
どの言語やランタイムも一長一短ありますけど、それこそJavaなんかでもJVMのメジャーアップデートでAPI廃止や追加が行われて、外部ライブラリの更新が必要で、その外部ライブラリもメジャーアップデートしなければならずにコード修正が必要だとか、実際に起きた話です。
Goなんかのアプリでも、OSのAPIに依存していないにも関わらず、OSのアップグレードで動かなくて、対応した新バージョンのバイナリーが必要になったりすることに何度遭遇したことか…
どの言語、ランタイム、エコシステムでも起こる話なので、若干弱かったとはいえ、向かないと言い切るのは違うと思います。
自分は採用しないという理由としては、そう思うのでしょうから否定はする気はないですけどね。
辛口になってしまい申し訳ありません。
・TypeScript のデメリットが外部パッケージっていうのはちょっと変な話だと思います。それはTypeScirptのデメリットではなくて、あるプロジェクトで依存している外部パッケージの問題であって、TypeScriptの言語自体の問題ではありません。使ってるパッケージに不満があれば使ってる本人たちが自分で実装するなりforkして自分でメンテナンスするなりすれば良いのです。TypeScript自体に問題はありません。
・枯れた技術の話は、何を持って枯れていると判断するのかがかなりハイコンテキストに書かれていて、よくわかりません。
・どの言語で実装されたある目的を達成するどのライブラリもそれぞれ良し悪しがあって複数実装があるので、迷わずこれ一択、というのも基本的にはないです。
・「ライブラリがある」ことと「この言語でバックエンドを実装しても良い」ということはそんなに関係あるのでしょうか。それを重視しすぎると、言語そのものの特性を無視することになってしまわないでしょうか。例えばですが、ゲームサーバーなど並列処理が重要なプロジェクトにおいて、Elixirやgoなど並列処理が得意な言語でサーバーを実装するメリットは、その言語で実装されたあなたが気に入ったORMがないデメリットによって帳消しにされてしまうのでしょうか?
・AIを使うのは良いですがAIの出力そのままの箇所が散見されます。
私も勉強中ですし、あなた個人を批判する意図は決してありません。
あくまで記事の内容に関する批判と捉えていただければと思います。
また私自身の発言にも間違いがあるかと思いますのでその際はご指摘頂けますと幸いです。
言語というものはそれ単体で評価されるものではありません。必ずランタイムや標準ライブラリ、サードパーティライブラリなどのエコシステム全体を含め評価されるのです。
TypeScript をバックエンドで採用するということは ランタイムはNode.js を想定しているはずですが、最大の問題点は標準ライブラリがあまりにも小さすぎるとこにあります。
これにより大量のサードパーティライブラリが乱立しています。他の言語では標準ライブラリでできることをいちいち誰かが作ったライブラリを使わなくてはなりません。
これが諸悪の根源なのです。
TypeScript というよりは TypeScript + Node.js エコシステム全体の問題そのものです。
例えば C#/.NET などは非常に重厚な標準ライブラリがあるので Node.jsと比べるとサードパーティのライブラリはほとんど必要としません。GoもNode.jsに比べるとかなり標準ライブラリが充実しています。
そもそもNode.js 自体コミュニティベースで開発されているため、MicrosoftやGoogleがバックにいる C# や Go に勝てないのは当然のことです。
したがってバックエンド開発には 標準ライブラリが充実した枯れた堅牢な言語の方がよいでしょう。
なお「バックエンド」というのはWebAPIのみを指すわけではありませんのでご注意ください。
WebAPI用途では確かにNode.jsを採用するメリットは大きいでしょうが、「バックエンド」というより大きな領域においてこれといって採用する理由はあまりないのです。