Skip to content

Latest commit

 

History

History
789 lines (551 loc) · 33.2 KB

File metadata and controls

789 lines (551 loc) · 33.2 KB

API ハンズオン

0. スライド

Note

このセクションにはスライド資料があります。

0-0. 今日の流れ

「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)ではありません。

0-1. 今日さわるファイル

3 つです。そのうち 2 つは、これから新しくつくります。

ファイル 今日の扱い
server.js return serveDir(の行の上に足すだけ
public/api.html 新しくつくる。今日の画面はここ
public/api.js 新しくつくる。今日書く JavaScript は全部ここ

public/index.htmlpublic/index.jspublic/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 行です。この行は消すとページが表示されなくなるので、デプロイできた人には必ず残っています。

0-2. デプロイしたアプリを開く

手順

  1. VSCode で、Deno のセクションでクローンしたフォルダ(例: deno-app)を開く
  2. ブラウザで https://deno.com/deploy を開いて、自分のアプリのページを表示する
  3. そこに出ている URL を開く(Deno のセクションでデプロイしたページです)

発行された URL は、こういう形をしています。

https://アプリ名.置き場所の名前.deno.net

こうなっていれば成功

Deno のセクションでつくったページが表示されます。

この 2 つのタブは、今日ずっと開いたままにしておいてください。

タブ 何に使うか
自分のアプリ(.deno.net 今日つくるページも API も、全部この URL の後ろにくっつけて開きます
Deno Deploy push したあとのデプロイの進み具合と、サーバー側のログを見ます

Note

この資料では、あなたの URL のことをhttps://自分のURLと書きます。 出てきたら、自分の URL に読み替えてください。

0-3. 直したら、毎回 push します

今日は、自分の PC でサーバーを動かしません。書いたコードは、https://自分のURLで動いているものを直接見て確認します。

ただし、あちらで動いているのはGitHub に置いてあるコードです。VSCode で保存しただけでは、まだ自分の PC の中にあるだけで、あちらには何も届いていません。

VSCode で保存          ← まだ自分のPCの中
    ↓
commit → push          ← ここでGitHubに届く
    ↓
GitHub
    ↓  自動でデプロイ(1〜2分かかる)
https://自分のURL に反映

つまり、書き換えるたびに commit と push が必要です。

毎回やる手順

  1. VSCode でCmd + S(Windows は Ctrl + S)で保存する
  2. 左側のソース管理(Source Control)タブを開く
  3. 変更したファイルの横の+ボタンを押す(ステージする)
  4. メッセージを書いて commit する
  5. push する
  6. 1〜2 分待つDeno Deploy の自分のアプリのページで、新しいビルドに緑のチェックが付けば反映済みです)
  7. ブラウザでCmd + Shift + R(Windows は Ctrl + Shift + R)で強制再読み込みして確認する

やり方を忘れた人はこちらを見てください。

準備は終わりです。手を動かしていきましょう。

1. 情報を送る API をつくる

/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

1-1. server.js/auth を追加する

手順

  1. server.jsを開く
  2. ファイルの上のほうにあるDeno.serve((req) => {asyncを足して、こう書き換える
Deno.serve(async (req) => {
  1. ファイルの下のほうにある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: "パスワードが違います" });
  }
  1. 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 で来たときだけ動くようにしています。前の章で勉強したGETPOSTが、ここに出てきます
  • pathnameに、叩かれた URL のパス(/authの部分)が入っています
  • await req.json()で、body に入っている JSON を取り出します
  • body.passwordjigjpと同じかどうかで、返す内容を変えています
  • returnしたら、そこでおわりです。合っていれば 1 つ目を返して終了、違っていれば下の行に進みます

asyncを足したのは、このawait req.json()のためです。awaitasyncが付いた関数の中でしか使えません。fetchawaitを使ったときもasync () => {とセットになっていました。awaitasyncはセットです。

そして返し方です。もとからある/welcome-messageは文字を 1 つ返していました。今日は JSON を返します。

new Response("文字")            // 文字を返す(これまで)
Response.json({ キー:  })     // JSONを返す(今日)

{ }の中にキー: 値をカンマで区切って並べます。今回はokmessageの 2 つを返しています。1 回のやりとりで、複数の情報をまとめて返せるのが JSON です。実際の Web アプリの API は、ほとんどこの形です。

1-2. public/api.html をつくる

次に、今日の画面をつくります。すでにあるindex.htmlとは別の、新しいページです。

手順

  1. VSCode の左側で、publicフォルダを右クリックする
  2. 新しいファイル」を選ぶ
  3. api.htmlと入力して Enter
  4. 開いた空のファイルに、以下を全部貼る
<!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>
  1. 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を読み込みます

1-3. public/api.js をつくる

今日書く JavaScript を入れるファイルを、新しくつくります。

手順

  1. VSCode の左側で、publicフォルダを右クリックする
  2. 新しいファイル」を選ぶ
  3. api.jsと入力して Enter
  4. 開いた空のファイルに、以下を全部貼る
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;
};
  1. Cmd + Sで保存する

書いたコードの意味

  • document.querySelector("#authButton")で、idauthButtonのボタンを取ってきます
  • .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.okdata.messageのようにキーを指定して取り出せます。

const data = await response.json();
data.ok        // true
data.message   // "ログインできました"

1-4. push する

3 つのファイルが揃いました。まとめて公開します。

手順

  1. commit して push する0-3 の手順)
  2. 1〜2 分待つ

Important

public/api.htmlpublic/api.js+を押すのを忘れないでください。 どちらも新しいファイルです。新しいファイルは+(ステージ)を押さないと commit に入らず、GitHub に届きません。

1-5. /api.html を開いて確認する

つくったページは、もとのページとは別の URL です。トップページを開いても出てきません。

開く URL 表示されるもの
https://自分のURL index.html … もとのページ
https://自分のURL/api.html api.html今日つくったページ

手順

  1. ブラウザのアドレスバーに、以下を直接入力して Enter
https://自分のURL/api.html

後ろの/api.htmlを忘れずに。これがないと、もとのページが開くだけでボタンはありません

  1. 入力欄にjigjpと打ってログインボタンを押す
  2. 次に、わざと違うパスワードを打ってボタンを押す

こうなっていれば成功

  • jigjpのとき → ログインできました
  • 違うとき → パスワードが違います

API が完成しました。送った情報でサーバーが判断して、その結果が返ってきています。

うまくいかない人は 3. うまくいかないときは を見てください。

Note

なぜ/api.htmlで開けるのでしょうか。 publicの中に置いたファイルは、URL の後ろにファイル名を付けると開けます。 https://自分のURL/api.htmlを開くと、server.jsifはどれにも当たりません。どれにも当たらなかったときの受け皿が、一番下のserveDirです。これがpublic/api.htmlを探して返してくれます。 ページを開くのも、API を叩くのも、同じ窓口へのリクエストです。違うのは、誰が答えるかだけです。

Note

今回はパスワードをserver.jsに直接書きました(ハードコーディングといいます)。これは練習用です。本物のアプリでこれをやると、GitHub にパスワードが公開されてしまいます。 ちゃんと扱う方法は ログインハンズオン にあります。

2. (発展) もっとやってみる

2-1. 送ったものを開発者ツールで見てみる

せっかくなので、POSTで送ったパスワードが URL に出ていないことを確認しましょう。

手順

  1. 開発者ツールを開く(F12、または右クリック →「検証」)
  2. Networkタブを開く
  3. ボタンをもう一度押す
  4. 一覧に出てきたauthをクリックする
  5. Payloadタブ(またはペイロード)を開く

こうなっていれば成功

passwordが body に入っていて、URL には出ていないことが確認できます。前の章で見た GitHub のログインと同じ形です。

Responseタブを開けば、返ってきた JSON もそのまま見られます。

2-2. スマホから叩いてみる

つくった API は、もう公開されています。push した時点でインターネットに出ているので、改めて公開する作業はありません。

つまり、この URL は自分の PC 以外からも叩けます。確かめてみましょう。

手順

  1. スマホのブラウザでhttps://自分のURL/api.htmlを開く
  2. jigjpと打ってボタンを押す

こうなっていれば成功

PC と同じようにログインできましたと表示されます。

自分のつくった API が、インターネットに公開されています。この URL を教えれば、他の人のプログラムからも叩けます。

Tip

もとのページ(https://自分のURL)も、そのまま残っています。 index.htmlを触らなかったので、2 つのページが 1 つのアプリの中に並んでいる状態です。 index.htmlにリンクを 1 行足せば、もとのページから今日のページに飛べます。

<a href="./api.html">APIハンズオンのページへ</a>

2-3. GET の API を 1 本

ここまでで、API のつくり方は 4 パターン覚えました。

パターン 使うもの
決まったものを返す new Response("文字")
まとめて返す Response.json({ ... })
送られたものを受け取る await req.json()
送る側から呼ぶ fetch(URL, { method, headers, body })

今日つくったのはPOSTでした。もう 1 つのメソッド、GETもやってみましょう。

/greeting-menameを渡すと、Hello, {渡した名前}が返ってくるAPI です。渡す方法は、前の章で勉強したクエリパラメータです。URL の後ろに?name=taroのようにくっつけます。

手順

  1. server.jsreturn serveDir(req, {すぐ上に、以下を貼る
  if (req.method === "GET" && pathname === "/greeting-me") {
    const name = new URL(req.url).searchParams.get("name");
    return new Response("Hello, " + name);
  }
  1. Cmd + Sで保存して、push する
  2. 1〜2 分待って、ブラウザのアドレスバーに以下を入力して Enter
https://自分のURL/greeting-me?name=taro
  1. taroの部分を自分の名前に変えて、もう一度 Enter

こうなっていれば成功

1 回目はHello, taro、2 回目はHello, 自分の名前と表示されます。URL を変えると返事が変わりました。

GET はブラウザだけで確認できます

気づいたでしょうか。ボタンをつくらずに動作確認ができました。

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()です。

2-4. お題の例

思いつかない人は、この中から選んでください。手順は同じで、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()

2-5. 見つからないときは 404 を返す

いまつくった 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 のルールの一部です。

3. うまくいかないときは

まず、中を覗ける場所が 2 つあります

見えるものが違います。

どこで見るか 何が見えるか 開き方
Deno Deploy の Logs(サーバー側) どのパスが叩かれたか Deno Deploy で自分のアプリのページ → Logs
開発者ツールの Console / Network(ブラウザ側) エラーと、何を送って何が返ってきたか F12(または右クリック →「検証」)

Logs に出ているパスの一覧は、server.jsconsole.log(pathname)が出しています。叩かれたパスを毎回表示しているので、ここを見れば「ちゃんと叩けているか」が分かります。

/
/styles.css
/index.js
/welcome-message

この 2 つは切り分けの道具です。ボタンを押して動かないときはこう考えてください。

  • ログに何も出ない → リクエストがサーバーに届いていない(ブラウザ側の問題)
  • ログには出るのに画面が変わらない → 届いてはいる(サーバーの返し方か、ブラウザの受け取り方の問題)

症状ごとの対処

直したのに、何も変わらない

いちばん多いのがこれです。今日は保存しただけでは反映されません。 この 3 つを順に疑ってください。

  1. push できていない(ソース管理タブに変更が残っていないか)
  2. デプロイがまだ終わっていない(あと 1 分待つ)
  3. ブラウザが古いものを覚えている(Cmd + Shift + Rで強制再読み込み)

手順は 0-3 にあります。

ページ全体が表示されなくなった

asyncの書き忘れです。 server.jsが壊れていて、サーバーが起動できていません。API だけでなく、これまでにつくったページも表示されなくなるので、いちばん焦るパターンです。

Deno DeployLogs に、こう出ています。

error: Uncaught SyntaxError: Unexpected reserved word
    const body = await req.json();

1-1 に戻ってDeno.serve(async (req) => {に直し、もう一度 push すれば元に戻ります。壊れっぱなしにはなりません。

ボタンも入力欄も出てこない

もとのページを開いています。URL の後ろに/api.htmlが付いているか確認してください。

ページ自体が Not Found

api.htmlが push できていません。新しいファイルなので、ソース管理タブで+を押す必要があります(1-4)。GitHub の自分のリポジトリのページを開いて、publicの中にapi.htmlがあるか確認してください。

ボタンを押しても何も起きない

開発者ツール(F12)のConsoleタブを見てください。出ているものと、その意味です。

Console に出ているもの 意味と対処
Cannot set properties of null api.htmlidapi.js#〇〇が違います。見比べてください
Failed to load resource: api.js api.jsが push できていません。ソース管理タブで+を押したか確認してください

undefined と表示される

キー名がズレています。今日いちばんズレやすいところです。 送る側と受け取る側で名前をそろえてください。

api.js      body: JSON.stringify({ password: password })
                                    ↑ ここと
server.js   if (body.password === "jigjp")
                      ↑ ここが同じ名前

返すときも同じです。server.jsmessageと、api.jsdata.messageをそろえます。片方だけ直しても動きません。必ず両方セットで。

server.jsreturn serveDir( が見当たらない

これまでの書き換えで消してしまったのかもしれません。以下のコマンドでserver.jsだけを最初の状態に戻せます。

curl -O https://raw.githubusercontent.com/jigintern/template-deno-dev/main/server.js

Warning

このコマンドはserver.js上書きします。これまでに書き換えた文言も、今日つくった API も消えます。 実行する前に声をかけてください。

どうしても直らない

遠慮なく声をかけてください。

4. 今日書いたコードの全体

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のようなデータは、サーバーを再起動すると消えてしまいますが、その解決編です。