UUID を生成しようと crypto.randomUUID() を呼んだら、古い Node で次のエラーが出て落ちることがあります。
TypeError: crypto.randomUUID is not a function
at Object.<anonymous> (/app/index.js:3:20)
コピペ検索用の表記ゆれです。いずれも同じ原因です。
TypeError: crypto.randomUUID is not a functioncrypto.randomUUID is not a functionrandomUUID is not a function
crypto 自体は読み込めているのにメソッドだけが無い出方なので、require/import の書き方を疑いたくなりますが、原因はモジュールの読み込みではありません。crypto.randomUUID が追加されたのが Node 14.17.0 からで、それより前の版には無いのが理由です。crypto モジュールはどの版にもありますが、randomUUID メソッドだけが古い版に存在しないため、is not defined ではなく is not a function という形で出ます。
これは典型的な版境界です。しかも境界が 14.16 と 14.17 という同じメジャー版の中にあるので、「Node 14 で動くはず」と思っていても、固定しているマイナー版によって結果が変わります。開発マシンや新しい CI では通るのに、FROM node:14.16 のような古い固定のコンテナでだけ落ちる、という形で現れます。
いつから crypto.randomUUID が使えるのか
crypto.randomUUID(RFC 4122 version 4 の UUID を返す)は Node 14.17.0 で追加され、15 系には 15.6.0 で入りました。16 以降はどの版にもあります。
| Node の版 | crypto.randomUUID | 挙動 |
|---|---|---|
| 14.16.x 以前 | 無し | TypeError: crypto.randomUUID is not a function |
| 14.17.0 以降の 14 系 | 有り | そのまま使える |
| 15.0.0 〜 15.5.x | 無し | TypeError: crypto.randomUUID is not a function |
| 15.6.0 以降の 15 系 / 16 以降 | 有り | そのまま使える |
追加が LTS(14)へ先にバックポートされた関係で、14 系は 14.17.0 から使えるのに、当時の最新だった 15 系は 15.6.0 まで入りませんでした。そのため 15.0〜15.5 は 14.17 より番号が大きいのに crypto.randomUUID が無い、という逆転が起きます。番号の大小ではなく「14.17 以降の 14 系 / 15.6 以降 / 16 以降」で判断してください。この「LTS へ先に入って番号が逆転する」形は crypto.hash is not a function とまったく同じ構図です。node:14.16.1 のように固定していたり、古いイメージがキャッシュに残っていたりすると、この境界より前で止まります。
再現(最小構成)
次の1ファイルを置きます。
// index.js
const crypto = require('crypto');
console.log(crypto.randomUUID());
これを Node 14.16 以前で node index.js すると TypeError: crypto.randomUUID is not a function が出て、終了コードは 1 になります。Node 14.17 以降なら UUID 文字列(3b12f1df-... のような形)が表示されます。同じコード・同じマシンでも、Node のマイナー版だけで結果が変わります。
解決
1. Node を 14.17 以降(実務では 18 以降の LTS)に上げる
crypto.randomUUID を使い続けるなら、実行する Node(CI・本番の Docker ベースイメージを含む)を上げます。API が入ったのは 14.17.0 ですが、14・15・16 はすでに EOL(サポート終了)なので、本番ではその時点でサポート中の LTS へ揃えます。Dockerfile の固定タグや CI のバージョン設定を見直してください。
2. uuid パッケージを使う(古い Node を残す場合)
古い Node をサポート対象に残すなら、UUID 生成で広く使われている uuid を使います。crypto.randomUUID と同じ version 4 の UUID を返します。
// index.js(uuid パッケージを使う)
const { v4: uuidv4 } = require('uuid');
console.log(uuidv4());
3. crypto.randomBytes から自前で作る(依存を増やしたくない場合)
パッケージを足したくないなら、どの版にもある crypto.randomBytes から version 4 の UUID を組み立てられます。ビット操作は RFC 4122 の version(3 番目のグループの先頭を 4 にする)と variant(4 番目のグループの先頭を 8〜b にする)を立てるためのものです(ハイフンを除いた 16 進では 13 文字目と 17 文字目にあたります)。
const crypto = require('crypto');
function randomUuid() {
if (typeof crypto.randomUUID === 'function') {
return crypto.randomUUID();
}
const bytes = crypto.randomBytes(16);
bytes[6] = (bytes[6] & 0x0f) | 0x40; // version 4
bytes[8] = (bytes[8] & 0x3f) | 0x80; // variant
const hex = bytes.toString('hex');
return [
hex.slice(0, 8),
hex.slice(8, 12),
hex.slice(12, 16),
hex.slice(16, 20),
hex.slice(20),
].join('-');
}
console.log(randomUuid());
crypto.randomBytes は暗号論的に安全な乱数を返すので、生成される UUID の質は crypto.randomUUID と同等です。Math.random() で UUID を作る例が出回っていますが、乱数の質が保証されないので使わないでください。
切り分け(似ているが原因が違うもの)
is not a functionとis not definedの違い:crypto.randomUUIDはcryptoオブジェクトのメソッドなので、無いときはis not a functionになります。一方fetchやstructuredCloneのようなグローバルは、無いときにReferenceError: ... is not definedです。メッセージの語尾は違いますが、どちらも「その版にその API がまだ無い」という同じ版境界です。fetchは ReferenceError: fetch is not defined の直し方、structuredCloneは ReferenceError: structuredClone is not defined の直し方 を参照してください。cryptoがundefinedで落ちた:Cannot read properties of undefined (reading 'randomUUID')のようにcrypto自体が無いなら、これは版境界ではなくrequire('crypto')(ESM ならimport)を書き忘れているか、ブラウザ環境で Node のcryptoを期待している可能性が高いです。- ブラウザ(Web Crypto)で
crypto.randomUUIDが無い:ブラウザ側のcrypto.randomUUIDはセキュアコンテキストでのみ使えます。セキュアコンテキストにはhttpsのほかhttp://localhost/127.0.0.1/file:も含まれるので、ローカル開発では動いても、httpで配信した本番ではundefinedになり得ます。この記事は Node 実行時の話ですが、ブラウザでも配信を https にする点を併せて確認してください。