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
890
【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
410
Denoについて、同人誌記事を出しました+update
toranoana
0
110
【虎の穴ラボ Tech Talk #2】プロンプトエンジニアリング
toranoana
0
41
20241121_[TechTalk#2]虎の穴ラボでのLLMについて取り組み紹介
toranoana
0
61
社内チャットへRAG導入した話(Tech Talk #2)
toranoana
0
83
Deno Deploy で Web Cache API を 使えるようになったので試した知見
toranoana
1
380
【虎の穴ラボ Tech Talk】虎の穴ラボTech Talk説明資料
toranoana
0
250
虎の穴ラボ Tech Talk_CDKでFargate環境構築
toranoana
1
280
虎の穴ラボスキルアップ支援制度の利用例
toranoana
0
3.3k
Other Decks in Technology
See All in Technology
2025-02-21 ゆるSRE勉強会 Enhancing SRE Using AI
yoshiiryo1
1
330
モノレポ開発のエラー、誰が見る?Datadog で実現する適切なトリアージとエスカレーション
biwashi
6
810
Amazon S3 Tablesと外部分析基盤連携について / Amazon S3 Tables and External Data Analytics Platform
nttcom
0
130
Tech Blogを書きやすい環境づくり
lycorptech_jp
PRO
1
240
Oracle Base Database Service 技術詳細
oracle4engineer
PRO
6
57k
ハッキングの世界に迫る~攻撃者の思考で考えるセキュリティ~
nomizone
13
5.2k
プロセス改善による品質向上事例
tomasagi
2
2.5k
Goで作って学ぶWebSocket
ryuichi1208
0
190
速くて安いWebサイトを作る
nishiharatsubasa
10
12k
PL900試験から学ぶ Power Platform 基礎知識講座
kumikeyy
0
130
株式会社EventHub・エンジニア採用資料
eventhub
0
4.3k
Moved to https://speakerdeck.com/toshihue/presales-engineer-career-bridging-tech-biz-ja
toshihue
2
740
Featured
See All Featured
The Power of CSS Pseudo Elements
geoffreycrofte
75
5.5k
Easily Structure & Communicate Ideas using Wireframe
afnizarnur
193
16k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7.1k
Writing Fast Ruby
sferik
628
61k
How STYLIGHT went responsive
nonsquared
98
5.4k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
251
21k
Producing Creativity
orderedlist
PRO
344
39k
Side Projects
sachag
452
42k
Fight the Zombie Pattern Library - RWD Summit 2016
marcelosomers
233
17k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
6
550
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
29
1k
How GitHub (no longer) Works
holman
314
140k
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