はじめに
業務でゼロからSPA(Single Page Application)をJavaScriptで作っていて、型がないのが辛くなってきたためTypeScriptを導入したら、開発効率が圧倒的に上がった、という経験からTypeScript推しになったものです。
型定義ができないのがJavaScriptで、型定義してもしないくてもいいのがTypeScriptなら、TypeScriptはJavaScriptの上位互換じゃね?と思うのです。関数の引数やクラスのメンバ変数って型が定義されていた方が嬉しいですよね?
しかし、巷にあるSPAの情報は大体がJavaScript。TypeScriptで書く人がもうちょっと増えてもいいと思うのです。
そこで、TypeScriptを使っていて優秀だなと気付いた瞬間を、こちらで紹介していこうと思います。
これはその一つ目。
WebAPIのシンプルな実装
WebAPIのデータ受け渡しが非常に楽に書けますよという話です。
フロントエンドとバックエンドが同じ言語で書けるのであれば、コードが共通化できたり、学習コストが少なくて済んだりと、開発効率が上がってすごく嬉しいことですよね。
Webアプリケーションの場合、フロントエンドの言語がほぼJavaScriptの一択である以上、フロントエンドとバックエンドを同じ言語で書けるのはJavaScriptもしくはAltJS(TypeScript,CoffeeScriptなど)しかありません。
型定義ができると非常に便利です。なのでTypeScriptなのです。
ではなぜ、型定義のできるTypeScriptだと便利なのか、という話からしていきます。
WebAPIにおける型定義の必要性
クライントからサーバに下記のデータクラスを送信するWebAPIがあるとします。
export default class User {
constructor(
private _name: string,
private _age: number,
){}
get name(): string {
return this._name;
}
get age(): number {
return this._age;
}
}
クライアント側ではこういったコードでサーバに送信します。
const user = new User("Taro", 20);
request.post(url).send(user).then(...);
もし何かのバグでageにstring型の"20"を入れようとしたら、TypeScriptの場合、型が誤っているのでビルドエラーになります。
const user = new User("Taro", "20"); //エラーになる
しかし、JavaScriptは実行してもエラーにならず、そのままサーバに送信されてしまいます。
サーバ側でこんなコードがあった場合、もしかするとバグに気づかないことがあるかもしれません。
if(user.age === 20) {
//user.age="20"だとここは通らない
}
クライアントからサーバにデータを渡す場面は、バグが発生しやすいポイントです。
型を定義することで、こういったデータの受け渡しの誤りを未然に防ぐことができます。
また、User.tsがAPI仕様そのものになっているという点も注目です。
値と型が定義できているので、クライント/サーバ間でどんなデータがやり取りされるのかが一目でわかります。
データクラスの共通化
ここから、クライアントとサーバ側で共通のデータクラスのコードを使用した実装を紹介します。
サーバ側はNode.jsでExpressを使用します。
クライアント側
import * as request from "superagent";
import User from "User";
const user = new User("Taro", 20);
request.post("http://localhost:3000/user")
.send(user)
.then((res) => {
//受信後の処理を書く
});
サーバ側
import * as Express from "express";
import User from "User";
const router = Express.Router();
router.post("/", (req, res) => {
const user: User = User.fromJSON(req.body);
...
res.json({status: true});
});
共通
export default class User {
constructor(
private _name: string,
private _age: number,
){}
get name(): string {
return this._name;
}
get age(): number {
return this._age;
}
public static fromJSON(obj: [key: string]: any): User {
return new User(
obj._name,
obj._age,
);
}
}
サーバ側はこれだけでは動きません。詳細は「TypeScript + Expressで実装するWebAPIサーバの実践的なソースコード構成」を参照ください。
ここでは、送受信部分のみを紹介しています。
クライアント側、サーバ側はUserというデータクラスを共有して使っています。
ポイントなのは、クライアント側はsend(user)、サーバ側はUser.fromJSON(req.body)というシンプルなコードでデータクラスを受け渡すことができてしまっているという点です。
クライアントはsuperagentを使っています。superagentのsendの引数はobject型です。実際はこれをJSON.stringifyしたものが送信されます。Userクラスの実体はobject型で、JSON.stringifyされれば{"_name": "Taro", "_age": 20}という文字列になります。
サーバ側は、ここのコード上には登場していませんが、body-parserというExpressのミドルウェアを使っています。そのおかげで、req.bodyはobject型になっています。
User.fromJSONでobject型をUser型に変換してあげれば、クライアントのデータクラスをそのままサーバ側に渡すことができます。
クライントとサーバの言語が異なると、それぞれでデータクラスを定義し、送信側はJSONに変換し、受信側はJSONをパースするコードを実装しなくてはいけません。それなりに手間がかかることで、それだけバグも生まれやすいです。
今回の例のように、ここまでシンプルで読みやすく実装できるのは、クライアントとサーバのコードを共通化でき、型が定義できるTypeScriptだからこそだと思います。
まとめ
データクラスを共通化することで、WebAPIが非常に楽に書けますという紹介でした。
WebAPIサーバの言語選択は、そのプロジェクトの要件や状況によって最善は違ってくると思いますので、必ずしもTypeScriptがいいというわけではありません。ただ、選択に迷うような状況の方がいるのでしたら、その方にTypeScriptが優秀であるという一面が紹介できていればいいなと思います。
関連
「TypeScript + Expressで実装するWebAPIサーバの実践的なソースコード構成」
「TypeScriptが優秀だと気付いた瞬間〜IDEとの相性抜群〜」