遺伝子情報検索ツール — APIとJSONを活用した検索ボックスの作成
このトピックを終えると
教科書で学んだ API と JSON を組み合わせて、遺伝子名を入力すると、公開データベースから機能、位置、別名を取得してカード形式で表示する 検索ウィジェットを自分で作成できます。「他人が作成した巨大なデータベースを自分のアプリで使用する」ということが、実際にどのようなコードになるのかを理解します。
この記事は 教育用の一般的な例 です。公開されている無料の遺伝子注釈 API を題材に、ウェブから外部データを取得する一般的なパターンを学びます。
「この遺伝子は何だったかな」——毎回サイトを探し回る問題
実験をしていると、遺伝子名は知っているけれど、詳細が思い出せないことがよくあります。『TP53』はどの染色体にあったかな?『BRCA1』の正式な別名は何だったかな?
そのたびに、ブラウザを開いて、公開データベースサイトにアクセスし、検索窓にキーワードを入力して、結果ページを閲覧します。一度や二度ならまだ良いですが、50個の遺伝子を確認しなければならないとしたら? 50回タブを開けたり閉じたりする地獄が始まります。
そこで開発者の発想が生まれます。「あのサイトも結局、どこかのデータベースからデータを取得して表示しているだけだ。そのデータベースに直接問い合わせることができないだろうか?」それが API です。
まずは完成品を見てみましょう(ブラックボックスを先に実行する)
私たちが作成するウィジェットは、次のように動作します。検索ボックスに遺伝子名を入力すると――
┌─────────────────────────────────────────┐
│ 🔍 [ TP53 ] [検索] │
├─────────────────────────────────────────┤
│ TP53 (tumor protein p53) │
│ ───────────────────────────── │
│ 📍 位置 : 17p13.1 │
│ 🏷️ 別名 : p53, LFS1, BCC7 │
│ 🧬 要約 : 細胞周期の制御、腫瘍抑制 │
└─────────────────────────────────────────┘ウェブサイトを開かなくても、検索ボックス一つで情報が表示されます。これを実現するために必要なのは、たった2つの概念――どこに問い合わせるか(API)、回答をどのように読み取るか(JSON)です。
このツールはどのような部品で構成されているか(部品分解図)
遺伝子情報検索ウィジェット
┌─────────────────────────────────────────────────────┐
│ [質問] リクエストURLの組み立て ──── 部品: API │ ← 自分で作成 ★
│ │ │
│ ▼ │
│ [送信] fetchで呼び出し ───── 部品: fetch │ ← 完成品として提供(ツール)
│ │ + 非同期 │
│ ▼ │
│ [解釈] 応答のパース ──────── 部品: JSON │ ← 自分で作成 ★
│ │ │
│ ▼ │
│ [出力] カードのレンダリング │
└─────────────────────────────────────────────────────┘| 部品 | どこで学んだか | このツールで何をするか |
|---|---|---|
| API | api-basics | 「何を、どこに尋ねるか」URLの作成 |
| fetch/非同期 | ajax-fetch, sync-vs-async | 実際にリクエストを送信し、待機 |
| JSON | json-data-format | 戻ってきた応答から必要な値を取り出す |
📌 これらの概念を初めて学ぶ場合(上部のリンク)
自分で作成する新しい概念は、APIとJSONの2つだけです。 実際の通信を行うfetchはツールとして完成品を提供します。
🔎 APIを一言で表すと(ドロー — api-basics) APIはレストランのメニューです。厨房(DB)に直接入ることはできませんが、メニュー(API)に定められた方法で注文すると、料理(データ)が出てきます。私たちは、「この遺伝子情報をください」をメニューのルールに合わせて注文する方法を学びます。
制作步骤 1 — 组装 URL 以询问所需信息 ★ (API)
✍️ 手动填写的部分。 组件 = API。目标:将“提供此基因信息”转换为 API 可以理解的 URL。
向 API 发送请求通常使用 URL。“地址 + 所需内容”按照规则组合即可。例如,假设某个公开的基因 API 具有以下规则。
https://example-api.org/query?q={基因名称}&fields={所需项目}q=后面是搜索词(基因名称)fields=后面是想要获取的项目(用逗号分隔)
如果手动创建,可能会出现拼写错误或编码错误。因此,我们将创建一个安全组装 URL 的函数。这里重要的是 encodeURIComponent——它可以包裹搜索词,即使搜索词中包含空格或特殊字符,URL 也不会损坏。
const API_BASE = "https://example-gene-api.org/query";
function buildQueryUrl(geneName, fields = ["symbol", "name", "genomic_pos", "alias", "summary"]) {
const q = encodeURIComponent(geneName.trim());
const f = encodeURIComponent(fields.join(","));
return `${API_BASE}?q=${q}&fields=${f}`;
}
// 验证
console.assert(
buildQueryUrl("TP53") === "https://example-gene-api.org/query?q=TP53&fields=symbol%2Cname%2Cgenomic_pos%2Calias%2Csummary",
"基本 URL 组装失败"
);
// 即使是带有空格的搜索词也应安全地进行编码
console.assert(
buildQueryUrl(" TP 53 ").includes("q=TP%2053"),
"空格编码失败"
);第二个 assert 是关键。即使用户输入 TP 53 这样的空格,也会安全地编码为 TP%2053。如果不使用 encodeURIComponent,空格会将 URL 分成两部分,导致请求失败。API 调用错误的 50% 是由于遗漏了这种编码。
🤔 自我解释提示 为什么使用了
geneName.trim()?考虑一下,如果用户在搜索框中输入"TP53 "(末尾带有空格),如果没有trim,API 会如何接收。 (与前面的引物部分中对序列进行规范化处理的理念相同——在发送之前统一格式。)
2段階目 — 実際にリクエストを送信する(fetch、完成版)
実際にリクエストを送信し、応答を待つ部分はツールなので、完成版を提供します。ajax-fetchとsync-vs-asyncで学んだfetch + async/awaitをそのまま使います。
async function callGeneApi(geneName) {
const url = buildQueryUrl(geneName);
const response = await fetch(url); // ← ネットワーク往復。待機する(await)
if (!response.ok) {
throw new Error(`APIエラー: ${response.status}`);
}
return await response.json(); // ← 応答本文をJSONオブジェクトに変換
}🔎 なぜasync/awaitなのか?(ドロー — sync-vs-async) ネットワークリクエストは時間がかかります(サーバーとの往復)。その間、ブラウザが停止してはいけません。
awaitは、「これを完了するまで待つが、その間は画面がフリーズしないようにする」という処理を行います。レストランで注文し、呼び出しベルを受け取って席に座って待つようなものです。料理ができるまで立ちっぱなしで待つ必要はありません。
この関数の最後の行 response.json() が、次の段階に進むための橋渡しとなります。サーバーが送信した文字列の塊(テキスト)を、扱いやすいJavaScriptオブジェクトに変換します。そのオブジェクトを調理するのが3段階です。
ステップ 3:必要なものだけを抽出する ★(JSON)
✍️ 手動で記述する箇所。 部品 = JSON。目的:複雑な応答から、必要な値だけを安全に抽出する。
API が返す JSON は、通常、私たちが望むものよりもはるかに大きく複雑です。実際の応答は、大体以下のようになっているとします。
{
"hits": [
{
"symbol": "TP53",
"name": "tumor protein p53",
"genomic_pos": { "chr": "17", "start": 7668402, "end": 7687550 },
"alias": ["p53", "LFS1", "BCC7"],
"summary": "細胞周期の調節と腫瘍抑制に関与する遺伝子。"
}
]
}ここで、カードに表示するものは、名前、位置、別名、概要程度です。JSON を調べて、必要な値だけを抽出し、きれいなオブジェクトに整理する関数を作成します。このとき、必ず注意すべきこと — 存在しない可能性のある値を安全に処理することです。一部の遺伝子には別名がなく、一部の応答には結果が全く含まれていない場合があります。
function parseGeneResponse(data) {
const hit = data.hits && data.hits[0];
if (!hit) {
return null; // 結果がない — 呼び出し元で処理
}
const pos = hit.genomic_pos || {};
return {
symbol: hit.symbol || "?",
name: hit.name || "",
location: pos.chr ? `chr${pos.chr}:${pos.start}-${pos.end}` : "情報なし",
aliases: Array.isArray(hit.alias) ? hit.alias : [], // ない場合は空の配列
summary: hit.summary || "概要情報がありません。",
};
}次に、モック応答で検証します。実際のネットワークを使用せずに、解析ロジックが正しいかどうかを確認できます。これは優れた設計です — 純粋なデータ処理(解析)をネットワークから分離しておくと、テストが容易になります。
const mockResponse = {
hits: [{
symbol: "TP53",
name: "tumor protein p53",
genomic_pos: { chr: "17", start: 7668402, end: 7687550 },
alias: ["p53", "LFS1", "BCC7"],
summary: "細胞周期の調節と腫瘍抑制に関与する遺伝子。",
}],
};
const parsed = parseGeneResponse(mockResponse);
console.assert(parsed.symbol === "TP53", "シンボル解析失敗");
console.assert(parsed.location === "chr17:7668402-7687550", "位置の組み立て失敗");
console.assert(parsed.aliases.length === 3, "別名解析失敗");
// 結果がない応答も安全に処理する必要がある
console.assert(parseGeneResponse({ hits: [] }) === null, "空の結果の処理失敗");
console.assert(parseGeneResponse({}) === null, "空のオブジェクトの処理失敗");
// 別名がない遺伝子でもエラーにならないようにする
const noAlias = parseGeneResponse({ hits: [{ symbol: "X", genomic_pos: { chr: "1", start: 1, end: 2 } }] });
console.assert(Array.isArray(noAlias.aliases) && noAlias.aliases.length === 0, "別名がない場合の処理失敗");最後の 3 つの assert が、初心者と上級者を区別します。正常な場合だけを処理すれば初心者で、存在しない場合、空の場合、壊れた場合まで処理すれば上級者です。hit.alias が存在しない場合、そのまま使用すると undefined.length でアプリがクラッシュします。Array.isArray(...) ? ... : [] という 1 行のコードが、その事故を防ぎます。
🤔 自己説明プロンプト
data.hits && data.hits[0]の&&はなぜ使用したのでしょうか?もしdata.hitsが全く存在しない(undefined)応答が来た場合、この防御がなければどのようなエラーが発生するかを説明してください。(前のプライマーの「存在しない可能性のある値」と同じテーマです。)
部品を一つに — 完成されたウィジェット
URLの組み立て(API)→呼び出し(fetch)→パース(JSON)→レンダリングを繋ぎます。
function renderGeneCard(gene) {
if (!gene) return "検索結果がありません。";
return [
`${gene.symbol} (${gene.name})`,
`📍 位置 : ${gene.location}`,
`🏷️ 別名 : ${gene.aliases.join(", ") || "なし"}`,
`🧬 要約 : ${gene.summary}`,
].join("\n");
}
async function searchGene(geneName) {
try {
const raw = await callGeneApi(geneName);
const gene = parseGeneResponse(raw);
return renderGeneCard(gene);
} catch (e) {
return `エラーが発生しました: ${e.message}`;
}
}
// レンダリングロジックもモックで検証(ネットワーク不要)
const card = renderGeneCard(parseGeneResponse(mockResponse));
console.assert(card.includes("TP53"), "カードのレンダリングに失敗");
console.assert(card.includes("chr17:7668402-7687550"), "カードの位置のレンダリングに失敗");
console.assert(renderGeneCard(null) === "検索結果がありません。", "空の結果のレンダリングに失敗");searchGene全体がtry/catchで囲まれていることに注目してください。ネットワークはいつでも失敗する可能性があります(サーバーダウン、インターネット接続の切断)。その時、アプリ全体が停止するのではなく、「エラーが発生しました」と表示する — これが実用的なツールと玩具の違いです。
別の道もある(マルチパス推論)
- 複数の遺伝子を同時に検索: 遺伝子を50個、一つずつ
awaitすると遅くなる(逐次処理)。Promise.all([...])を使って同時に実行すると、はるかに高速になる。トレードオフ: サーバーに負荷をかける可能性があるため、公開APIには1秒あたりのリクエスト数の制限(レート制限)がある。 - キャッシュ: 同じ遺伝子を再度検索する場合、APIを再度呼び出すのではなく、保存しておいた結果を使用する。前回の辞書(配列→値)と同じ考え方。ここでは
遺伝子名→結果。 - バックエンドプロキシ: APIキーが必要なサービスの場合、キーをブラウザに置くと、キーが公開されてしまう。そのため、実務では、サーバーを介してAPIを呼び出す。→応用編「遺伝子検索バックエンド」に続く。
要点: フロントエンドで外部APIを直接呼び出すことは、高速化に役立つが、**速度(同時リクエスト)、再利用(キャッシュ)、セキュリティ(キー)**の3つの要素において、それぞれより良い選択肢が存在する。
次のステップへ(下部のリンク)
- 検索結果をデータベースに保存し、高速に再度検索するには→DBインデックスを連携させた応用編配列DBインデックス
- リアルタイムで結果をプッシュしたい場合は→WebSocket
- 同じ結果を再利用(キャッシュ)するための基礎→リストと辞書
実際にやってみよう(独立した課題)
- ローディング状態: 検索中に「検索中…」を表示し、完了したら結果に置き換えてください。(非同期の「待ち時間」をUIで表現)
- キャッシュの追加: 既に検索した遺伝子がAPIを再度呼び出さないように、オブジェクト(
{})に保存して再利用してください。console.assertで2回目の呼び出しがキャッシュを使用していることを確認します。 - 複数の遺伝子: カンマで区切られた複数の遺伝子名を受け取り、
Promise.allを使用して同時に検索します。 - チャレンジ: APIの応答形式が変更され、
genomic_posが配列(複数の位置)で返される場合があります。parseGeneResponseが配列であろうとオブジェクトであろうと、安全に処理できるように修正してください。
まとめ
「毎回サイトで遺伝子情報を検索する」という手間を、APIを1つ使ってアプリに組み込む検索ウィジェットに置き換えました。
- APIは、「何をどこに尋ねるか」をURLで表現します。(質問)
- fetch/非同期は、リクエストを送信し、画面をフリーズさせずに待ちます。(送信)
- JSONは、複雑なレスポンスから必要な値だけを、存在しない値も含めて安全に取り出します。(解釈)
APIとJSONは、別々に学ぶときは抽象的ですが、組み合わさると世界のすべての公開データベースが、自分のアプリの材料になります。遺伝子データベースであろうと、論文データベースであろうと、タンパク質構造データベースであろうと、パターンは常に同じです。注文URLを作成し、送信し、回答をパースします。
この記事は一般的な教育例です。実際のサービスでは、認証、リクエスト制限、エラー再試行、ページネーションなどが追加されます。その詳細なバージョンは、この基本構造の上にあなたが追加することができます。