---
title: "サーバサイドTypeScriptを選ぶ前に向き合ってほしいこと"
date: "2026-06-26T22:00:00+09:00"
slug: "posts/2026/06/26/before-choosing-server-side-typescript"
description: "Coding Agentがコードを書く時代でも、アプリケーションコードを書くだけでは解決できない問題がある。サーバサイドTypeScriptが本当に自分たちの要求にフィットしているか、考えるきっかけにしてほしい。"
themes: ["typescript"]
private: false
---

私は、医療機関向けに幾多のサービスを展開する組織に所属し、認証基盤、ID基盤、ライセンス基盤、証明書基盤などを開発・運用するチームでテックリードをしている。この4年間、そうしたミッションクリティカルな領域のプラットフォームがサーバサイドTypeScriptで実現されているという状況に向き合い、苦しみ、そしてその苦しみを解決し続けてきた。そこから得た洞察を共有したい。

サーバサイドTypeScriptに限らず、せっかくこの記事を読んでくれている方々に対して、改めて技術やプログラミング言語に対する向き合い方を考え直す上でたった一つでもヒントを提供できたらうれしい。

## なぜ今もプログラミング言語へ関心を払うのか

さて、Coding Agentは、アプリケーションコードだろうがインフラだろうがDBのクエリだろうが、何でも設計して実装する。そうした環境にあって、昨今は「どの言語を選んでも目的を達成できる」という空気が広がっているように感じている。

だが、実行環境の特性、言語そのものの特性、非同期ランタイムの特性、ライブラリやフレームワークやSDKといったエコシステムの整備状況は、アプリケーションコードを書くだけではどうにも解決できない。アプリケーションコードをいくら書いても、例えばCPUバウンドな処理を苦手とする言語で複雑な計算処理をさせても性能は出ないし、VMの起動が遅い言語でサーバレス構成を選んでもスケールしないだろう。ビジネスやプロダクトの機能要求・非機能要求によってシステムに求められる能力は当然変わるし、それぞれの言語には必ず得意・不得意がある。コードをただ書き続ければそうした問題も解決するだろうという思考停止した態度をやめて、ビジネスとプロダクトと、何よりも技術ときちんと向き合い続けてほしい。

もちろん、求められる機能要求や非機能要求次第では言語の得意・不得意を無視できるかもしれない。そもそも自分たちでデータを持たないようなビジネスや、障害による影響が事業継続性へ大きな影響を与えないようなビジネスなら、とりあえず何の言語でもいいからコードを書いて、デプロイして、効果を測定して...というサイクルを高速に回していけばいいだろう。しかし、金融や医療、製造業や物流など、ミッションクリティカルな領域においてはその限りではないだろう。

## TypeScriptを選ぶ目的を明らかにする

### コード資産の共有

サーバサイドにTypeScriptを選ぶ理由として最もよく聞くのは、フロントエンドとバックエンドで型やスキーマを共有したいというものだ。

しかし、スキーマを共有したいだけなら、OpenAPIを含めていくらでもやり方がある。複雑なロジックをコードで表現して共有したい場合であっても、例えばWASMという選択肢もあるはずだ。そもそも、フロントエンドとバックエンドで本当に同じコードを動かす必要があるケースはどれだけあるだろうか。例えばオフラインでも稼働する医療システムの診療報酬計算ロジックのように、ネットワークが切断された状態でもクライアントサイドで同じ計算結果を再現しなければならないケースであれば、コードを共有する明確な理由がある。しかし、そうしたケースは限られている。

他にも選択肢がある中で、それでもTypeScriptを選ぶ理由があるならば、それを感覚的なものから体系立てられた言葉にすることが大事だ。

### 人材採用における母集団の広さ

フロントエンドからサーバサイドに加え、IaCや負荷試験ツール、ファームウェアに至るまで、様々な領域でTypeScriptが利用されていることから、TypeScriptを使用したことがある人間の数は増えている。それを踏まえ、「TypeScriptは人材を確保しやすい」とする企業もちらほら見かける。

しかし、果たして本当に採用したい人材は「TypeScriptの経験者」だろうか。様々な領域でTypeScriptが利用されているとしても、領域によって払うべき関心は違うから、求められる設計もかなり変わってくるはずだ。それなら、それに適合できる人材の範囲は「TypeScriptの経験者」よりもきっと狭いだろう。

