frourioでAPIにリクエストしたときにバイナリファイルを送信する場合、APIのレスポンスの型を定義しておかないとNext.js側で処理できずに詰まった時の事をまとめます。
Next.js側での実装
ファイルのダウンロードの方法は色々あるかと思いますが、Blobを使ってみることにしました。
zipファイルをダウンロードすることにします。
サンプルなので雰囲気ですがこんな感じになると思います
const apiClient = api(aspida());
const res = await apiClient.file.$get();
const blob = new Blob([res], { type: 'application/zip' });
const link = document.createElement('a');
link.href = URL.createObjectURL(blob);
link.download = `file.zip`;
link.click();
link.remove();
こんなわけでBlobオブジェクトを作らないといけないわけですが、コンストラクタに渡すデータはBufferにします。
というわけでAPI側の定義はBufferを返すようにします。
API側の実装
定義したまま値を返す
index.tsではBufferを返すように定義します。
// index.ts
export type Methods = {
get: {
resBody: Buffer;
};
};
でそうするとcontroller.ts側ではBufferを返さないといけないのですが、
// controller.ts
export default defineController(() => ({
get: async () => {
const buffer = await getBuffer();
return { status: 200, body: buffer };
},
}));
getBuffer
からBufferを返してもレスポンスがファイルのバイナリと一致しないのでファイルを開くのが失敗してしまいます。
定義とは別にBufferを返す
一方、fastifyでバイナリファイルを送信するサンプルはいくらでもあるので、それが使えないかやってみました。
controller.tsの実装を以下のように変えます。
// controller.ts
export type AdditionalRequest = {
reply: FastifyReply;
};
export default defineController(() => ({
get: async ({ reply }) => {
await getBuffer(reply);
return { status: 200, body: Buffer.from([])}; // Buffer.fromはダミー
},
}));
getBufferにFastifyReplyを渡したいので、まずAdditionalRequestでFastifyReplyをリクエストに追加するように定義します。
getメソッドでそれを受け取れるのでgetBufferに渡します。
getBuffer内ではFastifyReply.sendを使ってバイナリファイルを送信するようにします。
で、controllerのgetメソッド側ではbodyにBufferを返してあげないと型エラーになってしまうので空のBufferを返します。
実際にはFastifyReply.sendでレスポンスが返されているのでここで返している値は影響しません。
まとめ
こうすると型定義としてNext.js側ではBufferを受け取ることになり、バイナリファイルがダウンロードできます。