この記事は?
RustでCloudflare D1からデータを取得するAPIを実装し、Vue.jsのアプリでAPIからデータを取得するということをしたので、その紹介です。
解説ではなく、やったこと紹介です。
やったこと
- RustでCloudflare D1からデータを取得するAPIを実装し、Cloudflare Workersで動かす(←適切な表現かはわからない)
- Vue.jsのアプリでAPIからデータ取得
- CORSエラー対応
導入
RustでAPIを実装できるらしいのでやってみました。
なんとなくCloudflare D1を使い、かつ、APIをたたくのはなんでもよいのですが、今後の予定からVue.jsを使用しました。
よくわからないけど、CORSエラーが起こらないように実装しました。
流れ
APIを実装してから、アプリを作成しました。
するとCORSエラーが発生しましたので、API側にCORS対応をおこなったという流れです。
以下でもその順番で実施したことを紹介します。
本編
RustでCloudflare D1からデータを取得するAPIを実装し、Cloudflare Workersで動かす
Rustの開発環境は導入済みなところから始めます。
まずは以下でHello Worldなスケルトンをつくります。
cargo generate cloudflare/workers-rs
↓ 実行結果
Favorite `cloudflare/workers-rs` not found in config, using it as a git repository: https://github.com/cloudflare/workers-rs.git
✔ Which template should be expanded? · templates\hello-world
Project Name: server_rust
Destination: E:\****\server_rust ...
project-name: server_rust ...
Generating template ...
Moving generated files into: `E:\\****\\server_rust`...
Initializing a fresh Git repository
Done! New project created E:\****\server_rust
cd ./server_rust
npx wrangler dev
これで、Hello Worldが表示されます。
と思ったら、エラーになります。
Compiling worker v0.2.0
error[E0599]: no method named `unwrap` found for struct `js_sys::Iterator` in the current scope
--> ****\.cargo\registry\src\index.crates.io-6f17d22bba15001f\worker-0.2.0\src\headers.rs:83:14
|
80 | / ... self.0
81 | | ... .keys()
82 | | ... // Header.keys() doesn't error: https://developer.moz...
83 | | ... .unwrap()
| | -^^^^^^ method not found in `Iterator`
| |___________|
|
エラー全文(長いので折りたたみ)
Compiling worker v0.2.0
error[E0599]: no method named `unwrap` found for struct `js_sys::Iterator` in the current scope
--> ****\.cargo\registry\src\index.crates.io-6f17d22bba15001f\worker-0.2.0\src\headers.rs:83:14
|
80 | / ... self.0
81 | | ... .keys()
82 | | ... // Header.keys() doesn't error: https://developer.moz...
83 | | ... .unwrap()
| | -^^^^^^ method not found in `Iterator`
| |___________|
|
error[E0599]: no method named `unwrap` found for struct `js_sys::Iterator` in the current scope
--> ****\.cargo\registry\src\index.crates.io-6f17d22bba15001f\worker-0.2.0\src\headers.rs:95:14
|
92 | / ... self.0
93 | | ... .values()
94 | | ... // Header.values() doesn't error: https://developer.m...
95 | | ... .unwrap()
| | -^^^^^^ method not found in `Iterator`
| |___________|
|
error[E0599]: no method named `unwrap` found for struct `js_sys::Iterator` in the current scope
--> ****\.cargo\registry\src\index.crates.io-6f17d22bba15001f\worker-0.2.0\src\headers.rs:69:14
|
66 | / ... self.0
67 | | ... .entries()
68 | | ... // Header.entries() doesn't error: https://developer....
69 | | ... .unwrap()
| | -^^^^^^ method not found in `Iterator`
| |___________|
|
For more information about this error, try `rustc --explain E0599`.
error: could not compile `worker` (lib) due to 3 previous errors
Error: Compiling your crate to WebAssembly failed
Caused by: Compiling your crate to WebAssembly failed
Caused by: failed to execute `cargo build`: exited with exit code: 101
full command: "cargo" "build" "--lib" "--release" "--target" "wasm32-unknown-unknown"
Error: wasm-pack exited with status exit code: 1
X [ERROR] Running custom build `cargo install -q worker-build && worker-build --release` failed. There are likely more logs from your build command above.
そのままビルドできないなんてけしからんですね。
プンプン怒りながら、ググります。
Workaroundが提示されていたので、Cargo.toml
に追記し回避します。
[dependencies]
wasm-bindgen = "=0.2.92"
何をしているか不明ですが、ビルドに成功し、動きました。
Hello Worldさん、こんにちは。
今回はCloudflare D1からデータを取得するため、Cargo.toml
を以下のように修正します。
ついでに使うので、serde_json
とserde
も追加します。
[dependencies]
- worker = { version="0.2.0" }
+ worker = { version="0.2.0", features=["d1"] }
+ serde_json = "1.0.125"
+ serde = "1.0.208"
また、wrangler.toml
にDB情報も記載します。
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "workers-rs-d1"
database_id = "****"
データベースのスキーマも準備します。
内容はなんでもいいので、ゲームの素材情報です。
DROP TABLE IF EXISTS materials;
CREATE TABLE materials
(id TEXT, name TEXT, category TEXT, PRIMARY KEY (`id`));
INSERT INTO materials (id, name, category) VALUES
("e8c40da556a", "ウコギ原木", "木材"),
("46f892dc3ba", "オルコクロマイト", "石材"),
("364a2350c5e", "雪木綿", "布材"),
("8f817bd6b10", "セイバ原木", "木材");
これを実行します。今回はローカルのデータベースでのみおこないます。
npx wrangler d1 execute workers-rs-d1 --local --file=./db/schema.sql
正しくデータが入っているかを確認します。
npx wrangler d1 execute workers-rs-d1 --local --command="SELECT * FROM materials"
↓ 実行結果
⛅️ wrangler 3.65.1 (update available 3.72.2)
-------------------------------------------------------
🌀 Executing on local database workers-rs-d1 (****) from .wrangler\state\v3\d1:
🌀 To execute on your remote database, add a --remote flag to your wrangler command.
┌─────────────┬──────────┬──────────┐
│ id │ name │ category │
├─────────────┼──────────┼──────────┤
│ e8c40da556a │ ウコギ原木 │ 木材 │
├─────────────┼──────────┼──────────┤
│ 46f892dc3ba │ オルコクロマイト │ 石材 │
├─────────────┼──────────┼──────────┤
│ 364a2350c5e │ 雪木綿 │ 布材 │
├─────────────┼──────────┼──────────┤
│ 8f817bd6b10 │ セイバ原木 │ 木材 │
└─────────────┴──────────┴──────────┘
よさそうです。
最後に本体の処理の記述です。
use worker::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Materials {
id: String,
name: String,
category: String,
}
#[event(fetch)]
async fn fetch(
req: Request,
env: Env,
_ctx: Context,
) -> Result<Response> {
console_error_panic_hook::set_once();
Router::new()
.get_async("/", |_, ctx | async move {
let d1 = ctx.env.d1("DB")?;
let statement = d1.prepare("select * from materials");
let result = statement.all().await?;
Response::from_json(&result.results::<Materials>().unwrap())
})
.run(req, env)
.await
}
ソースコードは以下を参考にさせていただきました。
ありがとうございます。
これを動かして、WebブラウザからURLへ直接アクセスすると以下が表示されました。
ひとまずOKです。
Vue.jsのアプリでAPIからデータ取得
いつもどおりにプロジェクトを作成します。
npm create vue@latest
そして、src/App.vue
を書き換えます。
<script setup>
import { ref, onMounted } from 'vue';
const data = ref();
onMounted(async () => {
const response = await fetch(`http://127.0.0.1:8787`, {
cache: 'no-store',
});
data.value = await response.json();
});
</script>
<template>
<div class="fl">
<div class="ma" v-for="material in data">
<p>id: {{ material.id }}</p>
<p>name: {{ material.name }}</p>
<p>category: {{ material.category }}</p>
</div>
</div>
</template>
<style scoped>
div.fl {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
}
div.ma {
margin-right: 2rem;
margin-bottom: 2rem;
}
p {
margin-bottom: .6rem;
}
</style>
動かします。
白紙のページが表示されました。
DevToolからコンソールを見ると、CORSポリシー違反が発生していました。
Access to fetch at 'http://127.0.0.1:8787/' from origin 'http://localhost:5173' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.
ということで、対策します。
CORSエラー対応
ぐぐったら、以下が出ましたので、これを参考に実装します。
use worker::*;
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize, Debug)]
struct Materials {
id: String,
name: String,
category: String,
}
#[event(fetch)]
async fn fetch(
req: Request,
env: Env,
_ctx: Context,
) -> Result<Response> {
console_error_panic_hook::set_once();
+ let cors = Cors::default().with_origins(vec!["*"]);
Router::new()
.get_async("/", |_, ctx | async move {
let d1 = ctx.env.d1("DB")?;
let statement = d1.prepare("select * from materials");
let result = statement.all().await?;
Response::from_json(&result.results::<Materials>().unwrap())
})
.run(req, env)
- .await
+ .await?
+ .with_cors(&cors)
}
これでもう一度確認します。
表示されました。
課題クリアです。
まとめ
ということで、Rustでサーバーサイドを書くことができました。
CORS周りやAPIの実装方法にたどり着くまでは少し大変でしたが、実装できて満足です。
後は、ローカル開発環境とプロダクション環境で、APIのURLが変わるのを環境変数で切り分けると、実際にCloudflare Workersにデプロイしても動くはずです。
(この例ではおこなっていませんが、他に実施したものでは切り分けができました)
いつかこの仕組みで、自分用にゲームの素材計算機をつくりたいなあとぼんやり考えています。
では、また何か記事にしたいことができましたら。