$30 off During Our Annual Pro Sale. View Details »
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
560
Denoについて、同人誌記事を出しました+update
toranoana
0
190
【虎の穴ラボ Tech Talk #2】プロンプトエンジニアリング
toranoana
0
120
20241121_[TechTalk#2]虎の穴ラボでのLLMについて取り組み紹介
toranoana
0
110
社内チャットへRAG導入した話(Tech Talk #2)
toranoana
0
190
Deno Deploy で Web Cache API を 使えるようになったので試した知見
toranoana
1
590
【虎の穴ラボ Tech Talk】虎の穴ラボTech Talk説明資料
toranoana
0
420
虎の穴ラボ Tech Talk_CDKでFargate環境構築
toranoana
1
470
虎の穴ラボスキルアップ支援制度の利用例
toranoana
0
8.8k
Other Decks in Technology
See All in Technology
20251222_サンフランシスコサバイバル術
ponponmikankan
2
140
MySQLのSpatial(GIS)機能をもっと充実させたい ~ MyNA望年会2025LT
sakaik
0
120
フィッシュボウルのやり方 / How to do a fishbowl
pauli
2
390
Amazon Bedrock Knowledge Bases × メタデータ活用で実現する検証可能な RAG 設計
tomoaki25
6
2.4k
Entity Framework Core におけるIN句クエリ最適化について
htkym
0
130
投資戦略を量産せよ 2 - マケデコセミナー(2025/12/26)
gamella
0
450
AI with TiDD
shiraji
1
300
ESXi のAIOps だ!2025冬
unnowataru
0
390
AIエージェント開発と活用を加速するワークフロー自動生成への挑戦
shibuiwilliam
5
870
Knowledge Work の AI Backend
kworkdev
PRO
0
270
たまに起きる外部サービスの障害に備えたり備えなかったりする話
egmc
0
420
まだ間に合う! Agentic AI on AWSの現在地をやさしく一挙おさらい
minorun365
17
2.8k
Featured
See All Featured
Leadership Guide Workshop - DevTernity 2021
reverentgeek
0
170
How to build an LLM SEO readiness audit: a practical framework
nmsamuel
1
580
RailsConf 2023
tenderlove
30
1.3k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
0
31
Learning to Love Humans: Emotional Interface Design
aarron
274
41k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.6k
Measuring Dark Social's Impact On Conversion and Attribution
stephenakadiri
0
96
Intergalactic Javascript Robots from Outer Space
tanoku
273
27k
Making the Leap to Tech Lead
cromwellryan
135
9.7k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
115
94k
Navigating Weather and Climate Data
rabernat
0
53
Why Mistakes Are the Best Teachers: Turning Failure into a Pathway for Growth
auna
0
28
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