LoginSignup
8
1

More than 1 year has passed since last update.

CloudflareWorks + KVでカウンターウェブアプリを作る

Posted at

事前準備(hello worldまで)

環境
OS: macOS Monterey 12.3.1
node -v: v18.0.0.
yarn -v: 1.22.18

# ディレクトリ作成
mkdir cloudflare-works-counter
cd $_

# 初期化
% npx wrangler init -y
Need to install the following packages:
  wrangler
Ok to proceed? (y) y
 ⛅️ wrangler 2.0.5 
-------------------
Using npm as package manager.
✨ Created wrangler.toml
npm WARN deprecated rollup-plugin-inject@3.0.2: This package has been deprecated and is no longer maintained. Please use @rollup/plugin-inject.

added 57 packages, and audited 58 packages in 10s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities
✨ Created package.json

added 2 packages, and audited 60 packages in 2s

1 package is looking for funding
  run `npm fund` for details

found 0 vulnerabilities
✨ Created tsconfig.json, installed @cloudflare/workers-types into devDependencies
✨ Created src/index.ts

To start developing your Worker, run `npm start`
To publish your Worker to the Internet, run `npm run publish`

こんな感じで雛形ができました

% tree -L 1
.
├── node_modules
├── package-lock.json
├── package.json
├── src
│   └── index.ts
├── tsconfig.json
└── wrangler.toml

この時点で yarn start すると動きますが、package.json にscriptをひとつ追加します。
エミュレータのMiniflareで起動するコマンドです。

  "scripts": {
    "start": "wrangler dev",
+   "local": "wrangler dev --local",
    "publish": "wrangler publish"
  }

b でブラウザを開くと「Hello World!」が表示されます
/src/index.js を編集してリロードすると表示内容が変わりますね

% yarn local          
yarn run v1.22.18
$ wrangler dev --local
 ⛅️ wrangler 2.0.5 
-------------------
⎔ Starting a local server...
╭────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╮
│ [b] open a browser, [d] open Devtools, [l] turn off local mode, [c] clear console, [x] to exit                                                                                 │
╰────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────╯
[mf:inf] Worker reloaded! (180B)
[mf:inf] Listening on localhost:8787
[mf:inf] - http://localhost:8787
[mf:inf] Updated Request cf object cache!
GET / 200 OK (7.21ms)

yarn start でも動きます
(初回はCloudflareをブラウザで開いて認証する必要があったかも)

カウンターを作る

hono導入

まずはhonoを使って書き換えます
「Hono[炎]」はCloudflare Workersに特化した「Webアプリを作るためのフレームワーク」です

% npm add hono                        

added 1 package, and audited 61 packages in 2s
1 package is looking for funding
  run `npm fund` for details
found 0 vulnerabilities
import { Hono } from 'hono'

const app = new Hono()

app.get('/', (c) => c.text('Hello! Cloudflare Works!'))

app.fire()

表示される文言が変わっただけで、やってることは同じ

% curl -X GET "http://localhost:8787"               
Hello! Cloudflare Works!

counterのエンドポイントを作る

ユーザーごとにカウントできるように動的なurlにしておきます

+ app.get('/counter/:username', (c) => {
+   const username = c.req.param('username')
+   const count = 0
+ 
+   return c.text(`${username}, count: ${count}`)
+ })

app.fire()
% curl -X GET "http://localhost:8787/counter/yamada"
yamada, count: 0

KVの追加

for production

% npx wrangler kv:namespace create COUNTER
 ⛅️ wrangler 2.0.5 
-------------------
🌀 Creating namespace with title "cloudflare-works-counter-COUNTER"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "COUNTER", id = "feoifeojeoijfaeojfo" }

wrangler.toml に設定を追加

compatibility_date = "2022-05-14"

+ kv_namespaces = [
+   { binding = "COUNTER", id = "feoifeojeoijfaeojfo" }
+ ]

for preview

% npx wrangler kv:namespace create COUNTER --preview
 ⛅️ wrangler 2.0.5 
