e-Gov法令API v2をTypeScriptで使う:依存なしfetchで法令検索からJSON保存まで
e-Gov法令API v2をTypeScript(Node.js標準のfetch、依存パッケージなし)で呼び出し、法令名検索→法令ID確定→本文取得→JSON保存まで動く116行のサンプルを解説。Node.js 24.8・22.14・tsxの3通りで2026年9月14日に実行確認しました。
e-Gov法令API v2をTypeScriptから使うのに、HTTPクライアントもビルドツールも要りません。Node.js標準の fetch と URL.searchParams だけで、法令名検索→法令ID確定→本文取得→JSON保存まで116行で動きます。 依存がゼロなので、MCPサーバーや定期実行スクリプトの土台にそのまま流用できます。
この記事のサンプルは、Node.js 24.8.0(.ts をそのまま実行)、Node.js 22.14.0(--experimental-strip-types)、npx tsx の3通りで2026年9月14日に実行し、同じ結果になることを確認しています。Pythonで同じことをする手順はPython実装の記事に、使っているエンドポイントの一覧は逆引きリファレンスにあります。
laws→law_dataの2段階。 法令名から候補を取り、完全一致が1件のときだけ本文を取ります。- 日本語の文字コードは
URL.searchParamsに任せる。 curlで起きるShift_JIS混入が構造的に起きません。 - 保存JSONに出典情報を同梱する。 取得日時、URL、
asof、履歴IDを本文と同じファイルに入れます。 - 終了コードで結果を伝える。 0=成功、1=APIエラーや複数一致、2=引数不足。定期実行の分岐に使えます。
サンプルコード(116行)
egov-fetch.ts として保存してください。Node.js 24なら node egov-fetch.ts "労働基準法" で動きます。
// e-Gov 法令API v2: 法令名検索 → 法令ID確定 → 本文取得 → JSON保存// 実行例:// node --experimental-strip-types egov-fetch.ts "労働基準法" (Node.js 22.6 以降)// node egov-fetch.ts "労働基準法" 2025-06-01 (Node.js 24 以降は型ストリップが既定)import { mkdir, writeFile } from 'node:fs/promises';import path from 'node:path';
const BASE = 'https://laws.e-gov.go.jp/api/2';
type LawInfo = { law_id: string; law_num: string; law_type: string; promulgation_date: string;};
type RevisionInfo = { law_revision_id: string; law_title: string; abbrev: string | null; amendment_enforcement_date: string; current_revision_status: 'CurrentEnforced' | 'UnEnforced' | 'PreviousEnforced' | 'Repeal';};
type LawsResponse = { total_count: number; count: number; next_offset: number | null; laws: { law_info: LawInfo; revision_info: RevisionInfo }[];};
type LawDataResponse = { law_info: LawInfo; revision_info: RevisionInfo; law_full_text: unknown;};
type ErrorInfo = { code: string; message: string };
async function egovGet<T>(endpoint: string, params: Record<string, string> = {}): Promise<T> { const url = new URL(`${BASE}/${endpoint}`); for (const [key, value] of Object.entries(params)) url.searchParams.set(key, value);
const res = await fetch(url, { headers: { Accept: 'application/json' }, signal: AbortSignal.timeout(30_000), }); if (!res.ok) { const body = await res.text(); let detail = body.slice(0, 200); try { const err = JSON.parse(body) as ErrorInfo; detail = `${err.code} ${err.message}`; } catch { // JSON 以外(HTML の 400 など)はそのまま } throw new Error(`HTTP ${res.status} ${url.pathname}: ${detail}`); } return (await res.json()) as T;}
async function resolveLawId(title: string): Promise<LawInfo & RevisionInfo> { const data = await egovGet<LawsResponse>('laws', { law_title: title, law_type: 'Act', limit: '10' }); if (data.count === 0) throw new Error(`「${title}」に一致する法律がありません`);
const exact = data.laws.filter((l) => l.revision_info.law_title === title); if (exact.length === 1) return { ...exact[0].law_info, ...exact[0].revision_info };
const lines = data.laws.map((l) => ` ${l.law_info.law_id} ${l.revision_info.law_title}`); throw new Error(`完全一致が1件に絞れません(${data.total_count}件)。ID を指定してください:\n${lines.join('\n')}`);}
async function main(): Promise<void> { const [title, asof] = process.argv.slice(2); if (!title) { console.error('usage: node egov-fetch.ts <法令名> [asof=YYYY-MM-DD]'); process.exitCode = 2; return; }
const law = await resolveLawId(title); console.log(`law_id=${law.law_id} ${law.law_title} (${law.law_num})`);
const params: Record<string, string> = { json_format: 'light' }; if (asof) params.asof = asof; const data = await egovGet<LawDataResponse>(`law_data/${law.law_id}`, params);
const rev = data.revision_info; console.log(`law_revision_id=${rev.law_revision_id} enforced=${rev.amendment_enforcement_date} status=${rev.current_revision_status}`);
const outDir = path.join(process.cwd(), 'out'); await mkdir(outDir, { recursive: true }); const file = path.join(outDir, `${rev.law_revision_id}.json`); await writeFile( file, JSON.stringify( { fetched_at: new Date().toISOString(), source: `${BASE}/law_data/${law.law_id}${asof ? `?asof=${asof}` : ''}`, asof: asof ?? null, law_info: data.law_info, revision_info: rev, law_full_text: data.law_full_text, }, null, 2, ), 'utf8', ); console.log(`saved: ${file}`);}
main().catch((err: unknown) => { console.error(err instanceof Error ? err.message : String(err)); process.exitCode = 1;});型は type エイリアスと as キャストだけに絞ってあります。enum や namespace、コンストラクタの引数プロパティといった「型を消すだけでは済まない構文」を使っていないので、Node.jsの型ストリップで動きます。
実行結果
Node.js 24.8.0で、現行版と2025年6月1日時点を続けて取りました。
$ node egov-fetch.ts "労働基準法"law_id=322AC0000000049 労働基準法 (昭和二十二年法律第四十九号)law_revision_id=322AC0000000049_20260717_508AC0000000060 enforced=2026-07-17 status=CurrentEnforcedsaved: ...\out\322AC0000000049_20260717_508AC0000000060.json
$ node egov-fetch.ts "労働基準法" 2025-06-01law_id=322AC0000000049 労働基準法 (昭和二十二年法律第四十九号)law_revision_id=322AC0000000049_20250601_504AC0000000068 enforced=2025-06-01 status=PreviousEnforcedsaved: ...\out\322AC0000000049_20250601_504AC0000000068.jsonasof の有無で履歴IDが分かれ、ファイル名にもそのまま残ります。保存したJSONは fetched_at source asof law_info revision_info law_full_text の6キーで、source には https://laws.e-gov.go.jp/api/2/law_data/322AC0000000049?asof=2025-06-01 が入っていました。後から「どの時点の条文か」を確認できる形です。
実行環境の違い
同じファイルを3通りで動かしました。
新しく書くならNode.js 24でフラグなしが一番手数が少なく、既存のNode 22環境ならフラグを付けるだけで済みます。tsx を入れる必要はありませんでした。
設計で決めたこと
法令IDは完全一致でしか確定しない
laws は部分一致なので、「民法」で11件、「会社」で38件ヒットします。サンプルは revision_info.law_title が入力と完全一致する法令が1件のときだけ先へ進み、それ以外は候補を表示して止まります。
$ node egov-fetch.ts "会社"完全一致が1件に絞れません(38件)。ID を指定してください: 321AC0000000007 会社経理応急措置法 322AC0000000151 昭和二十二年法律第百五十一号(国際電気通信株式会社等の...) 323CO0000000402 会社等臨時措置法等を廃止する政令 ...(終了コード 1)「民法」は11件ヒットしても完全一致が 129AC0000000089 の1件だったので、そのまま本文取得に進みました。先頭1件を黙って採用する実装にしなかったのは、laws の既定の並びが law_info.law_id 昇順で、目的の法令が先頭に来る保証がないからです。通称で引けない場合の考え方は通称と法令名のズレの記事を参照してください。
文字コードは URL に任せる
curlでは、呼び出す殻によって日本語がShift_JISで送られて0件になる現象がありました(エラーと制限の記事)。URL.searchParams.set はUTF-8でパーセントエンコードするので、この問題が構造的に起きません。角括弧を含む elm=LawTitle[1] のような値も %5B %5D に自動でエンコードされます。
エラー本文を捨てない
res.ok でなければ本文を読み、JSONなら code と message を、JSON以外(HTMLの400など)なら先頭200文字をそのまま例外に載せます。不正な asof を渡したときの表示です。
$ node egov-fetch.ts "労働基準法" 2025-13-45law_id=322AC0000000049 労働基準法 (昭和二十二年法律第四十九号)HTTP 400 /api/2/law_data/322AC0000000049: 400004 日付(asof等)が誤っています。(終了コード 1)APIが返したコードと日本語メッセージがそのまま出るので、エラーコードの一覧と突き合わせられます。
process.exit ではなく exitCode
最初は catch の中で process.exit(1) を呼んでいました。すると法令名が見つからない場合に、Node.js 24.8.0(Windows)で Assertion failed: !(handle->flags & UV_HANDLE_CLOSING), file src\win\async.c, line 76 が出て終了コードが127になりました。fetchの内部ハンドルが閉じ切る前にプロセスを落としたためと見ています。process.exitCode = 1 に変えて自然終了させると解消し、終了コードも1になりました。非同期処理の直後に process.exit を呼ぶのは避けた方が安全です。
MCP や定期実行へ広げるとき
このサンプルは「1法令を1回取る」までですが、egovGet をそのまま使い回せます。
- MCPサーバーにする:
resolveLawIdとlaw_dataの呼び出しをツールに分け、返り値にlaw_revision_idとsourceを必ず含めます。XMLを扱う場合の型設計はXML→JSONの記事にあります。 - 改正を監視する:本文ではなく
law_revisionsを取り、履歴IDの集合を前回と比べます。実装と運用は改正チェックの定期実行に分けました。 - 本文を絞る:
law_dataにelm: 'MainProvision-Article_32'を足せば1条だけ(1.7KB)になります。全文を毎回保存する必要はありません。
まとめ
- Node.js標準の
fetchとURLだけで、法令名検索→ID確定→本文取得→JSON保存が116行、依存ゼロで動く - Node.js 24.8はフラグなし、22.14は
--experimental-strip-types、npx tsxの3通りで同じ結果 - 法令IDは完全一致1件のときだけ確定する。「会社」は38件で止まり、「民法」は1件に絞れた
- 保存JSONに
fetched_atsourceasoflaw_revision_idを同梱し、後から時点を検証できる形にする - 非同期処理の直後の
process.exitはNode 24.8で assertion を出した。process.exitCodeを使う
パラメータの組み立てを先に画面で試したい場合はe-Gov法令APIリクエストビルダー、エージェントから使う全体像はe-Gov法令API活用ガイドから辿れます。
関連して読む
mcp・egovを続けて読む
· 参考リンク 5件e-Gov法令XMLをJSONへ変換する方法:MCP用の型と入出力例
e-Gov法令XMLのArticle・Paragraph・SentenceをMCP向けJSONに変換する設計を、最小のXMLとJSON例、TypeScriptの変換コードで解説。条項の階層・基準日・出典を保持する方法が分かります。
egov・japanese-lawを続けて読む
· 参考リンク 3件e-Gov法令API v2エンドポイント逆引きリファレンス:6本の用途・パラメータ・実測レスポンス
e-Gov法令API v2の6エンドポイント(laws / law_revisions / law_data / keyword / law_file / attachment)を、やりたいこと別に逆引きできる形で整理。全エンドポイントをcurlで実行し、主要パラメータと応答を2026年9月14日に実測しました。
この記事の情報・検証メモ
- egov
- japanese-law
- legal-tech
- typescript
- api
- json
- mcp
- 公開日
- 情報確認
- 参考リンク
- 2件
- 更新性
- 定期更新
仕様・料金・提供範囲が変わりやすいテーマは、公開日・更新日・情報確認日を分けて管理します。 導入前には必ず記事末尾の一次情報と公式ドキュメントで最新状況を確認してください。