このチュートリアルでは、REST API を使ってデータベースを D1 にインポートする方法を学びます。
- Cloudflare アカウント ↗ に登録します。
Node.js↗ をインストールします。
Node.js のバージョンマネージャー
権限の問題を避け、Node.js のバージョンを切り替えられるよう、Volta ↗ や nvm ↗ などの Node バージョンマネージャーを使います。このガイドの後半で説明する Wrangler には、Node バージョン 16.17.0 以降が必要です。
REST API を使うには、リクエストを認証するための API トークンが必要です。Cloudflare ダッシュボードから作成できます。
-
Cloudflare ダッシュボードで API Tokens ページを開きます。
Account API tokens を開く ↗ -
API Tokens で Create Token を選びます。
-
Custom token > Create custom token までスクロールし、Get started を選びます。
-
Token name にわかりやすいトークン名を入力します。例:
Name-D1-Import-API-Token。 -
Permissions で次を選びます。
- Account を選びます。
- D1 を選びます。
- Edit を選びます。
-
Continue to summary を選びます。
-
Create token を選びます。
-
API トークンをコピーし、安全なファイルに保存します。
- Cloudflare ダッシュボードで API トークンを作成する詳細は、API トークンを作成 を参照してください。
- API 経由で API トークンを作成する詳細は、API 経由でトークンを作成 を参照してください。
インポートするデータのスキーマと一致する D1 テーブルが、あらかじめ必要です。
このチュートリアルでは次を使います。
- データベース名
d1-import-tutorial - テーブル名
TargetD1Table TargetD1Table内の 3 つの列id、text、date_added
テーブルを作成する手順は次のとおりです。
-
Cloudflare ダッシュボードで、D1 ページに移動します。
D1 SQL database を開く ↗ -
Create database を選択します。
-
データベースに名前を付けます。このチュートリアルでは、D1 データベース名を
d1-import-tutorialにします。 -
(任意)ロケーションヒントを指定します。ロケーションヒントは、データベースの希望する地理的な場所を示す任意のパラメーターです。詳細は ロケーションヒントを指定する を参照してください。
-
Create を選択します。
-
Console に移動し、次の SQL スニペットを貼り付けます。
TargetD1Tableというテーブルが作成されます。DROP TABLE IF EXISTS TargetD1Table; CREATE TABLE IF NOT EXISTS TargetD1Table (id INTEGER PRIMARY KEY, text TEXT, date_added TEXT);代わりに Wrangler CLI も使えます。
# Create a D1 database npx wrangler d1 create d1-import-tutorial # Create a D1 table npx wrangler d1 execute d1-import-tutorial --command="DROP TABLE IF EXISTS TargetD1Table; CREATE TABLE IF NOT EXISTS TargetD1Table (id INTEGER PRIMARY KEY, text TEXT, date_added TEXT);" --remote
-
新しいディレクトリを作り、Node.js プロジェクトを初期化します。
mkdir d1-import-tutorial cd d1-import-tutorial npm init -y -
このリポジトリに
index.jsという新しいファイルを作成します。このファイルに、REST API で D1 データベースへデータをインポートするコードを書きます。 -
index.jsで次の変数を定義します。TARGET_TABLE: 取り込み先のテーブル名ACCOUNT_ID: アカウント ID。Workers & Pages の Account Details を参照してください。DATABASE_ID: D1 のデータベース ID。データベースを開くと確認できます。D1_API_KEY: 手順 1 で作成した D1 API トークン
index.jsjs const TARGET_TABLE = " "; // for the tutorial, `TargetD1Table` const ACCOUNT_ID = " "; const DATABASE_ID = " "; const D1_API_KEY = " "; const D1_URL = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/d1/database/${DATABASE_ID}/import`; const filename = crypto.randomUUID(); // create a random filename const uploadSize = 500; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${D1_API_KEY}`, };
実務では、D1 にインポートしたいデータがすでに手元にあることがあります。
このチュートリアルでは、インポート手順を示すためにサンプルデータを生成します。
-
@faker-js/fakerモジュールをインストールします。npm i @faker-js/fakeryarn add @faker-js/fakerpnpm add @faker-js/fakerbun add @faker-js/faker -
index.jsの先頭に次のコードを追加します。このコードは、要素数 2500(uploadSize)のdata配列を作ります。各要素はid、text、date_addedを持つオブジェクトです。各要素がテーブルの 1 行に対応します。index.jsjs import crypto from "crypto"; import { faker } from "@faker-js/faker"; // Generate Fake data const data = Array.from({ length: uploadSize }, () => ({ id: Math.floor(Math.random() * 1000000), text: faker.lorem.paragraph(), date_added: new Date().toISOString().slice(0, 19).replace("T", " "), }));
-
取り込み先テーブルへデータを挿入する SQL コマンドを生成する関数を作成します。この関数は、前の手順で作った
data配列を使います。index.jsjs function makeSqlInsert(data, tableName, skipCols = []) { const columns = Object.keys(data[0]).join(","); const values = data .map((row) => { return ( "(" + Object.values(row) .map((val) => { if (skipCols.includes(val) || val === null || val === "") { return "NULL"; } return `'${String(val).replace(/'/g, "").replace(/"/g, "'")}'`; }) .join(",") + ")" ); }) .join(","); return `INSERT INTO ${tableName} (${columns}) VALUES ${values};`; }
インポート処理は次の 4 ステップです。
- Init upload: アップロードを初期化します。SQL コマンドのハッシュを D1 API に送り、アップロード URL を受け取ります。
- Upload to R2: SQL コマンドをアップロード URL へ送ります。
- Start ingestion: 取り込み処理を開始します。
- Polling: インポートが完了するまでポーリングします。
-
インポートの 4 ステップを実行する
uploadToD1関数を作成します。index.jsjs async function uploadToD1() { // 1. Init upload const hashStr = crypto.createHash("md5").update(sqlInsert).digest("hex"); try { const initResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify({ action: "init", etag: hashStr, }), }); const uploadData = await initResponse.json(); const uploadUrl = uploadData.result.upload_url; const filename = uploadData.result.filename; // 2. Upload to R2 const r2Response = await fetch(uploadUrl, { method: "PUT", body: sqlInsert, }); const r2Etag = r2Response.headers.get("ETag").replace(/"/g, ""); // Verify etag if (r2Etag !== hashStr) { throw new Error("ETag mismatch"); } // 3. Start ingestion const ingestResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify({ action: "ingest", etag: hashStr, filename, }), }); const ingestData = await ingestResponse.json(); console.log("Ingestion Response:", ingestData); // 4. Polling await pollImport(ingestData.result.at_bookmark); return "Import completed successfully"; } catch (e) { console.error("Error:", e); return "Import failed"; } }上記のコードでは次を行います。
- SQL コマンドの
md5ハッシュを生成します。 initResponseでアップロードを初期化し、アップロード URL を受け取ります。r2Responseで SQL コマンドをアップロード URL へ送ります。- 取り込み開始前に ETag を検証します。
ingestResponseで取り込み処理を開始します。pollImportでインポートが完了するまでポーリングします。
- SQL コマンドの
-
pollImport関数をindex.jsに追加します。index.jsjs async function pollImport(bookmark) { const payload = { action: "poll", current_bookmark: bookmark, }; while (true) { const pollResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify(payload), }); const result = await pollResponse.json(); console.log("Poll Response:", result.result); const { success, error } = result.result; if ( success || (!success && error === "Not currently importing anything.") ) { break; } await new Promise((resolve) => setTimeout(resolve, 1000)); } }上記のコードでは次を行います。
- D1 API に
pollアクションを送ります。 - インポートが完了するまでポーリングします。
- D1 API に
-
最後に、インポートを実行する
runImport関数をindex.jsに追加します。index.jsjs async function runImport() { const result = await uploadToD1(); console.log(result); } runImport();
これまでの手順で、D1 へのデータインポートに必要な各処理の関数を作成しました。最終コードは、これらの関数を実行してサンプルデータを取り込み先の D1 テーブルへインポートします。
-
次に示す
index.jsの最終コードをコピーし、変数をコード先頭で定義します。import crypto from "crypto"; import { faker } from "@faker-js/faker"; const TARGET_TABLE = ""; const ACCOUNT_ID = ""; const DATABASE_ID = ""; const D1_API_KEY = ""; const D1_URL = `https://api.cloudflare.com/client/v4/accounts/${ACCOUNT_ID}/d1/database/${DATABASE_ID}/import`; const uploadSize = 500; const headers = { "Content-Type": "application/json", Authorization: `Bearer ${D1_API_KEY}`, }; // Generate Fake data const data = Array.from({ length: uploadSize }, () => ({ id: Math.floor(Math.random() * 1000000), text: faker.lorem.paragraph(), date_added: new Date().toISOString().slice(0, 19).replace("T", " "), })); // Make SQL insert statements function makeSqlInsert(data, tableName, skipCols = []) { const columns = Object.keys(data[0]).join(","); const values = data .map((row) => { return ( "(" + Object.values(row) .map((val) => { if (skipCols.includes(val) || val === null || val === "") { return "NULL"; } return `'${String(val).replace(/'/g, "").replace(/"/g, "'")}'`; }) .join(",") + ")" ); }) .join(","); return `INSERT INTO ${tableName} (${columns}) VALUES ${values};`; } const sqlInsert = makeSqlInsert(data, TARGET_TABLE); async function pollImport(bookmark) { const payload = { action: "poll", current_bookmark: bookmark, }; while (true) { const pollResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify(payload), }); const result = await pollResponse.json(); console.log("Poll Response:", result.result); const { success, error } = result.result; if ( success || (!success && error === "Not currently importing anything.") ) { break; } await new Promise((resolve) => setTimeout(resolve, 1000)); } } // Upload to D1 async function uploadToD1() { // 1. Init upload const hashStr = crypto.createHash("md5").update(sqlInsert).digest("hex"); try { const initResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify({ action: "init", etag: hashStr, }), }); const uploadData = await initResponse.json(); const uploadUrl = uploadData.result.upload_url; const filename = uploadData.result.filename; // 2. Upload to R2 const r2Response = await fetch(uploadUrl, { method: "PUT", body: sqlInsert, }); const r2Etag = r2Response.headers.get("ETag").replace(/"/g, ""); // Verify etag if (r2Etag !== hashStr) { throw new Error("ETag mismatch"); } // 3. Start ingestion const ingestResponse = await fetch(D1_URL, { method: "POST", headers, body: JSON.stringify({ action: "ingest", etag: hashStr, filename, }), }); const ingestData = await ingestResponse.json(); console.log("Ingestion Response:", ingestData); // 4. Polling await pollImport(ingestData.result.at_bookmark); return "Import completed successfully"; } catch (e) { console.error("Error:", e); return "Import failed"; } } async function runImport() { const result = await uploadToD1(); console.log(result); } runImport();
-
コードを実行します。
node index.js
取り込み先の D1 テーブルに、サンプルデータが入ります。
このチュートリアルを完了すると、次のことを行えています。
- API トークンを作成しました。
- 取り込み先のデータベースとテーブルを作成しました。
- サンプルデータを生成しました。
- サンプルデータ用の SQL コマンドを作成しました。
- REST API でサンプルデータを D1 の取り込み先テーブルへインポートしました。