Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
devio-2022-sapporo-moko.pdf
Search
mokonist
November 21, 2022
180
2
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
devio-2022-sapporo-moko.pdf
mokonist
November 21, 2022
More Decks by mokonist
See All by mokonist
devio-2024-Introduction-golang-backend
mokocm
7
4.8k
Google Cloud Next '24 Recap(Cloud Run/k8s)
mokocm
0
1.2k
1年間モダンなアプリへの移行支援をやってみて分かった、モダナイズの重要性と難しさ
mokocm
1
1.7k
レガシーシステム、モダナイズへの道筋
mokocm
0
1.8k
Application Composerのすすめ
mokocm
0
1.6k
DeepDive into Modern Development with AWS
mokocm
1
1.4k
IaCで全てが上手くいくと思うなよ_失敗事例のご紹介.pdf
mokocm
0
9.7k
re:Growth infra 2020
mokocm
0
4.8k
入社1年でAWS資格フルコンプして本書いた話
mokocm
0
3.9k
Featured
See All Featured
Understanding Cognitive Biases in Performance Measurement
bluesmoon
32
3k
We Analyzed 250 Million AI Search Results: Here's What I Found
joshbly
1
1.9k
Why Your Marketing Sucks and What You Can Do About It - Sophie Logan
marketingsoph
0
410
Site-Speed That Sticks
csswizardry
13
1.5k
Music & Morning Musume
bryan
48
7.4k
jQuery: Nuts, Bolts and Bling
dougneiner
66
8.6k
Pawsitive SEO: Lessons from My Dog (and Many Mistakes) on Thriving as a Consultant in the Age of AI
davidcarrasco
0
240
Large-scale JavaScript Application Architecture
addyosmani
515
110k
Principles of Awesome APIs and How to Build Them.
keavy
128
18k
Jamie Indigo - Trashchat’s Guide to Black Boxes: Technical SEO Tactics for LLMs
techseoconnect
PRO
0
670
Statistics for Hackers
jakevdp
799
230k
Future Trends and Review - Lecture 12 - Web Technologies (1019888BNR)
signer
PRO
0
3.7k
Transcript
Fastify + Zod + Prisma さくさくTypeScriptバックエンド開発 2022/11/21 AWS事業本部コンサルティング部 ⾨別 優多
ハッシュタグ: #devio2022
イベント終了後 スライド & ソースコードを公開します!
4 ※TypeScriptでバックエンド開発をされた事が ある方を対象のスライドです (AWS要素は無いです) Fastify + Zod + Prisma +
TypeScript 使っときゃ良い感じになるだけ 覚えて帰ってください!
5 スキーマファースト開発 してますか?(挨拶)
6 まさか手動でOpenAPI(Swagger) 書いて無いですよね・・・?
7 ・・・まさかExcelでAPI定義書なんて 書いて無いですよね・・・?
8 今日はなすこと 1. スキーマファースト開発について 2. Fastify + JSON Schema +
Zod を使った構成 3. Prismaはいいぞ
9 自己紹介 門別 優多 – moko クラスメソッド株式会社 AWS事業本部コンサルティング部 入社: 2019/07
Twitter, GitHub: @mokocm 2019年7月〜2021年6月 コンサルティング部 2021年7月〜2022年6月 CX事業本部MAD事業部 2022年7月〜コンサルティング部 2020-2022 APN AWS Top Engineer 2021 APN ALL AWS Certifications Engineer 2022年技能五輪国際大会クラウドコンピューティング職種 優秀賞 好きなAWSサービス: AWS CDK YAMLメンテ職人(もうやりたくない)
10 元ネタ https://dev.classmethod.jp/articles/fastify-zod-openapi/
11 スキーマファースト開発?
12 スキーマファースト開発 • スキーマを起点とした開発手法 • OpenAPI(Swagger)を定義して、スキーマを元にコードを生成して開 発を進める • OpenAPI(Swagger)で定義したスキーマを各チームで合意 •
フロント, 各マイクロサービス間等で実装待ちの時間を省ける • Input/Outputが明記されるため、実装もしやすい
13 OpenAPI(Swagger) • API設計をYAML, JSON等で記述 • Git管理することによりバージョン管理 & Pull Requestでレビュー
できる • WebUIでAPIの確認、実行が可能 • OpenAPI(Swagger)から言語の壁を越えて型を作成可能 • バックエンド、フロントと別ける場合などとても便利
14 OpenAPI(Swagger)を手動で書く • OpenAPI(Swagger)は下記のような記述で可能
15 OpenAPI(Swagger)を手動で書く
16 OpenAPI(Swagger)を手動で書く→課題がたくさん • 1万行を超えるOpenAPIファイルの爆誕 • JetBrainsのBookmark機能に救われました… • 宣言的に何度も同じような内容を書く必要のある箇所が多すぎて 苦しい •
ファイル分割するも可読性に難あり • そもそもEditorによってはファイル分割にPluginが対応していない 場合もある ↑とあるプロジェクトで YAML 1万行をメンテした時に言われたチャット
17 つらい • 不幸な事にOpenAPIから型を生成していなかった場合 • OpenAPIとバックエンドで利用する型を二箇所で手動記述 • OpenAPIとバックエンドの実装で乖離が発生・・・ • OpenAPIで定義したバリデーションと実装の乖離
• 1万行のYAMLとバックエンドで利用する型との差分をどうやって探 す・・・ ↑1万行のSwaggerに殺される人 ※1pt=約1.5h程度
解決方法は2つ
(1) OpenAPIファイルからプログラミング言語の 型を作成 型からValidation, Serialization, 実装で利用する ための型ファイルを出力
20 素晴らしいライブラリがたくさん https://www.npmjs.com/package/swagger-typescript-api
クライアント側などでは重宝するが、 根本的にYAMLを手動で書いているようじゃ 1万行YAMLメンテ職人から脱せない
22 課題を整理 1. 手動でOpenAPI(Swagger)を書きたくない 2. OpenAPI(Swagger)を自動出力したい 3. バックエンド側の型定義とかValidation, Serializationとか良い感じ にやりたい
4. リクエスト・レスポンススキーマの重複管理したくない 5. もう二度と1万行のYAMLメンテしたくない ↑無駄技術を習得してしまった図
これらを解決する方法、あります。
(2) OpenAPIを何らかのライブラリを利用して プログラム側から記述、出力
25 Fastify https://www.npmjs.com/package/fastify
• TypeScript(JavaScript) API Framework • TypeScriptでAPI作るならExpressに代わるファーストチョイスになってきた • Expressのつらい部分、遅い部分を解消 • 早くて使いやすい
26 Fastify import Fastify, { FastifyInstance, FastifyReply } from "fastify"; const server = Fastify({ logger: true, }); server.get("/", async (req: FastifyRequest, reply: FastifyReply) => { return { hello: "world" }; });
• FastifyでのValidationとSerializationはAjv JSON Schema Validatorで実行される • FastifyのRouteにJSON Schema形式でを定 義できる •
※厳密にはJSON Schema Draft 7 • Routeに入れたJSON Schemaを元に @fastify/swagger @fastify/swagger-uiを使 えばOpenAPI(Swagger)出力可能 • ちなみにOpenAPIはJSON Schema Draft 00 に準拠している • =ほぼ互換がある • JSON Schema手動で書くのはしんどい • 実質OpenAPI(Swagger)書いているようなもん 27 FastifyでのValidation&SerializationとOpenAPI(Swagger)
28 Zod https://www.npmjs.com/package/zod
• TypeScript界最強の静的型付けバリデーション • z.number().min(0).max(100).nullable()のようにチェーンで書いてい ける • TypeScriptで使うための型を出力可能 • Zodでスキーマ定義 〜バリデーションを行うのが一般的
29 Zod import { z } from "zod"; const Blog = z.object({ blog_id: z.number(), title: z.string().max(50), description: z.string().max(200), body: z.string().describe("ブログの中身(Markdown)"), thumnail_url: z.string().url().optional().describe("サムネイル(optional)"), author_id: z.string().describe("投稿者"), created_at: z.date().describe("作成日"), });
30 zod-to-json-schema https://www.npmjs.com/package/zod-to-json-schema
31 zod-to-json-schema • ZodからJSON Schemaを出力可能 • Zodの書きやすさ、Fastifyでのバリデーション、OpenAPI(Swagger) 出力、良いところ取り!! // TypeScriptの型
export type GetBlogsOutput = z.infer<typeof Blog>; // JSON Schema export const GetBlogsOutput = zodToJsonSchema(Blog, { name: "blog", }).definitions.blog;
32 まとめると 1. Zodスキーマを作成(Zodで定義)。なお今回はZodではバリデーションをしない 2. z.infer<typeof > でZodObjectを食わしてTypeScriptの型を作成 バックエンドでの実装の際に利用 3.
zot-to-json-schema で JSON Schemaを作成 4. FastifyのJSON Schemaに食わせる 5. AjvがJSON Schemaを元に良い感じにRequest/Responseをチェックしてくれる 6. ついでに @fastify/swagger が Swagger (OpenAPI v2) または OpenAPI v3 を作ってくれる 7. それらを @fastify/swagger-ui でブラウザで表示する事が可能 JSON Schemaを中間言語とすることでZod, Fastify, Ajv, OpenAPI (Swagger) 間の壁を良い感じに緩衝してくれて便利だね!
33 まとめると 1. Zodスキーマを作成(Zodで定義)。なお今回はZodではバリデーションをしない 2. z.infer<typeof > でZodObjectを食わしてTypeScriptの型を作成 バックエンドでの実装の際に利用 3.
zot-to-json-schema で JSON Schemaを作成 4. FastifyのJSON Schemaに食わせる 5. AjvがJSON Schemaを元に良い感じにRequest/Responseをチェックしてくれる 6. ついでに @fastify/swagger が Swagger (OpenAPI v2) または OpenAPI v3 を作ってくれる 7. それらを @fastify/swagger-ui でブラウザで表示する事が可能 JSON Schemaを中間言語とすることでZod, Fastify, Ajv, OpenAPI (Swagger) 間の壁を良い感じに緩衝してくれて便利だね! Zod一度定義しとけばmonorepo構成でフロント側でもZodの機能を使ったvalidationをできたりtRPC 導入してEnd to End typesafeできたりするよ!便利だね! ちなみに最近の @fastify/swaggerのアップデートで@fastify/swaggerにSwaggerをWebで表示する機能 が @fastify/swagger-ui に分離される破壊的変更があったよ!
34 まとめると 1. Zodスキーマを作成(Zodで定義)。なお今回はZodではバリデーションをしない 2. z.infer<typeof > でZodObjectを食わしてTypeScriptの型を作成 バックエンドでの実装の際に利用 3.
zot-to-json-schema で JSON Schemaを作成 4. FastifyのJSON Schemaに食わせる 5. AjvがJSON Schemaを元に良い感じにRequest/Responseをチェックしてくれる 6. ついでに @fastify/swagger が Swagger (OpenAPI v2) または OpenAPI v3 を作ってくれる 7. それらを @fastify/swagger-ui でブラウザで表示する事が可能 JSON Schemaを中間言語とすることでZod, Fastify, Ajv, OpenAPI (Swagger) 間の壁を良い感じに緩衝してくれて便利だね! Zod一度定義しとけばmonorepo構成でフロント側でもZodの機能を使ったvalidationをできたりtRPC 導入してEnd to End typesafeできたりするよ!便利だね! ちなみに最近の @fastify/swaggerのアップデートで@fastify/swaggerにSwaggerをWebで表示する機能 が @fastify/swagger-ui に分離される破壊的変更があったよ! Fastifyは活発にアップデートされていくので日々のウォッチが必要だね!そういえばzod-to-json-schema はconst assertionをz.nativeEnum()に入れた物を食わせると空配列が帰ってくるバグがあったけど自分で 直してPRを送って先日Mergeされたよ!まだZodとFastifyを組み合わせて使ってる人が少ないので 今回の登壇を元にしてチャレンジしてくれると嬉しいな!めちゃめちゃ便利だよ!
35 まとめると 1. Zodスキーマを作成(Zodで定義)。なお今回はZodではバリデーションをしない 2. z.infer<typeof > でZodObjectを食わしてTypeScriptの型を作成 バックエンドでの実装の際に利用 3.
zot-to-json-schema で JSON Schemaを作成 4. FastifyのJSON Schemaに食わせる 5. AjvがJSON Schemaを元に良い感じにRequest/Responseをチェックしてくれる 6. ついでに @fastify/swagger が Swagger (OpenAPI v2) または OpenAPI v3 を作ってくれる 7. それらを @fastify/swagger-ui でブラウザで表示する事が可能 JSON Schemaを中間言語とすることでZod, Fastify, Ajv, OpenAPI (Swagger) 間の壁を良い感じに緩衝してくれて便利だね! Zod一度定義しとけばmonorepo構成でフロント側でもZodの機能を使ったvalidationをできたりtRPC 導入してEnd to End typesafeできたりするよ!便利だね! ちなみに最近の @fastify/swaggerのアップデートで@fastify/swaggerにSwaggerをWebで表示する機能 が @fastify/swagger-ui に分離される破壊的変更があったよ! Fastifyは活発にアップデートされていくので日々のウォッチが必要だね!そういえばzod-to-json-schema はconst assertionをz.nativeEnum()に入れた物を食わせると空配列が帰ってくるバグがあったけど自分で 直してPRを送って先日Mergeされたよ!まだZodとFastifyを組み合わせて使ってる人が少ないので 今回の登壇を元にしてチャレンジしてくれると嬉しいな!めちゃめちゃ便利だよ! わからん ※ここまで理解するまで半月かかった
ようするに
37 ようするに 1. Zodのバリデーションコードを書く 3. 下処理したFastifyに食わせる 2. JSON SchemaとTypeScript向けの型を作る import
{ z } from "zod"; const Blog = z.object({ blog_id: z.number(), title: z.string().max(50), description: z.string().max(200), body: z.string().describe("ブログの中身(Markdown)"), thumnail_url: z.string().url().optional().describe("サムネイル(optional)"), author: z.string().describe("投稿者"), created_at: z.date().describe("作成日"), }); const getBlogsOutput = z.array(Blog) // TypeScriptの型 export type GetBlogsOutput = z.infer<typeof getBlogsOutput >; // JSON Schema export const GetBlogsOutput = zodToJsonSchema(getBlogsOutput, { name: ”getBlogOutput", }).definitions.getBlogOutput; server.get("/blogs", { schema: { response: { 200: { ...GetBlogsOutput, description: "取得成功” }, }, tags: ["blog"], }, handler: getBlogsHandler, });
38 Reply
39 OpenAPI(Swagger)
40 課題を整理 ✅手動でOpenAPI(Swagger)を書きたくない ✅OpenAPI(Swagger)を自動出力したい ✅バックエンド側の型定義とかValidation, Serializationとか良い感じに やりたい ✅リクエスト・レスポンススキーマの重複管理したくない ✅✅✅もう二度と1万行のYAMLメンテしたくない 元ネタとなった記事を書いてくれたきんじょーさん、ありがとうございます。
ちなみに
42 json-schema-to-zod 既存のJSON SchemaからZodに変換する神ライブラリも存在します https://github.com/StefanTerdell/json-schema-to-zod
本日の1万行のYAMLを倒す会は以上です。 ご静聴ありがとうございました・・・?
Fastify + Zod + Prisma さくさくTypeScriptバックエンド開発 2022/11/21 AWS事業本部コンサルティング部 ⾨別 優多
45 いつの間にか1万行YAMLに翻弄されて つらい話かしてなかった
46 時間ないので
47 結論 新規プロジェクトで TypeScript + RDBMS使うなら Prisma 使ってりゃ なんとかなる
48 Prisma Next-generation ORM for Node.js & TypeScript TypeScriptでORM使うなら TypeORM
と Prismaどちらかが採用されがち TypeORMより癖が無く、型もきちんとしていて書きやすいので、 黙ってPrisma使っときゃ良いのでは(個人の感想)
49 TypeORMのつらみ • Databaseを定義するEntityで、「データベース向け」の型と、 「TypeScript向けの型」を定義する必要があり、型の嘘がつけてし まう • nullable: true なカラムでTypeScriptではNullを許容しないケースなど
export class Blog { @PrimaryGeneratedColumn({ name: ‘blog_id’, type: 'int’, }) blog_id: number @Column({ name: 'thumbnail_url’, type: 'varchar’, default: null, nullable: true, }) thumbnail_url: string // | null }
50 TypeORMのつらみ • QueryBuilderを使って if 文の中で .andWhere()で条件を書ける • とても便利だが、可読性が悪くなりがち •
※500行近いleftJoin, andWhere地獄を見た感想 const output = getManager().getRepository(Blog).createQueryBuilder('blog'); if (condition) { output.andWhere(...query); } else if { output.leftJoinAndSelect(...leftJoin) output.andWhere(...query); } else { output.andWhere(...otherQuery) }
51 PrismaでのSchema定義
52 Write const blog = await prisma.blog.create({ data: { title:
body.title, description: body.description, body: body.body, thumnailUrl: body.thumnail_url, authorId: body.author_id, }, });
53 Read const blogs = await prisma.blog.findMany(); const blogs =
await prisma.blog.findMany({ where: { blogId: 1, }, }); const blogs = await prisma.blog.findMany({ where: { blogId: body.blog_id ?? undefined }, include: { authorUser: true, }, }); FindMany where where and join ・Where内のvalueがundefinedの場合、Prismaは「何もしない」。三項演算子、null合体演算子などで簡単に振る舞いを変えられる ・超複雑な条件がある場合もだいたいfindMany内だけで簡潔できる ・全て(SQL直書き以外)において型が効くため、安全に利用できる。最高! ・既存DBからのdb pull、改修後のMigration実施なども可能
54 まとめ
Fastify + Zod + Prisma、本当に便利なので みんなもっと使おう!!!!!!!
None