TypeScriptでサーバー側の入力検証を安全に行う構成!初心者向けデータバリデーション徹底解説
生徒
「Webサイトやアプリで、ユーザーが入力したデータに間違いがないかチェックするサーバー側の仕組みを作りたいです。TypeScriptではどのように安全な構成を作れば良いですか?」
先生
「TypeScriptでは、型安全という仕組みと、データバリデーションという入力検証を組み合わせることで、非常に安全なサーバーの構成を作ることができますよ。」
生徒
「型安全やデータバリデーションという言葉は初めて聞きました。具体的にはどうやってプログラムを書くのですか?」
先生
「パソコンの操作が初めての方でも安心してください。まずは基本的な言葉の意味から、実際のプログラムの書き方まで、分かりやすい例えを交えながら順番に学んでいきましょう!」
1. サーバー側の入力検証とデータバリデーションとは?
インターネットで買い物をするとき、名前や住所、電話番号などを画面に入力します。このとき、入力されたデータが正しい形式かどうかを確かめる作業のことをデータバリデーション、または入力検証と呼びます。例えば、年齢を入れる場所に文字が入力されていたり、必須の項目が空っぽだったりしたときに、エラーを出して教えてくれる仕組みのことです。
このデータバリデーションは、ユーザーが操作する画面側だけでなく、データを保管する役割を持つサーバー側でも必ず行う必要があります。なぜなら、悪意のある人が画面の仕組みを無視して、おかしなデータを直接サーバーに送りつけてくる可能性があるからです。サーバー側でしっかりと鍵をかけるようにチェックすることで、システムが壊れたり、大切な情報が盗まれたりする危険を防ぎ、安全安心なアプリケーションを動かすことができます。
2. TypeScriptが持つ型安全の仕組みと限界
TypeScriptというプログラミング言語には、型という非常に便利な道具があります。型とは、データを入れる箱に貼るラベルのようなものです。この箱には数字しか入れてはいけません、この箱には文字しか入れてはいけません、というルールをあらかじめ決めることができます。このようにルールを厳しく守る性質を型安全と呼びます。開発者が間違えて数字の箱に文字を入れようとすると、パソコンが事前に「間違えていますよ」と教えてくれるため、ミスの少ないプログラムを書くことができます。
しかし、この型安全の仕組みには一つの限界があります。それは、プログラムを実行して動かしている最中には、そのラベルの効果が消えてしまうという点です。Webサイトから送られてくるデータは、プログラムが動いている最中にサーバーに届きます。そのため、TypeScriptの型の機能だけでは、実際に届いたデータが本当に正しい文字や数字であるかを、動的にチェックすることができません。そこで、動いている最中にもしっかりとデータを検査する専用の仕組みが必要になります。
3. 外部から届く未確定のデータを扱うunknown型
サーバーに届いたばかりのデータは、何が入っているか分からないお化け箱のような状態です。数字かもしれないし、文字かもしれないし、あるいは何も入っていないかもしれません。TypeScriptでは、このような何者か分からないデータを表現するためにunknown型という専用のラベルを用意しています。
以前のプログラミングでは、何でも許してしまう便利なラベルが使われることもありましたが、それだと危険なデータがそのまま通り抜けてしまいます。unknown型を使うと、パソコンは「中身をしっかり確認するまでは、このデータを使って処理をしてはいけません」と厳しく命令してくれます。これにより、開発者がうっかり未検証のデータを使ってしまうミスを、仕組みとして絶対に防ぐことができるようになります。
4. if文を使った手動の入力検証の基本
まずは、一番シンプルな方法として、条件分岐を行うための仕組みであるif文を使って、データの中身を手動で一つずつ確認するプログラムを書いてみましょう。データの種類を調べるために、今回は文字であるかを判定する仕組みを使います。
function checkUserName(inputData: unknown) {
if (typeof inputData === "string") {
console.log("入力されたデータは文字なので安全です。");
let userName: string = inputData;
console.log("ユーザー名: " + userName);
} else {
console.log("エラー: 文字以外のデータが届きました。");
}
}
checkUserName("たろう");
checkUserName(12345);
上記のプログラムでは、関数と呼ばれる処理の塊に対して、何が届くか分からないデータを入れています。typeofという道具を使うことで、データが文字であるかを調べています。実行すると、次のような結果が出力されます。
入力されたデータは文字なので安全です。
ユーザー名: たろう
エラー: 文字以外のデータが届きました。
このように、文字のときは安全な処理に進み、数字のときはしっかりとエラーを出して食い止めることができました。これが入力検証の最も基本的な形です。
5. 複数の項目を検証する複雑なプログラム
実際のWebサイトでは、名前だけでなく、年齢やメールアドレスなど、たくさんのデータを同時に送ることが一般的です。それぞれの項目について、文字であるか、数字であるか、適切な範囲に収まっているかなどを一つずつチェックしていく必要があります。今度は、ユーザーの登録情報を想定して、名前と年齢を同時に検証する少し複雑なプログラムを見てみましょう。
function registerUser(userData: any) {
if (userData && typeof userData === "object") {
let hasValidName = typeof userData.name === "string" && userData.name.length > 0;
let hasValidAge = typeof userData.age === "number" && userData.age >= 0;
if (hasValidName && hasValidAge) {
console.log("すべてのデータが正しいので登録に成功しました。");
console.log("名前: " + userData.name + "、年齢: " + userData.age);
} else {
console.log("エラー: 名前または年齢の形式が正しくありません。");
}
} else {
console.log("エラー: データがオブジェクトの形をしていません。");
}
}
registerUser({ name: "はなこ", age: 20 });
registerUser({ name: "", age: -5 });
このプログラムでは、データが物のかたまりであるオブジェクトであるかを調べた上で、名前が文字であり空っぽでないこと、年齢が数字でありゼロ以上であることを細かく条件分けして確認しています。実行結果は以下のようになります。
すべてのデータが正しいので登録に成功しました。
名前: はなこ、年齢: 20
エラー: 名前または年齢の形式が正しくありません。
正しくないデータが届いたときに、登録処理を行わずにエラーを出すことができました。しかし、このように手動でチェックする項目が増えると、プログラムの行数が長くなり、書くのが大変になってしまいます。
6. 安全性を高めるカスタム検証関数の作り方
プログラムが長くなる問題を解決するために、検証の処理を専門に行う独自の関数を作って切り分ける手法がよく使われます。TypeScriptには、ある関数が正しいと判定した場合に、そのデータの型を確定させることができる特殊なルールがあります。これを利用して、専用の判定部品を作ってみましょう。
interface Product {
title: string;
price: number;
}
function isProduct(data: any): data is Product {
return (
data !== null &&
typeof data === "object" &&
typeof data.title === "string" &&
typeof data.price === "number"
);
}
let item: unknown = { title: "ノートパソコン", price: 85000 };
if (isProduct(item)) {
console.log("商品データとして確定しました。");
console.log("商品名: " + item.title + "、価格: " + item.price + "円");
} else {
console.log("エラー: 商品データの型が一致しません。");
}
このプログラムでは、商品を表すルールとしてタイトルと価格を定義し、それを満たしているかを関数で確認しています。一度この関数を通過すると、パソコンはそのデータを安全なものとして認めてくれるため、その後の処理を安心して書くことができます。実行結果は次のようになります。
商品データとして確定しました。
商品名: ノートパソコン、価格: 85000円
独自の判定部品を作ることで、同じようなチェックを何度も使い回すことができるようになり、全体の構成がとてもすっきりします。
7. 安全なサーバー構成を実現するための設計思想
サーバー側のシステムをより安全にするためには、プログラムの書き方だけでなく、データの通り道を意識した設計が大切です。Webサイトから送られてきたデータは、まず最初に「バリデーションの壁」を通過するように構成します。この壁を突破したデータだけが、その奥にあるデータベースの保存処理や、計算を行う重要な処理へと進むことができるようにします。
入り口で悪いデータを完全にシャットアウトすることで、奥にあるプログラムはデータの心配をすることなく、本来の目的の処理に集中することができます。この「不純なものを入り口で弾く」という設計を行うことで、予期せぬ不具合が起きにくい、頑丈で安全なシステムを構築することができるのです。初心者の方も、まずはデータの入り口に厳しい門番を配置するイメージを大切に持ってください。