1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

RustでCloudflare D1からデータを取得するAPIを実装し、Vue.jsのアプリでAPIからデータを取得した話

Last updated at Posted at 2024-08-25

この記事は?

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_jsonserdeも追加します。

Cargo.toml
[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情報も記載します。

wrangler.toml
[[d1_databases]]
binding = "DB" # i.e. available in your Worker on env.DB
database_name = "workers-rs-d1"
database_id = "****"

データベースのスキーマも準備します。
内容はなんでもいいので、ゲームの素材情報です。

db/schema.sql
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 │ セイバ原木    │ 木材       │
└─────────────┴──────────┴──────────┘

よさそうです。

最後に本体の処理の記述です。

src/lib.rs
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へ直接アクセスすると以下が表示されました。

image.png

ひとまずOKです。

Vue.jsのアプリでAPIからデータ取得

いつもどおりにプロジェクトを作成します。

npm create vue@latest

そして、src/App.vueを書き換えます。

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.

image.png

ということで、対策します。

CORSエラー対応

ぐぐったら、以下が出ましたので、これを参考に実装します。

src/lib.rs
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)
}

これでもう一度確認します。

image.png

表示されました。
課題クリアです。

まとめ

ということで、Rustでサーバーサイドを書くことができました。

CORS周りやAPIの実装方法にたどり着くまでは少し大変でしたが、実装できて満足です。

後は、ローカル開発環境とプロダクション環境で、APIのURLが変わるのを環境変数で切り分けると、実際にCloudflare Workersにデプロイしても動くはずです。
(この例ではおこなっていませんが、他に実施したものでは切り分けができました)

いつかこの仕組みで、自分用にゲームの素材計算機をつくりたいなあとぼんやり考えています。

では、また何か記事にしたいことができましたら。

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?