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
2.8k
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
5.1k
TypeSpec を使い倒してる #kyotojs 22 / Use up TypeSpec
euxn23
2
670
NestJS アプリケーションから Swagger を自動生成する / nestjs-meetup-tokyo-01
euxn23
1
2.2k
Other Decks in Programming
See All in Programming
Simple組み合わせ村から大都会Railsにやってきた俺は / Coming to Rails from the Simple
moznion
3
4.2k
『改訂新版 良いコード/悪いコードで学ぶ設計入門』活用方法−爆速でスキルアップする!効果的な学習アプローチ / effective-learning-of-good-code
minodriven
29
5.1k
自動で //nolint を挿入する取り組み / Gopher's Gathering
utgwkk
1
230
Lottieアニメーションをカスタマイズしてみた
tahia910
0
110
Domain-Driven Transformation
hschwentner
2
1.9k
Kubernetes History Inspector(KHI)を触ってみた
bells17
0
190
WebDriver BiDiとは何なのか
yotahada3
1
130
Compose でデザインと実装の差異を減らすための取り組み
oidy
1
290
バックエンドのためのアプリ内課金入門 (サブスク編)
qnighy
8
1.7k
盆栽転じて家具となる / Bonsai and Furnitures
aereal
0
3.6k
時計仕掛けのCompose
mkeeda
1
270
AWS Lambda functions with C# 用の Dev Container Template を作ってみた件
mappie_kochi
0
230
Featured
See All Featured
4 Signs Your Business is Dying
shpigford
182
22k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.1k
Large-scale JavaScript Application Architecture
addyosmani
510
110k
Building Applications with DynamoDB
mza
93
6.2k
Typedesign – Prime Four
hannesfritz
40
2.5k
XXLCSS - How to scale CSS and keep your sanity
sugarenia
248
1.3M
Navigating Team Friction
lara
183
15k
CoffeeScript is Beautiful & I Never Want to Write Plain JavaScript Again
sstephenson
160
15k
[Rails World 2023 - Day 1 Closing Keynote] - The Magic of Rails
eileencodes
33
2k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Evolution of real-time – Irina Nazarova, EuRuKo, 2024
irinanazarova
6
530
BBQ
matthewcrist
86
9.4k
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 小隊が好き