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
670
【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 虎の穴ラボ株式会社
フレックスタイム制度の活用例
toranoana
1
150
【toranoana.deno#17】 DenoでGitHubのバックアップ with ChatGPT
toranoana
1
200
Deno Deploy のログを NewRelic に送り込もう
toranoana
1
190
Babylon.js の CSG で遊ぼう!
toranoana
1
100
Deno Queue を使って OGP画像の遅延作成をやってみる
toranoana
1
580
JBUG 京都#1 ボードに機能追加する拡張機能を作った話
toranoana
1
570
JSRにモジュールを公開するまで/公開してから
toranoana
0
100
FreshとHonoでDeno KVを使い倒す
toranoana
1
290
サブカル業界Developers_実践_開発業務でのAIの活用
toranoana
1
410
Other Decks in Technology
See All in Technology
エンジニアリングマネージャーが紐解く、事業視点から組織文化まで、包括的アプローチの探求 / READYFOR
9ma3r
13
2.2k
実践ジオフェンス 効率的に開発するために
navitimejapan
PRO
2
130
Hyperledger Fabricの成長、成熟を振り返る / Looking back history of Hyperledger Fabric
gakumura
0
180
Dive Deep in Cloud Run: Automatic Base Image update
aoto
PRO
0
900
Zero Data Loss Autonomous Recovery Service サービス概要
oracle4engineer
PRO
0
3.1k
20240828_EM_Fest
kzkmaeda
4
360
Oracle Exadata Database Service(Dedicated Infrastructure):サービス概要のご紹介
oracle4engineer
PRO
0
9.3k
Eventual Detection Engineering
ken5scal
0
390
Azure SQL Database Hyperscale HA レプリカの監視
sansantech
PRO
0
190
分野に潜むツールの紹介
pojiro
1
330
Binary Authorizationと友達になろう / Let's be friends with Binary Authorization
iselegant
2
130
データウェアハウス製品のSnowflakeでPythonが動くって知ってました?
foursue
1
150
Featured
See All Featured
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
157
15k
Clear Off the Table
cherdarchuk
90
320k
A better future with KSS
kneath
235
17k
Building a Scalable Design System with Sketch
lauravandoore
458
32k
Bootstrapping a Software Product
garrettdimon
PRO
304
110k
GitHub's CSS Performance
jonrohan
1029
450k
Learning to Love Humans: Emotional Interface Design
aarron
270
40k
Practical Orchestrator
shlominoach
185
10k
The Brand Is Dead. Long Live the Brand.
mthomps
53
37k
Building Adaptive Systems
keathley
36
2.1k
Atom: Resistance is Futile
akmur
261
25k
Build The Right Thing And Hit Your Dates
maggiecrowley
29
2.2k
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