Cloudflare Workers を使ってランダムなSpotifyのプレイリストを返す
はじめに
Cloudflare Workers を使って学習用に何か作ろうと思い、Spotifyのプレイリストをランダムで返すものを作ってみました。
処理の大まかな流れとしては以下になります。
- Workers KVに保存している検索ワードの中からランダムなワードを選び出す
- 選んだワードでプレイリストをSpotify APIで検索する
- 得られた複数のプレイリストからさらにランダムに一つを選び、それをResponseとして返す
あくまで学習目的で作成したため、プレイリストのURIを返すだけというシンプルなものになっています。
ゴール
返ってきたプレイリストのURIを、Spotifyのデスクトップアプリで開いてランダムなプレイリストになっていればOKというゴールに向かって進めて行きたいと思います。
Cloudflare workersとは
CloudflareのCDNのエッジサーバで実行されるサーバレス実行環境(FaaS)
また、CloudFlare workersで使えるキャッシュは Workers KV、Durable Objects、Cache APIの3つ
環境構築や準備
- node: v18.16.0
- wrangler: v2.15.1
1. Spotify URIについて
Spotifyには曲やプレイリストなどに関連する以下のようなURIが存在します。
spotify:artist:xxxxxxxxxxxxxxxx
SpotifyデスクトップアプリがインストールされているMacの場合、Terminal上で open spotify:artist:xxxxxxxxxxxxxxxx
と入力することで、Spotifyアプリが対象のコンテンツを開いてくれます。この手順に従い、レスポンスのURIをMacのSpotifyアプリで確認しながら作業を進めていこうと思います。
What's a Spotify URI? - The Spotify Community
2. Cloudflare Workersの環境構築
2-1. サインアップして最初のWorkerを作成してみる
早速 Cloudflare Workers の環境を構築していきたいと思います。
こちらのページからサインアップしダッシュボードの Workers
ページにいくと以下の様になっているので早速ダッシュボード上でWorkerを作成してみます。
作成した後IDEっぽい画面が表示されました。ここでも編集、再デプロイなんかができるみたいですね 👀
一旦 Workers
ページに戻ってみるとサブドメインが変更できるようになっていました ✨
ここからお好みのサブドメインを設定できるようです。
2-2. wranglerを使ってデプロイしてみる
CLIツール wrangler が提供されているのでそちらを使って進めてみたいと思います。
Commands · Cloudflare Workers docs
ここからはDockerを使って環境構築していきます。
まずはプロジェクト直下に以下内容のDockerfileを作成します。
FROM node:18-alpine
WORKDIR /usr/src/app
RUN apk update && \
apk add git vim bash curl
RUN npm install -g wrangler
次に以下内容で docker-compose.yml
を作成します。
version: '3.3'
services:
worker:
build: .
image: wrangler
container_name: "wrangler"
tty: true
stdin_open: true
volumes:
- .:/usr/src/app
- wrangler_config:/root/.config/.wrangler
ports:
- '8976:8976'
- '8787:8787'
entrypoint: bash
volumes:
wrangler_config:
コンテナを起動させ、コンテナ内で以下コマンドを実施し Cloudflare にログインします。
wrangler login
ブラウザが開けないので Failed to open
と表示されるますが、URLをコピーしてブラウザを起動させると以下の確認画面が表示され、「Allow」を選択する事でログインする事ができます。
wrangler whoami
コマンドを実行し自身のアカウントが表示されていればOKです。
2-3. プロジェクトの初期化
$ wrangler init hello
⛅️ wrangler 2.15.1 (update available 2.16.0)
-------------------------------------------------------
Using npm as package manager.
✨ Created hello/wrangler.toml
✔ No package.json found. Would you like to create one? … yes
✨ Created hello/package.json
✔ Would you like to use TypeScript? … yes
✨ Created hello/tsconfig.json
✔ Would you like to create a Worker at hello/src/index.ts? › Fetch handler
✨ Created hello/src/index.ts
✔ Would you like us to write your first test with Vitest? … no
↑ 今回は一旦Vitestはなしで、Fetch handlerとして作成してみました。
早速起動してみます。
cd hello
npm run start
起動後、http://localhost:8787/ にアクセスしてみると「Hello World!」が返ってくるはずです。
今度はデプロイしてみます。
cd hello
npm run deploy
↑これでサクッとデプロイされ、デプロイされたURLにアクセスすると同じ「Hello World!」が返ってくるはずかと思います。
ちなみにデプロイされたものを削除する場合は wrangler delete
で削除できます。
一旦ここまででCloudflare Workersを動かす環境ができました!
実装
1. まずはCloudflare Workersから返却されたURIをアプリで開いてみる
1-1. セットアップ
先程の hello
ディレクトリを削除し、以下コマンドでプロジェクトルート直下にsrcディレクトリを作成します。
wrangler init .
次に docker-compose.yml
に以下を追加します。
version: '3.3'
services:
worker:
build: .
image: wrangler
container_name: "wrangler"
command:
- ./bin/start_dev_server.sh # 追加
tty: true
stdin_open: true
environment:
- RUN_WRANGLER_DEV=${RUN_WRANGLER_DEV:-1} # 追加
volumes:
- .:/usr/src/app
- wrangler_config:/root/.config/.wrangler
- modules_data:/usr/src/app/node_modules # 追加
ports:
- '8976:8976'
- '8787:8787'
volumes:
wrangler_config:
modules_data: # 追加
次に bin/start_dev_server.sh
を以下内容で作成します。
#!/bin/bash
# エラーで処理中断
set -ex
npm install
if [ "${RUN_WRANGLER_DEV}" = "1" ] ; then
npm run start
else
echo "[NOT RUN WRANGLER DEV]"
tail -f /dev/null
fi
npm run start
を行わず wrangler login
などの操作を実施するケースも考慮して RUN_WRANGLER_DEV
の環境変数でserver起動のON/OFFを切り替えれるようにしています。
早速docker compose up -d
で起動させて http://localhost:8787 にアクセスし「Hello World」が表示されればOKです。
※ server起動させない場合は RUN_WRANGLER_DEV=0 docker compose up -d
で起動させます。
1-2. 固定のSpotify URIを返す
ひとまず固定のSpotify URIを用意し、単純にURIを返すように修正してみます。
SpotifyのURIの取得方法はこちらを参考に「シェア」を選択している際にOptionボタンを押下するとURIをコピーのメニューが表示されます ↓
次にsrc/index.ts
の Response("Hello World!");
部分を修正します。
Response("spotify:track:xxxxxxxxxxxxx");
起動している状態で以下をターミナルから実行し、Spotifyの指定した内容が開けばOKです。
open `curl http://localhost:8787`
2. Cloudflare Workers内でSpotify APIを使用してURIを取得する
2-1. Client IDと Client secretの取得
Spotify Clientを使用する際に必要な Client ID
と Client secret
を以下手順で取得します。
- Spotifyのアカウントを使ってダッシュボードにログインします
- 「Create app」でアプリを作成します
- 今回は以下の内容で作成しました
- 今回は以下の内容で作成しました
- appの「Settings」>「Basic Information」から
Client ID
とClient secret
を確認します
2-2.Environment variables
Environment variables · Cloudflare Workers docs
- wranglerによる環境変数
- Secret環境変数の場合
- ダッシュボードから環境変数を設定
今回はSecretの環境変数として先程取得した Client ID
と Client secret
を管理していきたいと思います。
プロジェクトルートの .dev.vars
ファイルを作成し、 Client ID
と Client secret
を設定します。
SPOTIFY_CLIENT_ID = "..."
SPOTIFY_CLIENT_SECRET = "..."
次に export interface Env
部分に↑の環境変数を定義します。
export interface Env {
SPOTIFY_CLIENT_ID: string;
SPOTIFY_CLIENT_SECRET: string;
}
これで、 env.SPOTIFY_CLIENT_ID
のようにアクセスできるようになりました。
2-3.特定のワードで検索してトップのプレイリストを返してみる
src/index.ts
を以下の様に修正します。
import { Buffer } from "buffer";
export interface Env {
SPOTIFY_CLIENT_ID: string;
SPOTIFY_CLIENT_SECRET: string;
}
const getAccessToken = async (clientID: string, clientSecret: string) => {
try {
const urlencoded = new URLSearchParams();
urlencoded.append("grant_type", "client_credentials");
const res = await fetch("https://accounts.spotify.com/api/token", {
method: "POST",
body: urlencoded,
headers: {
Authorization:
"Basic " +
Buffer.from(clientID + ":" + clientSecret).toString("base64"),
"Content-Type": "application/x-www-form-urlencoded",
},
});
const result = (await res.json()) as { access_token: string };
return result["access_token"];
} catch (e) {
console.error(e);
}
};
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
const token = await getAccessToken(
env.SPOTIFY_CLIENT_ID,
env.SPOTIFY_CLIENT_SECRET
);
try {
const params = {
q: "remaster",
type: "playlist",
};
const query = new URLSearchParams(params);
const result = await fetch(`https://api.spotify.com/v1/search?${query}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const json = (await result.json()) as any;
const uri = json["playlists"]["items"][0]["uri"];
return new Response(uri);
} catch (e: any) {
console.error(e);
return new Response(e.stack, { status: 500 });
}
},
};
remaster
というワードで検索した playlist
の候補から最初のURIを返すような実装になっています。
以下コマンドを実行してremasterのプレイリストが開いていればOKです!
open `curl http://localhost:8787`
※ SpotifyのWeb API の詳細は以下
Web API | Spotify for Developers
3. Workers KVでプレイリストのURIを複数保存する
Workers KVに関して
サーバーレスストレージとアプリケーション | Cloudflare Workers KV | Cloudflare
Cloudflare Workersから利用できるKey-Value型のストレージサービスになります。
3-1. KV namespaceの作成
今回は KV_PLAYLISTS
という名前でKV namespaceを作成します。wrangler CLIを使って作成していきます。
$ wrangler kv:namespace create "KV_PLAYLISTS"
⛅️ wrangler 3.0.0 (update available 3.0.1)
-----------------------------------------------------
🌀 Creating namespace with title "app-KV_PLAYLISTS"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "KV_PLAYLISTS", id = "......." }
--preview
オプションをつけるとローカルから実行する際に参照されるnamespaceが作成されるのでそちらも作成しておきます。
wrangler kv:namespace create "KV_PLAYLISTS" --preview
wrangler kv:namespace list
コマンドで2つのnamespaceが作成されていればOKです。
$ wrangler kv:namespace list
[
{
"id": "....",
"title": "app-KV_PLAYLISTS",
"supports_url_encoding": true
},
{
"id": "....",
"title": "app-KV_PLAYLISTS_preview",
"supports_url_encoding": true
}
]
上記で作成した2つのnamespaceのidを wrangler.toml
に追記しときます。
kv_namespaces = [
{ binding = "KV_PLAYLISTS", id = "....", preview_id = "...." }
]
次に設定した KV namespace KV_PLAYLISTS
が扱えるように export interface Env
に以下を 追加します。
export interface Env {
KV_PLAYLISTS: KVNamespace; // ← 追加
SPOTIFY_CLIENT_ID: string;
SPOTIFY_CLIENT_SECRET: string;
}
3-2. KV namespaceを使用する
試しにダッシュボード画面からPreview用のKV namespaceにKey Valueを追加してみます。
次に Workers上で↑で設定した値をログに取得してみたいと思います。
const value = await env.KV_PLAYLISTS.get("test");
console.log(value);
実行して「Hello world!」が表示されればOKです。
3-3. ランダムな検索ワードを取得する
ダッシュボード画面でPreview用のKV namespaceから一度先程の「test」keyのものは削除して、Spotifyで検索するワードを以下の様な形で好きなワードを複数登録しておきます。
次に以下を src/index.ts
に追加し、ランダムな検索ワードでプレイリストを取得できるようにします。
// ランダムなnumber値を取得するutility
const getRandomInt = (max: number) => {
return Math.floor(Math.random() * max);
};
// ...
export default {
async fetch(
request: Request,
env: Env,
ctx: ExecutionContext
): Promise<Response> {
// ↓追加
const list = await env.KV_PLAYLISTS.list();
const randamAt = getRandomInt(list.keys.length);
const key = list.keys[randamAt].name;
const queryWord = await env.KV_PLAYLISTS.get(key);
if (!queryWord) {
return new Response("Not found query word", { status: 404 });
}
const token = await getAccessToken(
env.SPOTIFY_CLIENT_ID,
env.SPOTIFY_CLIENT_SECRET
);
try {
const params = {
q: queryWord, // queryWord を使うように変更
type: "playlist",
};
const query = new URLSearchParams(params);
const result = await fetch(`https://api.spotify.com/v1/search?${query}`, {
method: "GET",
headers: {
Authorization: `Bearer ${token}`,
},
});
const json = (await result.json()) as any;
// ↓ついでにヒットしたPlylistsからランダムなものを返すように変更
const items = json["playlists"]["items"];
const uri = items[getRandomInt(items.length)]["uri"];
return new Response(uri);
} catch (e: any) {
console.error(e);
return new Response(e.stack, { status: 500 });
}
},
};
早速試しに以下を実行し、ランダムにプレイリストが開いていればOKです。
open `curl http://localhost:8787`
4. デプロイ
早速デプロイしてみたいと思います。
yarn deploy
ダッシュボード上のWorkers Overviewで対象のWorkerがデプロイされていればOKです。
次に Spotifyの Client ID
と Client secret
の環境変数を設定してやります。
「Worker選択」>「Settings」>「Variables」の画面で「Environment Variables」を設定します。
以下コマンドを実施し、ローカルと同様にランダムにPlaylistがSpotifyで開いていればOKです!
open `curl [デプロイされたURL]`
バッドノウハウ
Error: Adapter 'http' is not available in the build
が発生する!
Spotify APIを使う際に axios を使っていた時に↑のエラーが発生しました。色々調べてみるとEdgeの環境だとaxiosがサポートされてない(?)ようなので Cloudflare Workersのドキュメントにもあるようにfetchを使うようにしました。
参考リンク
まとめ
実際の用途としてはあってないかもですが、Cloudflare Workersを今回ローカルで動かして実際にデプロイするまでを実施してみて、Wrangler CLIで色々設定やKVの作成、Preview環境など使いやすく、スムーズに進めたかなと思いました。
次はCloudflare D1やR2など試して見たいと思います。
Discussion