「API とは何か」と「server.jsが何をしているファイルなのか」は、スライドで説明したとおりです。ここからは手を動かします。
つくる API は/authの 1 本だけです。 パスワードを送って、あっているかどうかを返してもらう API です。
手順は 5 つ。
| やること | |
|---|---|
| 1-1 | server.jsに窓口を足す |
| 1-2 | public/api.htmlを新しくつくる |
| 1-3 | public/api.jsを新しくつくる |
| 1-4 | push する |
| 1-5 | ブラウザで/api.htmlを開いて確認する |
そのあと、時間があれば 2. (発展) に進みます。
Note
実は、API を使うのは、これが初めてではありません。
Deno のセクションでつくったページはindex.jsからfetch("/welcome-message")で文字を受け取っていました。あれが受け取るだけの API です。今日やるのは送る側です。
なお、この資料では「API を叩く」という言い方をします。API にリクエストを送るという意味です。
Important
今日は、Deno のセクションで作った自分のリポジトリの中で作業します。
この教材リポジトリ(intern-dev-tutorial)ではありません。
3 つです。そのうち 2 つは、これから新しくつくります。
| ファイル | 今日の扱い |
|---|---|
server.js |
return serveDir(の行の上に足すだけ |
public/api.html |
新しくつくる。今日の画面はここ |
public/api.js |
新しくつくる。今日書く JavaScript は全部ここ |
public/index.html・public/index.js・public/styles.cssは今日は開きません。これまでにつくったページはそのまま残して、今日の分はapi.htmlという別のページにつくります。これまでの成果を壊す心配がありません。
既存のファイルに足すのはserver.jsの 1 か所だけです。場所はここ。
import { serveDir } from "jsr:@std/http@1.1.3/file-server";
Deno.serve((req) => {
const pathname = new URL(req.url).pathname;
console.log(pathname);
if (req.method === "GET" && pathname === "/welcome-message") {
return new Response("jigインターンへようこそ!");
}
// ====================================
// ここ!
// 今日足すのは、ぜんぶこの場所です
// ====================================
return serveDir(req, { // ← この行の「すぐ上」
fsRoot: "public",
urlRoot: "",
showDirListing: true,
enableCors: true,
});
});なぜ「上」なのか。returnは「ここで返して、おわり」という意味だからです。return serveDir(より下に書いたものには、一生たどりつきません。
Deno のセクションでそれぞれ好きなように書き換えたので、server.jsの中身は人によって違います。だからこの資料には行番号を書いていません。かわりの目印がreturn serveDir(の 1 行です。この行は消すとページが表示されなくなるので、デプロイできた人には必ず残っています。
- VSCode で、Deno のセクションでクローンしたフォルダ(例:
deno-app)を開く - ブラウザで https://deno.com/deploy を開いて、自分のアプリのページを表示する
- そこに出ている URL を開く(Deno のセクションでデプロイしたページです)
発行された URL は、こういう形をしています。
https://アプリ名.置き場所の名前.deno.net
Deno のセクションでつくったページが表示されます。
この 2 つのタブは、今日ずっと開いたままにしておいてください。
| タブ | 何に使うか |
|---|---|
自分のアプリ(.deno.net) |
今日つくるページも API も、全部この URL の後ろにくっつけて開きます |
| Deno Deploy | push したあとのデプロイの進み具合と、サーバー側のログを見ます |
Note
この資料では、あなたの URL のことをhttps://自分のURLと書きます。
出てきたら、自分の URL に読み替えてください。
今日は、自分の PC でサーバーを動かしません。書いたコードは、https://自分のURLで動いているものを直接見て確認します。
ただし、あちらで動いているのはGitHub に置いてあるコードです。VSCode で保存しただけでは、まだ自分の PC の中にあるだけで、あちらには何も届いていません。
VSCode で保存 ← まだ自分のPCの中
↓
commit → push ← ここでGitHubに届く
↓
GitHub
↓ 自動でデプロイ(1〜2分かかる)
https://自分のURL に反映
つまり、書き換えるたびに commit と push が必要です。
- VSCode で
Cmd + S(Windows はCtrl + S)で保存する - 左側のソース管理(Source Control)タブを開く
- 変更したファイルの横の
+ボタンを押す(ステージする) - メッセージを書いて commit する
- push する
- 1〜2 分待つ(Deno Deploy の自分のアプリのページで、新しいビルドに緑のチェックが付けば反映済みです)
- ブラウザで
Cmd + Shift + R(Windows はCtrl + Shift + R)で強制再読み込みして確認する
やり方を忘れた人はこちらを見てください。
準備は終わりです。手を動かしていきましょう。
/authにパスワードを送ると、あっているかどうかが返ってくるAPI をつくります。
なぜパスワードなのか。スライドで見た「利点 2 見せたくないものを隠せる」を、実際にやってみるためです。ブラウザ側のコードは開発者ツールで誰でも中身を見られますが、サーバー側(server.js)の中身は見えません。だからパスワードの照合はサーバー側でやります。
そして、送るときに使うのがPOSTです。前の章で勉強したとおり、GETで渡した情報はURL に出ます。もしGETでパスワードを送ったら、こうなってしまいます。
https://自分のURL/auth?password=jigjp
丸見えです。アドレスバーにも、ブラウザの履歴にも残ります。POSTは情報を URL ではなくbodyに入れて送るので、URL には出ません。だから今日はPOSTを使います。
つくるものはこの 4 つです。
| これがルール | 今日の場合 | |
|---|---|---|
| ① | どの URL か | /auth |
| ② | どのメソッドか | POST |
| ③ | 何を渡すか | password(body に JSON で) |
| ④ | 何が返るか | { ok, message }(JSON) |
server.jsを開く- ファイルの上のほうにある
Deno.serve((req) => {にasyncを足して、こう書き換える
Deno.serve(async (req) => {- ファイルの下のほうにある
return serveDir(req, {を探して、その行のすぐ上に以下を貼る
if (req.method === "POST" && pathname === "/auth") {
const body = await req.json();
if (body.password === "jigjp") {
return Response.json({ ok: true, message: "ログインできました" });
}
return Response.json({ ok: false, message: "パスワードが違います" });
}Cmd + Sで保存する
Important
手順 2 のasyncが、今日いちばん間違えやすいところです。
今日、すでにある行を書き換えるのはここだけでもあります。忘れずに足してください。
貼ったあと、あなたのserver.jsはこう見えます。...の部分は人によって違います。これまでに書き換えた内容はそのままで大丈夫です。
Deno.serve(async (req) => { // ← async を足した
const pathname = new URL(req.url).pathname;
console.log(pathname);
if (req.method === "GET" && pathname === "/welcome-message") {
return new Response("...");
}
if (req.method === "POST" && pathname === "/auth") {
const body = await req.json();
if (body.password === "jigjp") {
return Response.json({ ok: true, message: "ログインできました" });
}
return Response.json({ ok: false, message: "パスワードが違います" });
}
return serveDir(req, {
fsRoot: "public",req.method === "POST"で、POST で来たときだけ動くようにしています。前の章で勉強したGETとPOSTが、ここに出てきますpathnameに、叩かれた URL のパス(/authの部分)が入っていますawait req.json()で、body に入っている JSON を取り出しますbody.passwordがjigjpと同じかどうかで、返す内容を変えていますreturnしたら、そこでおわりです。合っていれば 1 つ目を返して終了、違っていれば下の行に進みます
asyncを足したのは、このawait req.json()のためです。awaitはasyncが付いた関数の中でしか使えません。fetchでawaitを使ったときもasync () => {とセットになっていました。awaitとasyncはセットです。
そして返し方です。もとからある/welcome-messageは文字を 1 つ返していました。今日は JSON を返します。
new Response("文字") // 文字を返す(これまで)
Response.json({ キー: 値 }) // JSONを返す(今日){ }の中にキー: 値をカンマで区切って並べます。今回はokとmessageの 2 つを返しています。1 回のやりとりで、複数の情報をまとめて返せるのが JSON です。実際の Web アプリの API は、ほとんどこの形です。
次に、今日の画面をつくります。すでにあるindex.htmlとは別の、新しいページです。
- VSCode の左側で、
publicフォルダを右クリックする - 「新しいファイル」を選ぶ
api.htmlと入力して Enter- 開いた空のファイルに、以下を全部貼る
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API ハンズオン</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<h1>ログイン</h1>
<div>
<input type="password" id="passwordInput" placeholder="パスワード">
<button id="authButton">ログイン</button>
<span id="authResult"></span>
</div>
<script type="module" src="./api.js"></script>
</body>
</html>Cmd + Sで保存する
VSCode の左側のpublicフォルダの中に、api.htmlが並んでいます。
public
├── api.html ← 今つくった
├── index.html
├── index.js
└── styles.css
type="password"にすると、打った文字が●●●で隠れます<link rel="stylesheet" href="./styles.css">で、すでにある CSS をそのまま使い回しています<script type="module" src="./api.js"></script>で、次につくるapi.jsを読み込みます
今日書く JavaScript を入れるファイルを、新しくつくります。
- VSCode の左側で、
publicフォルダを右クリックする - 「新しいファイル」を選ぶ
api.jsと入力して Enter- 開いた空のファイルに、以下を全部貼る
document.querySelector("#authButton").onclick = async () => {
const password = document.querySelector("#passwordInput").value;
const response = await fetch("/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: password }),
});
const data = await response.json();
document.querySelector("#authResult").innerText = data.message;
};Cmd + Sで保存する
document.querySelector("#authButton")で、idがauthButtonのボタンを取ってきます.onclick = async () => { ... }で、そのボタンが押されたときの処理を登録しますdocument.querySelector("#passwordInput").valueで、入力欄に打たれた文字を取り出します
そしてfetchです。これまではfetch("/welcome-message")だけでした。今日は 2 番目に{ }が増えています。POST で送るときは、これが必要です。
| 書いたもの | 意味 |
|---|---|
method: "POST" |
POST で送る |
headers: { "Content-Type": "application/json" } |
「これから JSON を送ります」とサーバーに伝える |
body: JSON.stringify({ password: password }) |
送りたい中身。JSON.stringify()で JSON の文字に変換する |
{ password: password }は、左が送るときのキー名、右が上の行で取り出した変数です。たまたま同じ名前にしているだけで、役割は違います。このキー名は、受け取るserver.js側(body.password)とそろえます。
受け取り方も、これまでと 1 か所だけ違います。
await response.text() // 文字として受け取る(これまで)
await response.json() // JSONとして受け取る(今日)response.json()で受け取ると、data.okやdata.messageのようにキーを指定して取り出せます。
const data = await response.json();
data.ok // true
data.message // "ログインできました"3 つのファイルが揃いました。まとめて公開します。
- commit して push する(0-3 の手順)
- 1〜2 分待つ
Important
public/api.htmlとpublic/api.jsの+を押すのを忘れないでください。
どちらも新しいファイルです。新しいファイルは+(ステージ)を押さないと commit に入らず、GitHub に届きません。
つくったページは、もとのページとは別の URL です。トップページを開いても出てきません。
| 開く URL | 表示されるもの |
|---|---|
https://自分のURL |
index.html … もとのページ |
https://自分のURL/api.html |
api.html … 今日つくったページ |
- ブラウザのアドレスバーに、以下を直接入力して Enter
https://自分のURL/api.html
後ろの/api.htmlを忘れずに。これがないと、もとのページが開くだけでボタンはありません
- 入力欄に
jigjpと打ってログインボタンを押す - 次に、わざと違うパスワードを打ってボタンを押す
jigjpのとき →ログインできました- 違うとき →
パスワードが違います
API が完成しました。送った情報でサーバーが判断して、その結果が返ってきています。
うまくいかない人は 3. うまくいかないときは を見てください。
Note
なぜ/api.htmlで開けるのでしょうか。
publicの中に置いたファイルは、URL の後ろにファイル名を付けると開けます。
https://自分のURL/api.htmlを開くと、server.jsのifはどれにも当たりません。どれにも当たらなかったときの受け皿が、一番下のserveDirです。これがpublic/api.htmlを探して返してくれます。
ページを開くのも、API を叩くのも、同じ窓口へのリクエストです。違うのは、誰が答えるかだけです。
Note
今回はパスワードをserver.jsに直接書きました(ハードコーディングといいます)。これは練習用です。本物のアプリでこれをやると、GitHub にパスワードが公開されてしまいます。
ちゃんと扱う方法は ログインハンズオン にあります。
せっかくなので、POSTで送ったパスワードが URL に出ていないことを確認しましょう。
- 開発者ツールを開く(
F12、または右クリック →「検証」) - Networkタブを開く
- ボタンをもう一度押す
- 一覧に出てきた
authをクリックする - Payloadタブ(またはペイロード)を開く
passwordが body に入っていて、URL には出ていないことが確認できます。前の章で見た GitHub のログインと同じ形です。
Responseタブを開けば、返ってきた JSON もそのまま見られます。
つくった API は、もう公開されています。push した時点でインターネットに出ているので、改めて公開する作業はありません。
つまり、この URL は自分の PC 以外からも叩けます。確かめてみましょう。
- スマホのブラウザで
https://自分のURL/api.htmlを開く jigjpと打ってボタンを押す
PC と同じようにログインできましたと表示されます。
自分のつくった API が、インターネットに公開されています。この URL を教えれば、他の人のプログラムからも叩けます。
Tip
もとのページ(https://自分のURL)も、そのまま残っています。
index.htmlを触らなかったので、2 つのページが 1 つのアプリの中に並んでいる状態です。
index.htmlにリンクを 1 行足せば、もとのページから今日のページに飛べます。
<a href="./api.html">APIハンズオンのページへ</a>ここまでで、API のつくり方は 4 パターン覚えました。
| パターン | 使うもの |
|---|---|
| 決まったものを返す | new Response("文字") |
| まとめて返す | Response.json({ ... }) |
| 送られたものを受け取る | await req.json() |
| 送る側から呼ぶ | fetch(URL, { method, headers, body }) |
今日つくったのはPOSTでした。もう 1 つのメソッド、GETもやってみましょう。
/greeting-meにnameを渡すと、Hello, {渡した名前}が返ってくるAPI です。渡す方法は、前の章で勉強したクエリパラメータです。URL の後ろに?name=taroのようにくっつけます。
server.jsのreturn serveDir(req, {のすぐ上に、以下を貼る
if (req.method === "GET" && pathname === "/greeting-me") {
const name = new URL(req.url).searchParams.get("name");
return new Response("Hello, " + name);
}Cmd + Sで保存して、push する- 1〜2 分待って、ブラウザのアドレスバーに以下を入力して Enter
https://自分のURL/greeting-me?name=taro
taroの部分を自分の名前に変えて、もう一度 Enter
1 回目はHello, taro、2 回目はHello, 自分の名前と表示されます。URL を変えると返事が変わりました。
気づいたでしょうか。ボタンをつくらずに動作確認ができました。
GETはアドレスバーに URL を入れるだけで叩けるので、サーバー側だけを先に確かめられます。動かないときに「サーバーが悪いのか、ブラウザ側が悪いのか」を自分で切り分けられるので、これは覚えておくと便利です。
逆にPOSTの API は、アドレスバーからは叩けません。アドレスバーに URL を入れる操作がGETだからです。だから/authはブラウザ側をつくってから確認しました。
new URL(req.url).searchParams.get("name")これで、クエリパラメータのnameの中身を取り出せます。?name=taroで来たらtaroが入ります。Hello, nullと出たら、URL の?name=を書き忘れています。
api.htmlの</body>のすぐ上(<script>の行より上)に足します。
<div>
<input type="text" id="nameInput">
<button id="greetingMeButton">greeting me</button>
<span id="greetingMeResult"></span>
</div>api.jsのいちばん下に足します。
document.querySelector("#greetingMeButton").onclick = async () => {
const name = document.querySelector("#nameInput").value;
const response = await fetch("/greeting-me?name=" + name);
document.querySelector("#greetingMeResult").innerText = await response.text();
};GETなのでfetchは URL 1 つだけ。受け取るのは文字なのでresponse.text()です。
思いつかない人は、この中から選んでください。手順は同じで、server.jsに足して push、URL 直打ちで確認、必要ならボタンを置く、です。
やさしい
/today… 今日の気分を JSON で返す/lucky… 好きな数字を返す
ふつう
/double?number=5… 渡された数字を 2 倍にして返す- ヒント:
Number(受け取った値) * 2
- ヒント:
/count?text=あいうえお… 渡された文字が何文字か返す- ヒント:
受け取った値.length
- ヒント:
むずかしい
/dice… 1〜6 のランダムな数字を返す- ヒント:
Math.floor(Math.random() * 6) + 1
- ヒント:
/greeting-time… 時間帯によって「おはよう」「こんにちは」「こんばんは」を返す- ヒント:
new Date().getHours()
- ヒント:
いまつくった API は、いつでも成功したことになっています。
Deno のセクションで、存在しない URL を開くとNot Foundと出ましたね。あれは404というステータスコードです。前の章で名前だけ出てきたものです。
自分の API でも返せます。
if (req.method === "GET" && pathname === "/profile") {
const name = new URL(req.url).searchParams.get("name");
if (name !== "taniguchi") {
return Response.json({ message: "そんな人はいません" }, { status: 404 });
}
return Response.json({ name: "たにぐち", favorite: "ラーメン" });
}Response.json()の 2 番目に{ status: 404 }を渡すだけです。
なぜ必要か。ブラウザ側が「うまくいったのか、失敗したのか」を判断できるようになります。
const response = await fetch("/profile?name=hoge");
if (!response.ok) {
document.querySelector("#profileResult").innerText = "見つかりませんでした";
return;
}response.okは、ステータスコードが 200 番台ならtrue、404 などならfalseになります。
エラーも、API のルールの一部です。
見えるものが違います。
| どこで見るか | 何が見えるか | 開き方 |
|---|---|---|
| Deno Deploy の Logs(サーバー側) | どのパスが叩かれたか | Deno Deploy で自分のアプリのページ → Logs |
| 開発者ツールの Console / Network(ブラウザ側) | エラーと、何を送って何が返ってきたか | F12(または右クリック →「検証」) |
Logs に出ているパスの一覧は、server.jsのconsole.log(pathname)が出しています。叩かれたパスを毎回表示しているので、ここを見れば「ちゃんと叩けているか」が分かります。
/
/styles.css
/index.js
/welcome-messageこの 2 つは切り分けの道具です。ボタンを押して動かないときはこう考えてください。
- ログに何も出ない → リクエストがサーバーに届いていない(ブラウザ側の問題)
- ログには出るのに画面が変わらない → 届いてはいる(サーバーの返し方か、ブラウザの受け取り方の問題)
いちばん多いのがこれです。今日は保存しただけでは反映されません。 この 3 つを順に疑ってください。
- push できていない(ソース管理タブに変更が残っていないか)
- デプロイがまだ終わっていない(あと 1 分待つ)
- ブラウザが古いものを覚えている(
Cmd + Shift + Rで強制再読み込み)
手順は 0-3 にあります。
asyncの書き忘れです。 server.jsが壊れていて、サーバーが起動できていません。API だけでなく、これまでにつくったページも表示されなくなるので、いちばん焦るパターンです。
Deno Deploy の Logs に、こう出ています。
error: Uncaught SyntaxError: Unexpected reserved word
const body = await req.json();1-1 に戻ってDeno.serve(async (req) => {に直し、もう一度 push すれば元に戻ります。壊れっぱなしにはなりません。
もとのページを開いています。URL の後ろに/api.htmlが付いているか確認してください。
api.htmlが push できていません。新しいファイルなので、ソース管理タブで+を押す必要があります(1-4)。GitHub の自分のリポジトリのページを開いて、publicの中にapi.htmlがあるか確認してください。
開発者ツール(F12)のConsoleタブを見てください。出ているものと、その意味です。
| Console に出ているもの | 意味と対処 |
|---|---|
Cannot set properties of null |
api.htmlのidとapi.jsの#〇〇が違います。見比べてください |
Failed to load resource: api.js |
api.jsが push できていません。ソース管理タブで+を押したか確認してください |
キー名がズレています。今日いちばんズレやすいところです。 送る側と受け取る側で名前をそろえてください。
api.js body: JSON.stringify({ password: password })
↑ ここと
server.js if (body.password === "jigjp")
↑ ここが同じ名前
返すときも同じです。server.jsのmessageと、api.jsのdata.messageをそろえます。片方だけ直しても動きません。必ず両方セットで。
これまでの書き換えで消してしまったのかもしれません。以下のコマンドでserver.jsだけを最初の状態に戻せます。
curl -O https://raw.githubusercontent.com/jigintern/template-deno-dev/main/server.jsWarning
このコマンドはserver.jsを上書きします。これまでに書き換えた文言も、今日つくった API も消えます。
実行する前に声をかけてください。
遠慮なく声をかけてください。
public/api.html(全体)
今日新しくつくったファイルなので、全体を載せます。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>API ハンズオン</title>
<link rel="stylesheet" href="./styles.css">
</head>
<body>
<h1>ログイン</h1>
<div>
<input type="password" id="passwordInput" placeholder="パスワード">
<button id="authButton">ログイン</button>
<span id="authResult"></span>
</div>
<script type="module" src="./api.js"></script>
</body>
</html>public/api.js(全体)
こちらも今日新しくつくったファイルです。
document.querySelector("#authButton").onclick = async () => {
const password = document.querySelector("#passwordInput").value;
const response = await fetch("/auth", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ password: password }),
});
const data = await response.json();
document.querySelector("#authResult").innerText = data.message;
};server.js に足したもの
[!IMPORTANT]
server.jsは全体を載せません。これまでの書き換えで、中身が人それぞれ違うからです。 ここにあるのは今日足した部分だけです。まるごとコピーせず、自分のserver.jsと見比べてください。
まず、1 行だけ書き換えました。
Deno.serve(async (req) => {そして、以下がreturn serveDir(より上にあれば完成です。
if (req.method === "POST" && pathname === "/auth") {
const body = await req.json();
if (body.password === "jigjp") {
return Response.json({ ok: true, message: "ログインできました" });
}
return Response.json({ ok: false, message: "パスワードが違います" });
}public/index.htmlは今日は触っていません。もとのページはそのまま残っています。
完成形の見本は、この教材リポジトリの api フォルダにも置いてあります。困ったときに見比べてみてください。
次は Deno KV で、送られたデータを保存できるようにします。今日つくった/authのようなデータは、サーバーを再起動すると消えてしまいますが、その解決編です。