Upgrade to PRO for Only $50/Year—Limited-Time Offer! 🔥
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
【Deno Fest】freshでちゃんとWebアプリを作ってみる
Search
虎の穴ラボ株式会社
October 18, 2023
Technology
0
1k
【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
550
Denoについて、同人誌記事を出しました+update
toranoana
0
180
【虎の穴ラボ Tech Talk #2】プロンプトエンジニアリング
toranoana
0
120
20241121_[TechTalk#2]虎の穴ラボでのLLMについて取り組み紹介
toranoana
0
110
社内チャットへRAG導入した話(Tech Talk #2)
toranoana
0
180
Deno Deploy で Web Cache API を 使えるようになったので試した知見
toranoana
1
580
【虎の穴ラボ Tech Talk】虎の穴ラボTech Talk説明資料
toranoana
0
420
虎の穴ラボ Tech Talk_CDKでFargate環境構築
toranoana
1
460
虎の穴ラボスキルアップ支援制度の利用例
toranoana
0
8.6k
Other Decks in Technology
See All in Technology
名刺メーカーDevグループ 紹介資料
sansan33
PRO
0
980
私も懇親会は苦手でした ~苦手だからこそ懇親会を楽しむ方法~ / 20251127 Masaki Okuda
shift_evolve
PRO
4
540
Noを伝える技術2025: 爆速合意形成のためのNICOフレームワーク速習 #pmconf2025
aki_iinuma
2
790
OpenShiftのBGPサポート - MetalLB+FRR-k8s編
orimanabu
0
150
ECMAScript仕様の最新動向: プロセスの変化と仕様のトレンド
uhyo
2
410
MAP-7thplaceSolution
yukichi0403
2
240
小規模チームによる衛星管制システムの開発とスケーラビリティの実現
sankichi92
0
190
Digitization部 紹介資料
sansan33
PRO
1
6.1k
Introduction to Bill One Development Engineer
sansan33
PRO
0
320
AIにおける自由の追求
shujisado
2
470
Symfony AI in Action
el_stoffel
2
350
GitLab Duo Agent Platformで実現する“AI駆動・継続的サービス開発”と最新情報のアップデート
jeffi7
0
110
Featured
See All Featured
Measuring & Analyzing Core Web Vitals
bluesmoon
9
690
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
Testing 201, or: Great Expectations
jmmastey
46
7.8k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
Writing Fast Ruby
sferik
630
62k
Navigating Team Friction
lara
191
16k
Typedesign – Prime Four
hannesfritz
42
2.9k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
253
22k
I Don’t Have Time: Getting Over the Fear to Launch Your Podcast
jcasabona
34
2.5k
The World Runs on Bad Software
bkeepers
PRO
72
12k
Making Projects Easy
brettharned
120
6.5k
Raft: Consensus for Rubyists
vanstee
140
7.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