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
Powerfully Typed TypeScript
Search
euxn23
May 11, 2024
Programming
4
3.1k
Powerfully Typed TypeScript
TSKaigi 2024
https://tskaigi.org/
Lightning Talk
euxn23
May 11, 2024
Tweet
Share
More Decks by euxn23
See All by euxn23
型付き API リクエストを実現するいくつかの手法とその選択 / Typed API Request
euxn23
10
7.1k
TypeSpec を使い倒してる #kyotojs 22 / Use up TypeSpec
euxn23
2
1.1k
NestJS アプリケーションから Swagger を自動生成する / nestjs-meetup-tokyo-01
euxn23
1
2.3k
Other Decks in Programming
See All in Programming
Чего вы не знали о строках в Python – Василий Рябов, PythoNN
sobolevn
0
160
LLMとPlaywright/reg-suitを活用した jQueryリファクタリングの実際
kinocoboy2
4
670
猫と暮らすネットワークカメラ生活🐈 ~Vision frameworkでペットを愛でよう~ / iOSDC Japan 2025
yutailang0119
0
220
Railsだからできる 例外業務に禍根を残さない 設定設計パターン
ei_ei_eiichi
0
340
Your Perfect Project Setup for Angular @BASTA! 2025 in Mainz
manfredsteyer
PRO
0
130
育てるアーキテクチャ:戦い抜くPythonマイクロサービスの設計と進化戦略
fujidomoe
1
150
クラシルを支える技術と組織
rakutek
0
190
『毎日の移動』を支えるGoバックエンド内製開発
yutautsugi
2
200
Django Ninja による API 開発効率化とリプレースの実践
kashewnuts
0
980
Web Components で実現する Hotwire とフロントエンドフレームワークの橋渡し / Bridging with Web Components
da1chi
3
1.9k
NetworkXとGNNで学ぶグラフデータ分析入門〜複雑な関係性を解き明かすPythonの力〜
mhrtech
3
1k
CSC305 Lecture 03
javiergs
PRO
0
230
Featured
See All Featured
What's in a price? How to price your products and services
michaelherold
246
12k
Build your cross-platform service in a week with App Engine
jlugia
232
18k
Embracing the Ebb and Flow
colly
88
4.8k
Optimising Largest Contentful Paint
csswizardry
37
3.4k
Practical Tips for Bootstrapping Information Extraction Pipelines
honnibal
PRO
23
1.5k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.7k
The Language of Interfaces
destraynor
162
25k
Why Our Code Smells
bkeepers
PRO
339
57k
Designing for Performance
lara
610
69k
Art, The Web, and Tiny UX
lynnandtonic
303
21k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
32
2.2k
For a Future-Friendly Web
brad_frost
180
9.9k
Transcript
Powerfully Typed TypeScript
fp-ts ほどガチガチじゃなくて 部分的に使えるライブラリを 5 分で紹介
zod 言わずと知れたバリデーション用ライブラリ 構造の定義からランタイムバリデータと型定義を生成できる infer が TS Server への負荷が高いので多用に注意 https://github.com/colinhacks/zod#basic-usage import
{ z } from "zod"; const User = z.object({ username: z.string(), }); User.parse({ username: "Ludwig" }); type User = z.infer<typeof User>;
neverthrow TS に Result 型をもたらしてくれるライブラリ throw の代わりに使ってエラー時の処理を追いやすくする 0 Dependencies https://zenn.dev/euxn23/articles/505fd9297eb2dc
const fetchUser = async (): Promise<Result<User, Error>> => { const response = await fetch(...) if (response.ok) { return ok((await response.json()) as User) } else { return err(new Error(...)) } } const fetchResult = await fetchUser() if (fetchResult.isErr()) { return fetchResult.error // <- Error 型である } return fetchResult.value // <- User 型である
ts-pattern TS にパターンマッチをもたらしてくれるライブラリ 0 Dependencies import { match, P }
from 'ts-pattern'; type Data = | { type: 'text'; content: string } | { type: 'img'; src: string }; type Result = | { type: 'ok'; data: Data } | { type: 'error'; error: Error }; const result: Result = ...; const html = match(result) .with({ type: 'error' }, () => <p>Oups! An error occured /p>) .with({ type: 'ok', data: { type: 'text' } }, (res) => <p>{res.data.content}</p>) .with({ type: 'ok', data: { type: 'img', src: P.select() } }, (src) => <img src={src} >) .exhaustive();
TypeScript だけじゃない……! OpenAPI + TypeScript で さらに Powerful に
openapi-typescript & openapi-fetch openapi-typescript は OpenAPI Schema から TS コードを生成
openapi-fetch はそれを利用した API Client コードを提供 https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-typescript#readme $ npx openapi-typescript ./path/to/my/schema.yaml -o ./path/to/my/schema.d.ts import { paths, components } from "./path/to/my/schema"; // <- generated by openapi-typescript // Schema Obj type MyType = components["schemas"]["MyType"]; // Path params type EndpointParams = paths["/my/endpoint"]["parameters"]; // Response obj type SuccessResponse = paths["/my/endpoint"]["get"]["responses"][200]["content"]["application/json"]["schema"]; type ErrorResponse = paths["/my/endpoint"]["get"]["responses"][500]["content"]["application/json"]["schema"];
openapi-typescript & openapi-fetch https://github.com/drwpow/openapi-typescript/tree/main/packages/openapi-fetch#readme import createClient from "openapi-fetch"; import type
{ paths } from "./my-openapi-3-schema"; // generated by openapi-typescript const client = createClient<paths>({ baseUrl: "https://myapi.dev/v1/" }); const { data, // only present if 2XX response error, // only present if 4XX or 5XX response } = await client.GET("/blogposts/{post_id}", { params: { path: { post_id: "123" }, }, }); await client.PUT("/blogposts", { body: { title: "My New Post", }, });
orval こちらも OpenAPI Schema から API Client を生成するライブラリ Axios を標準として
Custom Client を生成できる口がある。TanStack Query や SWR、 Zod との 連携もある。 https://github.com/anymaniax/orval/blob/master/samples/react-app import type { CreatePetsBody } from '../model'; import { customInstance } from '../mutator/custom-instance'; export const createPets = ( createPetsBody: CreatePetsBody, version: number = 1, ) => { return customInstance<void>({ url: `/v${version}/pets`, method: 'POST', headers: { 'Content-Type': 'application/json' }, data: createPetsBody, }); };
openapi-fetch と orval はおなじくらい
OpenAPI の定義も Powerful に
typespec TypeScript / C# 風味の DSL で OpenAPI Schema を書けるライブラリ
OpenAPI の yaml を手で書くのは大変だしエディタサポートが貧弱 TypeSpec は VSCode 向けの LSP を提供している 複雑でない module システムによりファイルの分割が容易に可能 コンパイル時に valid な記法か確認されるので安心
typespec https://typespec.io/ import "@typespec/http"; using TypeSpec.Http; model Store { name:
string; address: Address; } model Address { street: string; city: string; } @route("/stores") interface Stores { list(@query filter: string): Store[]; read(@path id: Store): Store; }
@hono/zod-openapi Hono の endpoint とparam の定義に zod を使うと OpenAPI も生えてくる
https://github.com/honojs/middleware/tree/main/packages/zod-openapi import { z } from '@hono/zod-openapi' const ParamsSchema = z.object({ id: z .string() .min(3) .openapi({ param: { name: 'id', in: 'path', }, example: '1212121', }), })
@hono/zod-openapi UserSchema の定義 const UserSchema = z .object({ id: z.string().openapi({
example: '123', }), name: z.string().openapi({ example: 'John Doe', }), age: z.number().openapi({ example: 42, }), }) .openapi('User')
@hono/zod-openapi route の定義 import { createRoute } from '@hono/zod-openapi' const
route = createRoute({ method: 'get', path: '/users/{id}', request: { params: ParamsSchema, }, responses: { 200: { content: { 'application/json': { schema: UserSchema, }, }, description: 'Retrieve the user', }, }, })
@hono/zod-openapi app をセットアップすれば完成 import { OpenAPIHono } from '@hono/zod-openapi' const
app = new OpenAPIHono() app.openapi(route, (c) => { const { id } = c.req.valid('param') return c.json({ id, age: 20, name: 'Ultra-man', }) }) // The OpenAPI documentation will be available at /doc app.doc('/doc', { openapi: '3.0.0', info: { version: '1.0.0', title: 'My API',
@hono/zod-openapi TypeScript の型情報の共有のみに頼り切らず OpenAPI エコシステムを利用 Framework Agnostic に成熟している path や
param 、 status code など詳細な定義が可能 最悪 Hono をやめても OpenAPI が残る 実装と API 定義が一体化しているため、 OpenAPI Schema が嘘になりにくい 実装から OpenAPI Schema を吐くことの良し悪しはケースバイケースで検討が必要
と書いていたところ、なんと Hono 公式で RPC が出た https://hono.dev/guides/rpc#client const route = app.post(
'/posts', zValidator( 'form', z.object({ title: z.string(), body: z.string(), }) ), (c) => { // ... return c.json( { ok: true, message: 'Created!', }, 201 ) } ) export type AppType = typeof route
Hono RPC client 側のコードは以下 import { AppType } from '.'
import { hc } from 'hono/client' const client = hc<AppType>('http://localhost:8787/') const res = await client.posts.$post({ form: { title: 'Hello', body: 'Hono is a cool project', }, }) if (res.ok) { const data = await res.json() console.log(data.message) }
Hono RPC OpenAPI を介さず Hono のみで完結する仕組み。実体は fetch と REST API
である zod 以外でも validator で params の型が定義されていれば OK 前述の @hono/zod-openapi とも組み合わせられるので、 OpenAPI の出力も可能 OpenAPI Client の生成を介さないので非常にパワフル だが、実は OpenAPI エコシステムを介してもあまり負担ではない プロジェクトが大きくなったときに AppType の推論がどれくらい重くなるかは未知数 OpenAPI Schema を介した自動生成では TS 側の負荷をオフロードできるメリットは大きい
まとめ 部分的に入れられるライブラリで小さくはじめよう TypeScript だけでなく OpenAPI エコシステムも強力 Hono の OpenAPI や
RPC はフル TS で開発する上でパワフル
自己紹介 ユーン (@euxn23) from ドワンゴ教育事業 フロントだけじゃない RABBIT 小隊が好き