サーバレスアーキテクチャ
ひとまずDynamoDBを操作するLambdaを作ってみる
python
import json
import boto3
import time
from boto3.dynamodb.conditions import Key
from decimal import Decimal
def _resp(status, body=None):
return {
"statusCode": status,
"headers": {
"Content-Type": "application/json",
# "Access-Control-Allow-Origin": "*",
# "Access-Control-Allow-Methods": "GET,POST,OPTIONS",
# "Access-Control-Allow-Headers": "Content-Type",
},
"body": json.dumps(body or {})
}
def _get_method(event):
return (event.get("requestContext", {}).get("http", {}).get("method")
or event.get("httpMethod") or "GET")
def _parse_body(event):
body_raw = event.get("body") or ""
return json.loads(body_raw)
def _to_jsonable(x):
if isinstance(x, list):
return [_to_jsonable(i) for i in x]
if isinstance(x, dict):
return {k: _to_jsonable(v) for k, v in x.items()}
if isinstance(x, Decimal):
# 1758384246206 のような整数なら int に、そうでなければ float に
return int(x) if x == int(x) else float(x)
return x
dynamodb = boto3.resource("dynamodb")
TABLE_NAME = "Messages"
table = dynamodb.Table(TABLE_NAME)
def lambda_handler(event, context):
method = _get_method(event)
# if method == "OPTIONS":
# return _resp(200, {}) # CORSプリフライト
if method == "GET":
qs = event.get("queryStringParameters") or {}
roomId = (qs.get("roomId") or "general").strip()
try:
limit = int(qs.get("limit") or "50")
except (ValueError, TypeError):
limit = 50
res = table.query(
KeyConditionExpression=Key("roomId").eq(roomId),
ScanIndexForward=True, # 古→新
Limit=limit
)
items = res.get("Items", [])
items = _to_jsonable(items)
return _resp(200, {"ok": True, "items": items})
if method == "POST":
try:
body = _parse_body(event)
except json.JSONDecodeError:
return _resp(400, {"error": "Invalid JSON"})
roomId = (body.get("roomId") or "default").strip()
user = (body.get("user") or "annonymous").strip()
text = (body.get("text") or "").strip()
if not text:
return _resp(400, {"error": "Text is empty"})
ts = int(time.time() * 1000)
item = {
"roomId": roomId,
"ts": ts,
"user": user,
"text": text,
}
table.put_item(Item=item)
return _resp(200, {"ok": True, "item": item})
return _resp(405, {"error": "Method not allowd"})
Lambda関数URLも有効化しておく。CORSの設定をちゃんとしておかないと動かない。
フロントエンドとなるhtmlはこんな感じ。
html
<!doctype html>
<meta charset="utf-8" />
<title>Serverless Chat (mini)</title>
<style>
body { font-family: sans-serif; max-width: 600px; margin: 40px auto; }
#messages { border: 1px solid #ddd; padding: 8px; min-height: 200px; }
.msg { margin: 4px 0; padding: 6px; border-radius: 6px; background: #f7f7f7; }
</style>
<h1>Serverless Chat</h1>
<div id="messages"></div>
<form id="form">
<input id="user" placeholder="name" value="noim">
<input id="text" placeholder="message">
<button>Send</button>
</form>
<script>
const URL = "https://2smlcb36562yoidtepzyyskcve0zqnyb.lambda-url.ap-northeast-1.on.aws";
const messagesDiv = document.getElementById("messages");
const form = document.getElementById("form");
const userInput = document.getElementById("user");
const textInput = document.getElementById("text");
async function load() {
const res = await fetch(`${URL}?roomId=general&limit=20`);
const data = await res.json();
messagesDiv.innerHTML = data.items.map(m =>
`<div class="msg"><b>${m.user}</b>: ${m.text}</div>`
).join("");
}
form.addEventListener("submit", async e => {
e.preventDefault();
const body = { roomId: "general", user: userInput.value, text: textInput.value };
await fetch(URL, { method: "POST", headers: {"Content-Type":"application/json"}, body: JSON.stringify(body) });
textInput.value = "";
await load();
});
load();
setInterval(load, 5000); // 5秒ごとに更新
</script>
index.htmlを作成したら、ローカルで簡単なwebサーバを立てて上記のhtmlを表示させる。 index.htmlがあるディレクトリで以下を実行。
bash
python3 -m http.server 8000
ブラウザでアクセスして動作を確認する。
これからやりたいこと
https://tmokmss.hatenablog.com/entry/serverless-fullstack-webapp-architecture-2025
ここらへんを参考に、これからやりたいことは
- 認証
- フロントエンドをリッチに(Nextjsとか?)
- フロントエンドをCloudFrontとかから配信