-------------------
🌀 Creating namespace with title "cloudflare-works-counter-COUNTER_preview"
✨ Success!
Add the following to your configuration file in your kv_namespaces array:
{ binding = "COUNTER", preview_id = "feuhvbiuehfiuaheoige" }

wrangler.toml に設定を追加

compatibility_date = "2022-05-14"

kv_namespaces = [
-  { binding = "COUNTER", id = "feoifeojeoijfaeojfo" }
+  { binding = "COUNTER", id = "feoifeojeoijfaeojfo", preview_id = "feuhvbiuehfiuaheoige" }
]

カウンターのコードの追加

+ declare const COUNTER: KVNamespace

+ const addCount = async (username: string) => {
+   const value = await COUNTER.get(username)
+   let count = value != null ? parseInt(value) : 0
+   count = count + 1
+   await COUNTER.put(username, `${count}`)
+   return count
+ }

- app.get('/counter/:username', (c) => {
+ app.get('/counter/:username', async (c) => {
  const username = c.req.param('username')
+  const count = await addCount(username)

-  return c.text(`${username}, count: 0`)
+  return c.text(`${username}, count: ${count}`)
})

app.fire()

KVから値を取得し、加算する関数addCountを追加します
WorksはKVと統合されているので、COUNTER.get(key) COUNTER.put(key, value)と書くだけで取得と登録ができます
初回のためにnull→0の変換、登録済みの値は文字列で返ってくるので数値への変換を行い、加算したのちKVに保存しています
型エラーを解消するためにdeclare const COUNTER: KVNamespaceを書いていますが、なくても動きます

動作確認

% yarn local
略)

別のターミナルから

 % curl -X GET "http://localhost:8787/counter/tanaka"
tanaka, count: 1

% curl -X GET "http://localhost:8787/counter/tanaka"
tanaka, count: 2

% curl -X GET "http://localhost:8787/counter/tanaka"
tanaka, count: 3

% curl -X GET "http://localhost:8787/counter/tanaka"
tanaka, count: 4

% curl -X GET "http://localhost:8787/counter/tanaka"
tanaka, count: 5

Production へ deploy

% npx wrangler publish                              
 ⛅️ wrangler 2.0.5 
-------------------
Uploaded cloudflare-works-counter (0.79 sec)
Published cloudflare-works-counter (3.64 sec)
  cloudflare-works-counter.tyakatyaka3390.workers.dev

4秒!速い!

% curl -X GET "https://cloudflare-works-counter.tyakatyaka3390.workers.dev/"
Hello! Cloudflare Works!

curl -X GET "https://cloudflare-works-counter.tyakatyaka3390.workers.dev/counter/tanaka"
tanaka, count: 1

prviewとproductionでKVが分かれているので、ちゃんとtanakaさんのカウンターが初期値からになってますね

Worksの上限に達するまでは置いておくので、試してみてください

おまけ

# KVの一覧
% npx wrangler kv:namespace list        
[
  {
    "id": "",
    "title": "cloudflare-works-counter-COUNTER",
    "supports_url_encoding": true
  },
  {
    "id": "",
    "title": "cloudflare-works-counter-COUNTER_preview",
    "supports_url_encoding": true
  }
]

# 指定のKVに保存されたkeyの一覧
% npx wrangler kv:key list --binding=COUNTER
[
  {
    "name": "tanaka"
  }
]

# 指定のKV, 指定keyの値を取得
% npx wrangler kv:key get tanaka --binding=COUNTER            
2

/src/index.ts

import { Hono } from 'hono'

const app = new Hono()

declare const COUNTER: KVNamespace

const addCount = async (username: string) => {
  const value = await COUNTER.get(username)
  let count = value != null ? parseInt(value) : 0
  count = count + 1
  await COUNTER.put(username, `${count}`)
  return count
}

app.get('/', (c) => c.text('Hello! Cloudflare Works!'))

app.get('/counter/:username', async (c) => {
  const username = c.req.param('username')
  const count = await addCount(username)

  return c.text(`${username}, count: ${count}`)
})

app.fire()
8
1
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
8
1