| marp | true |
|---|---|
| theme | default |
| paginate | true |
| header | APIハンズオン |
| style | section { font-family: "Hiragino Sans", "Yu Gothic", sans-serif; font-size: 30px; padding: 60px; } section.lead { text-align: center; justify-content: center; } section.lead h1 { font-size: 66px; } section.big { justify-content: center; text-align: center; } section.big p { font-size: 52px; line-height: 1.6; font-weight: bold; } section.chapter { background: #1f2933; color: #fff; justify-content: center; text-align: center; } section.chapter h1 { color: #fff; font-size: 60px; } section.chapter p { color: #9aa5b1; } h1 { color: #1f2933; } h2 { color: #1f2933; border-bottom: 3px solid #4dc0b5; padding-bottom: 12px; } strong { color: #0b8f82; } h1 strong, h2 strong, h3 strong, h4 strong { color: #0b8f82; } code { font-size: 0.9em; } pre { font-size: 24px; line-height: 1.5; } table { font-size: 26px; margin: 0 auto; } th, td { padding: 13px 18px; } td:first-child, th:first-child { white-space: nowrap; } td code { white-space: nowrap; } .step { display: inline-block; background: #4dc0b5; color: #fff; border-radius: 50%; width: 52px; height: 52px; line-height: 52px; text-align: center; font-weight: bold; margin-right: 12px; } .note { font-size: 24px; color: #616e7c; margin-top: 36px; } .note.tight { margin-top: 18px; } /* --- 図用 --- */ .row { display: flex; align-items: center; justify-content: center; gap: 40px; margin-top: 46px; } .row.tight { margin-top: 26px; gap: 30px; } .box { border: 4px solid #7b8794; border-radius: 16px; padding: 26px 32px; text-align: center; font-size: 29px; font-weight: bold; color: #3e4c59; min-width: 240px; } .box .label { font-size: 21px; font-weight: normal; color: #7b8794; display: block; margin-bottom: 14px; } .box.api { border-color: #0b8f82; background: #f0fdfa; color: #0b8f82; } .box.api .label { color: #0b8f82; } .inner { background: #f5f7fa; border-radius: 12px; padding: 16px 22px; font-size: 25px; color: #3e4c59; margin-top: 14px; } .arrow { font-size: 56px; color: #4dc0b5; font-weight: bold; } .arrowlabel { text-align: center; color: #0b8f82; font-size: 22px; font-weight: bold; } .arrowlabel .arrow { display: block; line-height: 1; } .dim { opacity: 0.35; } /* --- 手順リスト --- */ .steps { margin-top: 26px; } .steps div { font-size: 27px; color: #3e4c59; line-height: 1.95; } .steps strong { color: #0b8f82; } .steps span { color: #7b8794; font-size: 23px; } .steps div.on { color: #0b8f82; font-weight: bold; } .ng { color: #e12d39; font-weight: bold; } /* --- 強調用ハイライト --- */ .steps div strong.hl, strong.hl { background: #4dc0b5; color: #fff; padding: 4px 14px; border-radius: 7px; font-weight: bold; font-size: 28px; } /* --- スクリーンショット --- */ .shot { margin-top: 28px; text-align: center; } .shot img { max-height: 300px; max-width: 100%; border: 3px solid #cbd2d9; border-radius: 10px; } /* --- server.js が2種類を返す図 --- */ .split { margin-top: 40px; text-align: center; } .split .stop { display: inline-block; border: 4px solid #0b8f82; background: #f0fdfa; border-radius: 14px; padding: 16px 46px; font-size: 30px; font-weight: bold; color: #0b8f82; } .split .sarrows { display: flex; justify-content: center; gap: 330px; font-size: 34px; color: #4dc0b5; line-height: 1.1; margin: 6px 0 2px; } .split .srow { display: flex; justify-content: center; gap: 36px; } .split .sbox { border: 3px solid #9aa5b1; border-radius: 12px; padding: 18px 24px; width: 380px; text-align: center; } .split .sbox.api { border-color: #0b8f82; background: #f0fdfa; } .split .sbox .stitle { display: block; font-size: 24px; font-weight: bold; color: #3e4c59; margin-bottom: 12px; } .split .sbox.api .stitle { color: #0b8f82; } .split .sbox .sitems { display: block; font-size: 21px; color: #7b8794; line-height: 1.7; } /* --- 三層構造との対比 --- */ .layers { margin-top: 44px; } .lrow { display: flex; align-items: center; gap: 9px; margin-bottom: 40px; } .lrow .llabel { width: 168px; font-size: 23px; font-weight: bold; color: #9aa5b1; flex-shrink: 0; } .lrow.on .llabel { color: #0b8f82; } .lrow .lbox { border: 3px solid #cbd2d9; border-radius: 10px; padding: 12px 14px; font-size: 22px; font-weight: bold; color: #7b8794; text-align: center; line-height: 1.25; } .lrow .lbox em { display: block; font-size: 17px; font-style: normal; font-weight: normal; color: #9aa5b1; margin-top: 3px; } .lrow.on .lbox { border-color: #0b8f82; background: #f0fdfa; color: #0b8f82; } .lrow.on .lbox em { color: #0b8f82; } .lrow .larrow { font-size: 22px; color: #4dc0b5; font-weight: bold; } /* --- 利点の予告 --- */ .benefits { margin-top: 66px; } .benefits div { font-size: 38px; font-weight: bold; color: #3e4c59; margin-bottom: 38px; } .benefits .bnum { display: inline-block; background: #4dc0b5; color: #fff; font-size: 25px; border-radius: 8px; padding: 7px 18px; margin-right: 22px; vertical-align: middle; } /* --- 略語の分解 --- */ .acronym { margin-top: 44px; } .arow { display: flex; align-items: baseline; gap: 0; margin-bottom: 24px; } .arow .ini { font-size: 68px; font-weight: bold; color: #4dc0b5; line-height: 1; width: 52px; } .arow .eng { font-size: 40px; font-weight: bold; color: #3e4c59; letter-spacing: 0.01em; width: 300px; } .arow .jp { margin-left: 24px; font-size: 30px; color: #616e7c; } .arow.on .ini { color: #0b8f82; } .arow.on .eng { color: #0b8f82; } .arow.on .jp { color: #0b8f82; font-weight: bold; } .sum { margin-top: 6px; padding-top: 26px; border-top: 3px solid #cbd2d9; font-size: 34px; font-weight: bold; color: #3e4c59; text-align: center; } .sum strong { color: #0b8f82; } /* --- 窓口の図(窓口 = サーバー側の表面。線1本で描く) --- */ .io { display: flex; align-items: flex-end; justify-content: center; gap: 0; margin-top: 56px; } .io .ioside { border: 4px solid #7b8794; border-radius: 16px; padding: 42px 30px; font-size: 28px; font-weight: bold; color: #3e4c59; min-width: 190px; text-align: center; } /* サーバー側。左辺だけを太くして、それを窓口として見せる */ .io .ioside.api { border: 4px solid #0b8f82; border-left: 14px solid #4dc0b5; background: #f0fdfa; color: #0b8f82; } .io .arrows { width: 300px; padding: 0 14px 14px; } .io .ioline { display: flex; flex-direction: column; align-items: center; margin: 14px 0; } .io .iolabel { font-size: 24px; font-weight: bold; color: #0b8f82; } .io .ioarrow { font-size: 30px; color: #4dc0b5; letter-spacing: -2px; line-height: 1.1; } .io .gatelabel { font-size: 22px; font-weight: bold; color: #0b8f82; margin-left: -6px; margin-bottom: 10px; white-space: nowrap; } |
Application
アプリケーションを
Programming
プログラムから使うための
Interface
窓口
アプリをプログラムから使うための窓口
ブラウザ
決まった形で頼む
──────▶
◀──────
答えが返る
↓ この面が窓口 = API
server.js
inter(間) + face(面) = 間にある面 厚みのない1枚の面
窓口はサーバー側の表面 銀行の窓口が銀行の一部なのと同じ
窓口はサーバー側の表面 銀行の窓口が銀行の一部なのと同じ
利点1 中身を知らなくても使える
利点2 見せたくないものを隠せる
| 裏側でやっていること | 自分が書くこと | |
|---|---|---|
| 天気 | 観測データを集めて スパコンで計算 | fetch("/weather") |
| 地図 | 膨大な画像から その地点を切り出す | fetch("/map") |
| 翻訳 | 大量の文章で学習したモデルで推論 | fetch("/translate") |
左は知らなくていい 書くのは右の1行だけ
自分で気象観測をしなくても 天気を出すアプリがつくれる
自分で気象観測をしなくても 天気を出すアプリがつくれる
ブラウザ側
api.html / api.js
開発者ツールで
誰でも中身が見える
誰でも中身が見える
⇄
API
サーバー側
server.js
中身は
誰にも見えない
誰にも見えない
パスワードの照合 APIキー データベースの情報は 全部この右側に置く
この見えない側をバックエンドと呼ぶ
この見えない側をバックエンドと呼ぶ
| これがルール | 今日つくるもの | |
|---|---|---|
| ① | どのURLか | /auth |
| ② | どのメソッドか | POST |
| ③ | 何を渡すか | password |
| ④ | 何が返るか | { ok, message } |
この4つが決まっていれば 別々の人がつくっても繋がる
②のGETとPOSTが 前の章でやったやつ
②のGETとPOSTが 前の章でやったやつ
// server.js
if (req.method === "POST" && pathname === "/auth") {
// ② メソッド ① URL
const body = await req.json();
// ③ 送られたもの を受け取る
return Response.json({ ok: true, message: "ログインできました" });
// ④ 返すもの
}
今日書くのは これだけ
API を増やすときも ①②③④を決めるだけ
API を増やすときも ①②③④を決めるだけ
import { serveDir } from "jsr:@std/http/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", ... });
});
テンプレートから始まったときの
書き換えたのは中身だけ 形はこのまま残っている
server.js書き換えたのは中身だけ 形はこのまま残っている
通信が来た
1 GETで
/welcome-message か? → バックエンドの情報 を返す2 どれでもない →
serveDir が public の中のファイル を返す
一番下の
serveDir は「どれにも当たらなかったとき」の受け皿
server.js
↙↘
public の中のファイル
index.html / styles.cssindex.js
バックエンドの情報
の文字
/welcome-messageの文字
画面をつくる部品も データも 同じ入口から 同じ形で返る
だから 1ファイルで両方できている
だから 1ファイルで両方できている
| 叩かれたURL | 上から順に見た結果 | 返ってきたもの |
|---|---|---|
/ |
どれにも当たらない → serveDir |
index.html |
/styles.css |
どれにも当たらない → serveDir |
CSS |
/index.js |
どれにも当たらない → serveDir |
JavaScript |
/welcome-message |
1番目のifに当たった | 「ようこそ」の文字 |
3回は
どちらも同じ
public の中のファイル 1回は バックエンドの情報どちらも同じ
server.js が 同じ形で返している
1 開発者ツール(F12)→ Network タブ
2 ページを再読み込みすると 4行ならぶ
Initiator を見ると 誰が呼んだかが分かるstyles.css と index.js は (index) から つまりHTMLが呼んでいる
1 クローンしたフォルダを開く 例: deno-app
2
server.js に窓口を1つ足す3 直すたびに push する 今日は自分のPCでは動かさない デプロイ先のURLで確かめる
4 デプロイ先のURLに
/api.html を付けると スマホからも使える
作業するのは自分のリポジトリ
教材リポジトリ(
教材リポジトリ(
intern-dev-tutorial)ではない
1 サーバー側に書く server.js
2 ブラウザ側を新しくつくる api.html / api.js
3 push する
4
/api.html を開いて ボタンで確かめる
保存しただけでは 反映されない 動いているのはGitHubに置いたコード
| ファイル | 今日の扱い |
|---|---|
server.js |
return serveDir( の上に足す |
public/api.html |
新しくつくる 今日の画面 |
public/api.js |
新しくつくる 今日書くJSは全部ここ |
もとの
index.html は 触らない 別のページをつくるserveDir より下に書くとたどりつかないので 必ずその上へ
