Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
【Deno Fest】freshでちゃんとWebアプリを作ってみる
Search
虎の穴ラボ株式会社
October 18, 2023
Technology
0
820
【Deno Fest】freshでちゃんとWebアプリを作ってみる
2023/10/20に開催のDeno Festでの発表資料です
https://deno-fest-2023.deno.dev/
虎の穴ラボ株式会社
October 18, 2023
Tweet
Share
More Decks by 虎の穴ラボ株式会社
See All by 虎の穴ラボ株式会社
Tailwind CSSとAtomic Designで実現する効率的な Web 開発の事例
toranoana
1
330
Denoについて、同人誌記事を出しました+update
toranoana
0
80
【虎の穴ラボ Tech Talk #2】プロンプトエンジニアリング
toranoana
0
26
20241121_[TechTalk#2]虎の穴ラボでのLLMについて取り組み紹介
toranoana
0
53
社内チャットへRAG導入した話(Tech Talk #2)
toranoana
0
59
Deno Deploy で Web Cache API を 使えるようになったので試した知見
toranoana
1
310
【虎の穴ラボ Tech Talk】虎の穴ラボTech Talk説明資料
toranoana
0
230
虎の穴ラボ Tech Talk_CDKでFargate環境構築
toranoana
1
250
虎の穴ラボスキルアップ支援制度の利用例
toranoana
0
1.2k
Other Decks in Technology
See All in Technology
サイボウズフロントエンドエキスパートチームについて / FrontendExpert Team
cybozuinsideout
PRO
5
38k
普通のエンジニアがLaravelコアチームメンバーになるまで
avosalmon
0
120
[Ruby] Develop a Morse Code Learning Gem & Beep from Strings
oguressive
1
190
なぜCodeceptJSを選んだか
goataka
0
180
社内イベント管理システムを1週間でAKSからACAに移行した話し
shingo_kawahara
0
200
サイバー攻撃を想定したセキュリティガイドライン 策定とASM及びCNAPPの活用方法
syoshie
3
1.4k
ハイテク休憩
sat
PRO
2
180
あの日俺達が夢見たサーバレスアーキテクチャ/the-serverless-architecture-we-dreamed-of
tomoki10
0
500
[JAWS-UG新潟#20] re:Invent2024 -CloudOperationsアップデートについて-
shintaro_fukatsu
0
120
サービスでLLMを採用したばっかりに振り回され続けたこの一年のあれやこれや
segavvy
2
550
オプトインカメラ:UWB測位を応用したオプトイン型のカメラ計測
matthewlujp
0
200
AWS環境におけるランサムウェア攻撃対策の設計
nrinetcom
PRO
0
170
Featured
See All Featured
YesSQL, Process and Tooling at Scale
rocio
170
14k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
VelocityConf: Rendering Performance Case Studies
addyosmani
326
24k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
910
Embracing the Ebb and Flow
colly
84
4.5k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Speed Design
sergeychernyshev
25
680
A designer walks into a library…
pauljervisheath
205
24k
GraphQLの誤解/rethinking-graphql
sonatard
67
10k
Sharpening the Axe: The Primacy of Toolmaking
bcantrill
38
1.9k
Documentation Writing (for coders)
carmenintech
67
4.5k
Transcript
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. FreshでちゃんとWebアプリを作ってみる
虎の穴ラボ株式会社 藤原佳顕 T O R A N O A N A L a b
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 目次
1. 自己紹介 2. 前回の振り返り 3. 今回やること 4. 利用技術 5. やったこと 6. まとめ
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 自己紹介ページ(藤原)
藤原 佳顕(ふじわら よしあき) ‣ Webエンジニア ‣ 新規事業担当(Fantia、Creatia)、アーキテクトチーム ‣ 前職:独立系ソフトウェア会社、主に GISとWeb、ライブラリ開発 ‣ TypeScript、Ruby on Rails、C#、C++ ‣ React、Vue、Angular ‣ 入社理由 ‣ 自分がスキルアップできそうな場所に行きたい ‣ オタク系の話ができるところに行きたい 好きなモノ ‣ シューティングゲーム、格闘ゲーム ‣ SF小説 ‣ プログラミング
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 前回の振り返り
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 前回の振り返り
toranoana.deno#11にて以下のような発表をしました • freshで普通のWebアプリを作る ◦ https://speakerdeck.com/toranoana/toranoana-dot-deno-numb er-11-freshdepu-tong-nowebapuriwozuo-ru • FORMからのPOSTを扱うとともに、CSRFへの対策を実施 • Cookieセッションにてセッション管理を実施 • フロント側で地図を表示する機能を実装 上記を踏まえて、今回はより実践的な Webアプリを作ってみたいと思います
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 今回やること
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 今回やること
• Railsに代表されるいわゆる普通のWebアプリケーションを作りたい ◦ 最もオーソドックスな方法を示すことで業務利用しやすくなるのでは? • RDBの活用 ◦ DBマイグレーション ◦ CRUDの実行 • セッションの実態を外部ストレージへ ◦ ログイン情報管理がCookieなのをやめる • セキュリティ対策 ◦ CSRFやSQLインジェクションなどの代表的なもの • それなりのフロントエンド ◦ freshを利用することで自動的に解消
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 利用技術
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 利用技術
• Deno最新版 • fresh ◦ Webフレームワーク • Prisma ◦ マイグレーションのみで利用 • PostgreSQL ◦ 基本的にはクラウドサービスでも、ローカルでも動くようにする • Deno KV ◦ セッション管理 ◦ 将来的にRedis等も使えるようにしておきたい
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. 利用技術
PrismaをORMで使わないのはなぜ? • 試した感じではDeno版は現状prisma://プロトコルにしか対応してなさそう? • 今回のモチベーション上以下が必要 ◦ よくある構成として開発はDocker等でDB立ち上げ ◦ 本番はクラウドサービスのRDB利用というパターンに対応したい ◦ したがって、柔軟に接続先を切り替えられるようにしておきたい • 将来的にPrismaがlocalhost等にも対応したときのためにマイグレーションでは利用する • (元々DenoでNessieがあるが、NessieのリポジトリでPrismaの方をおすすめされてい る)
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
RDBを扱う • PrismaによるDB管理 ◦ 先述通り、現段階では管理のみ • deno-postgresによるコネクション管理、クエリ発行 ◦ https://deno-postgres.com/#/ ◦ ORMは今回は使わず、直接SQL実行
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
マイグレーション • 基本的にはPrismaのドキュメント通り進める ◦ https://www.prisma.io/docs/guides/deployment/edge/deploy-to-deno-deploy ◦ ただし、上記だとスキーマの反映等はできるが、世代管理といったことは出来ない • Prismaのマイグレーションをdenoから動かす ◦ 生成:`deno run -A --unstable npm:prisma migrate dev --name` ◦ 実行:`deno run -A --unstable npm:prisma migrate dev`
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
• この方法の課題 ◦ PrismaのORM用モデルが生成されるがそちらは使えない ▪ 前述の通り、prisma://プロトコルが必要 ◦ Prismaドキュメントのdenoで扱うパターンと生成物に若干の差異がある ▪ `deno run -A --unstable npm:prisma generate --no-engine` ▪ マイグレーションでもコードが生成されるが、上記と微妙にずれる ◦ そもそもPrismaが生成するコードが環境ごとに差異がある ▪ .gitignoreをうまく使うか、Docker等で統一する必要があるかも
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. generator
client { provider = "prisma-client-js" previewFeatures = ["deno"] output = "../generated/client" } datasource db { provider = "postgresql" url = env("DATABASE_URL") } model User { id Int @id @default(autoincrement()) email String @unique name String? passwordDigest String @map("password_digest") createdAt DateTime @default(now()) @map("created_at") @db.Timestamptz() updatedAt DateTime @updatedAt @default(now()) @map("updated_at") @db.Timestamptz() @@map("users") }
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
deno-postgresによるコネクション管理、クエリ発行 • connection pool利用 ◦ 起動時に初期化、クエリ発行時に利用 • SQLは文字列として定義 ◦ (当然ですが)placeholder利用 • とりあえず今回は制約違反関連はDB依存 ◦ ユニーク制約など、事前チェックはせずDB側の制約でエラーにする
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. import
{ QueryObjectResult } from "postgres-query"; import { Pool } from "postgres"; const pool = new Pool( Deno.env.get("DATABASE_URL") || "postgresql://postgres:postgres@localhost:5432/mydb?schema=public", parseInt(Deno.env.get("DATABASE_POOL_SIZE") || "20"), ); export async function dbInit() { return await runQuery(`SELECT 1;`); } export async function runQuery<T>( query: string, args?: unknown[], ): Promise<QueryObjectResult<T> | null> { let result = null; let client = null; try { client = await pool.connect(); result = await client.queryObject<T>({ camelcase: true, text: query, args, }); } finally { client?.release(); } return result; } dbInitは起動確認用 →poolの初期化もこちらの方が良い可能性あり (現状だと環境変数の読み込みが先行している必要がある)
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. export
interface User { id: number; email: string; name: string; passwordDigest: string; createdAt: Date; updatedAt: Date; } export async function findByEmail(email: string): Promise<User | null> { const sql = UserSql.findByEmail; const result = await runQuery<User>(sql, [email]); return result?.rows[0] || null; } export async function register(registerUser: RegisterUser) { const sql = UserSql.insertUser; const args = [ registerUser.email, registerUser.name, await hashPassword(registerUser.password), ]; const result = await runQuery(sql, args); console.log(result); } やったこと
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
Cookieではないセッション管理 • セッション情報の保存先としてDeno KVを利用 • 実装はRailsを参考にする • Rails同様CookieにはセッションIDのみ保存 ◦ 値の実態をDeno KVにおいて、セッションIDで引けるようにする • セッションに入れる情報は主に以下 ◦ ログインしていればユーザーID ◦ CSRFトークン • 本来であればCookieやDeno KV、Redis等で使い分けできるようにしておいた方 がいいが、今回はDeno KVのみターゲット
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
実装の方針 • セッションIDの重複は絶対NG ◦ 重複するとユーザーを乗っ取ったような形になるため ◦ 新規ID発行時は書き込み前にユニークチェック • 参考にしたRailsではセッションIDをハッシュ化してストレージ側のキーにしている が今回はやらない • セッション期限は30日
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. async
function buildSession(header: Headers) { if (kv == null) { return null; } const sessionId: string | null = getCookies(header)[SESSION_COOKIE_NAME]; if (sessionId) { const session = await kv?.get<SessionObject>([ SESSION_VERSION, sessionId ]); if (session?.value != null) return session; } // 5回まではセッションIDの生成を繰り返す。Rails等を参考にするなら無 限でもよいが、開発途中で無限ループしそうなので一旦制限 for (let i = 0; i < 5; i++) { const sessionId = crypt32ByteHex(); const pkey = [SESSION_VERSION, sessionId]; const res = await kv.atomic().check( { key: pkey, versionstamp: null, }, ).set(pkey, {}, { expireIn: SESSION_EXPIRE }).commit(); if (res.ok) { return await kv.get<SessionObject>(pkey); } } やったこと セッションの初期化→全体にかかるmiddlewareで実行
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
• cookieにセッションIDとそれに紐づくオブジェクトがあればそれを利用 • ない場合は重複しないように以下ルールで生成 ◦ 安全なランダム文字列を生成してそれをIDとする ◦ Deno Kvでトランザクションかけつつ上記IDで空データを入れる ▪ 重複しないことだけでなく、重複しないデータが必要なので、セッションIDの チェックと同時にデータも必要 ◦ 完了したら上記で入れたデータをgetして終わり ◦ 失敗したら(重複等していれば)5回まで繰り返す • あとはこれに対してsetやgetをしていくのみ ◦ 更新時は既存をコピーする必要がある点だけ注意
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. やったこと
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. まとめと課題
• RDBを利用したデータのやりとりが出来た ◦ 実際にはfreshはフロントのみで使うケースも多そうですが、フルスタックフ レームワークとしてfreshを扱うような形が出来た • ログインやCSRFトークンの維持、セッションのストア化など最低限のことは出来た • コードがとっちらかってるので整理が必要 ◦ Deno KVべったりなのでそれ以外もストアとして使えるようにしたい(型ちゃん と付ける、classにするなど) • Deno KVのベストプラクティス等はこれからかも? ◦ コネクションの取り扱いやトランザクションの勘所など
Copyright (C) 2022 Toranoana Lab Inc. All Rights Reserved. まとめ
• 虎の穴ラボ採用募集中です! ▪ https://yumenosora.co.jp/tora-lab