例えば、非機能要求という観点で見れば、フロントエンドでは使用性が重要だ。想定しないエラーが発生した時には、わざわざResult型を伝搬するよりも、例外をぶん投げてそれをキャッチし途中までフォームに入力された情報をどこかへ退避させた上で適切なエラー画面を提示する方が良いというケースが多いだろう。

一方、バックエンドであれば機能完全性や可用性が重要であって、エラーが発生した時には問題の種類を正しく判別できるように伝搬し、それを踏まえてロガーや関連するサービスなどへ情報を伝達し、DBのトランザクションを正しくキャンセルし、規定されたインタフェース通りにレスポンスを返し、必要に応じてリソースを解放するはずだ。

言い換えれば、例外による大域脱出を伴わないエラーの表現や、トランザクションやコネクションプールなどの管理はバックエンドという領域に特有の関心である。ゆえに、同じTypeScriptという言語であっても、求められる考え方、設計やパターンは大いに変わりうるのだ。

加えて、同じ領域であったとしても、それに対する解決策は組織やチームによって大いに異なるだろう。デコレータとクラスを活用したオブジェクト指向を採用するチームもあれば、関数型ドメインモデリングに基づいた設計を採用するチームもあるだろう。もしかしたら今もこの言語をプロトタイプベースとして使い倒しているチームもあるかもしれない。

もちろん、様々なパラダイムへ柔軟に適応できる人材ならどのチームでも活躍できるのかもしれないが、大いなる力には大いなる予算が必要だ。サーバサイドTypeScriptを採用する上で「人材採用における母集団」を理由に挙げるのであれば、自分たちがチームに必要とする人物像を明らかにしておこう。

## 言語の特性と向き合う

TypeScriptを選ぶと決めたなら、この言語が持つ特性を理解した上で向き合う覚悟が必要だ。TypeScriptには構造的部分型、型消去、プロトタイプベースという3つの特性があり、それぞれが固有の落とし穴を生む。これまでこのテーマで何度も記事を書き登壇したが、改めて振り返っておく。

### 構造的部分型

TypeScriptの型の互換性は、クラス名ではなく構造で決まる。

```typescript
class User { name = "" }
class Product { name = "" }

const greet = (u: User) => `Hello, ${u.name}`;
greet(new Product()); // エラーなし
```

`User`を受け取る関数に`Product`を渡してもエラーにならない。プロパティの構成が同じだからだ。テストダブルの差し替えが容易になるなどの利点はあるが、意図しない型の混同を許してしまうリスクも持っている。

### 型消去

TypeScriptの型情報はトランスパイル時にすべて削除される。型検査の時点では高機能な型システムの恩恵を受けられるが、実行時には単なるJavaScriptだ。構造的部分型によって`Rectangle`型として受け入れられたオブジェクトに対して`instanceof Rectangle`がfalseを返すということが起こりえる。型検査時のメンタルモデルと実行時の振る舞いがずれる。

### プロトタイプベースとclassの限界

JavaScriptのclassはprototypeに基づいて構築されており、`this`の指す先は呼び出し方で動的に決まる。メソッドを変数に代入して呼び出した瞬間に`this`がundefinedになり、TypeErrorで落ちる。型検査はこの問題を検出しない。

ECMAScriptのclassの表現力は他言語に比べてかなり限定的だ。`#private`が入ったがTypeScriptの`private`とは別物だし、継承時のsetterの振る舞いも`useDefineForClassFields`というフラグで変わる。これはTypeScriptがES2015より3年先にclassを実装し、後からECMAScript仕様と統合する過程で生まれた歴史的産物だ。同じコードでもtsconfigの設定で振る舞いが変わるという状況は、この言語を選ぶなら理解しておかなければならない。

そして、型の表現力が高いということは、それだけ自由度も高いということだ。行き過ぎた抽象化や難解なメタプログラミングを誘発する原因にもなりえる。

### 私なりの乗り越え方

#### 全てを値で表現する

こうした特性と4年間向き合った結果、私はclassを使わずに全てを値で表現するアプローチに辿り着いた。TypeScriptが構造的部分型を採用しているのだから、あらゆる情報をプレーンなオブジェクトで表現すれば型検査時と実行時の挙動に悩まされることもほぼない。

