🏗️
connect-web x connect-query x mswを使った開発環境構築
Connectを利用する機会があったので、使い方を共有します。
内容
Open API Schemaを利用したスキーマ駆動開発をする際、storybook x msw x tanstack-queryを採用することが多いです。
この記事ではこの構成と同等の環境を整える方法を共有します。
1. Protocol Buffer Schemaを元に型定義等を生成
package追加
# plugin
yarn add --dev @bufbuild/protoc-gen-connect-web @bufbuild/protoc-gen-es @bufbuild/protoc-gen-connect-query
# runtime
yarn add @bufbuild/connect-web @bufbuild/protobuf @bufbuild/connect-query
codegen設定ファイルを作成
# buf.gen.yaml
version: v1
plugins:
- name: es
out: src/gen
opt:
- target=ts
- import_extension=none
- name: connect-web
out: src/gen
opt:
- target=ts
- import_extension=none
- name: connect-query
out: src/gen
opt:
- target=ts
- import_extension=none
schemaを用意
syntax = "proto3";
package example.greet.v1;
message GreetRequest {
string name = 1;
}
message GreetResponse {
string greeting = 1;
}
service GreetService {
rpc Greet(GreetRequest) returns (GreetResponse) {}
}
codegen
buf generate --template buf.gen.yaml proto_dir
2. mock data factory作成
import type { PlainMessage } from '@bufbuild/protobuf';
import { GreetResponse } from '@/gen/example/greet/v1/greet_pb';
export const mockGreetResponse = {
base: (
override?: Partial<PlainMessage<GreetResponse>>
): PlainMessage<GreetResponse> => ({
greeting: 'hello world!',
...override,
}),
};
3. msw handler作成
import { rest } from 'msw';
import { GreetService } from '@/gen/example/greet/v1/greet_connectweb';
import { delayedResponse, path } from '@/libs/msw/util';
import { mockGreetResponse } from '../data';
const API_ENDPOINT = {
greet: path({
path: `/${GreetService.typeName}/${GreetService.methods.greet.name}`,
}),
} as const;
export const mockGreetHandler = {
greet: {
success: () =>
rest.post(API_ENDPOINT.greet, (_req, _res, ctx) => {
return delayedResponse({})(
ctx.status(200),
ctx.json(mockGreetResponse.base())
);
}),
error: {
unauthorized: () =>
rest.post(API_ENDPOINT.greet, (_req, _res, ctx) => {
return delayedResponse({})(ctx.status(400));
}),
},
},
};
delayedResponse, pathの実装
import { createResponseComposition, context } from 'msw';
const isTesting = process.env.NEXT_IS_TEST === 'true';
export const delayedResponse = ({
millisecond = 1000,
}: {
millisecond?: number;
}) =>
createResponseComposition(undefined, [
context.delay(isTesting ? 0 : millisecond),
]);
export const delayedResponseOnce = createResponseComposition({ once: true }, [
context.delay(isTesting ? 0 : 1000),
]);
const CLIENT_PATH: Record<'default', string> = {
default: process.env.NEXT_PUBLIC_API_BASE_URL || '',
};
export const path = ({
path,
client = 'default',
}: {
path: string;
client?: 'default';
}) => {
return `${CLIENT_PATH[client]}${path}`;
};
4. client作成
import { createConnectTransport } from '@bufbuild/connect-web';
import { authInterceptor } from './interceptors';
export const connectTransport = createConnectTransport({
baseUrl: process.env.NEXT_PUBLIC_API_BASE_URL || '',
interceptors: [authInterceptor],
});
interceptor例
Authorization Headerにaccess token付与
import { Interceptor } from '@bufbuild/connect';
export const authInterceptor: Interceptor = (next) => async (req) => {
const accessToken = getAccessToken();
if (accessToken != null) {
req.header.set('Authorization', `Bearer ${accessToken}`);
}
return await next(req);
};
5. 呼び出し(tanstack-query)
tanstack-queryのsetupは省略します。
import { useQuery } from '@tanstack/react-query';
import { greet } from '@/gen/example/greet/v1/greet-GreetService_connectquery';
export const HogeComponent = () => {
const {data} = useQuery(greet.useQuery({ name: 'hoge' }))
console.log(data)
return (
<div>hoge</div>
)
6. storybook
storybookやmswのsetupは省略します。
import type { Meta, StoryObj } from '@storybook/react';
import { mockGreetHandler } from '@/mocks/server/handlers';
import { HogeComponent } from './HogeComponent';
const meta: Meta<typeof HogeComponent> = {
component: HogeComponent,
parameters: {
controls: { expanded: true },
},
};
export default meta;
export const Base: StoryObj<typeof HogeComponent> = {
args: {},
parameters: {
msw: {
handlers: [mockGreetHandler.greet.success()],
},
},
};
おわりに
エコシステムが整っていたので、意外とすんなりいつもの開発パターンに持ち込めてgRPC/Connectにとても良い印象を持ちました
Discussion