```typescript
import { z } from 'zod'
import { UserId } from './userId.js' // Branded Type
import { UserDisplayName } from './displayName.js' // Branded Type

// エンティティは単なるオブジェクト
const userSchema = z.object({
  kind: z.literal('User'),
  id: UserId.schema,
  name: UserDisplayName.schema,
})

export type User = z.infer<typeof userSchema>

// 振る舞いは単なる関数
const rename = (user: User, name: string): Result<User, SameNameError> =>
  user.name === name ? err({ kind: 'SameNameError' }) : ok({ ...user, name })

export const User = {
  schema: userSchema,
  rename,
} as const
```

Branded Typeで構造が同じ型を区別し、Discriminated Unionで種別を判別し、thisを持たない関数で振る舞いを表現する方法だ。ただ、この方法も別に万能でもなんでもないし、注意点もある。詳細は下記の登壇資料や私が提供するCoding Agent向けスキルを見てほしい。

<a href="https://kosui.me/talks/2026/tskaigi-afterparty" target="_blank" alt="本当にTypeScriptのclassを使わずにシステムを運用できるの？ - kosui">
<img width="400" src="https://kosui.me/og/talks/2026/tskaigi-afterparty.png" />
</a>

<br />

<a href="https://iwasa-kosui.github.io/kamae-ts/ja/" target="_blank" alt="kamae-ts">
<img width="400" src="https://iwasa-kosui.github.io/kamae-ts/assets/og-image.png" />
</a>

いずれにせよ、TypeScriptの特性を理解した上で、自分たちの領域に合った付き合い方を見つけることが大事だ。

## 実行環境の特性と向き合う

ところで、言語そのものだけではなく、実行環境にも目を向けてほしい。

Node.jsはI/Oバウンドなタスクを得意としている。データベースや外部サービスとの通信が支配的なワークロードでは十分に力を発揮する。

一方で、CPUバウンドなタスクはシングルスレッドという構造からして苦手だ。あるリクエストのCPUバウンドな処理をCPUが捌いている間、他のリクエストは全て待たされることになる。

Worker Threadsを使えばマルチスレッド化できるという反論はあるだろう。しかし、スレッドプールの管理やスレッド間のメッセージングなど、考えるべきことはたくさんある。その複雑性を受け入れてまでNode.jsでCPUバウンドな処理を行う必要があるのか。その要求にもっと自然にフィットするランタイムや言語があるのではないか。

私が業務で扱っている認証基盤でいえば、パスワードのハッシュ化がまさにCPUバウンドな処理だった。医療システムの認証基盤としてセキュリティは最重要であり、パスワードのハッシュ化アルゴリズムについてもセキュリティ観点での妥協はしがたい。一方で、ハッシュ化ライブラリによっては、libuvを活用したマルチスレッド化ができていることもあれば、Worker Threadsを利用したマルチスレッド化をライブラリの利用者に委ねていることもある。これはまさに冒頭で述べたような、ビジネスやプロダクトの要求と言語や実行環境の特性が相反するケースだ。

ただ、CPUバウンドな処理が支配的であっても、AWS LambdaやCloudflare Workersなどのサーバレス環境で実行し、1つのリクエストに対して1つの実行環境を割り当てるなら、この問題はかなり緩和される。少なくとも、あるリクエストが他のリクエストに迷惑をかけることはなくなるだろう。

つまり、ビジネスやプロダクトの要求に対して、言語だけでなく実行環境の得意・不得意がマッチしているかよく考える必要がある。

## おわりに

最近、サーバサイドTypeScriptを辞める組織もあれば、今からサーバサイドTypeScriptに移行しようとする組織もある。

自分がそうした立場に立った時、チーム内外、そして組織内外へ説明責任を果たさなければならないだろう。本当にそうした移行をしなければならないのか。言語をスケープゴートにして本質的なプロダクト品質やチーム体制の課題から逃げていないか。目の前にある技術とどこまで向き合ったのか。

私は4年間、サーバサイドTypeScriptに非常に苦労しながら向き合い続けて、やっと自分たちのやり方が見えてきた。安易に選ぶのでも安易に辞めるのでもなく、ビジネスとプロダクトと、何よりも技術ときちんと向き合い続けたい。
