ライブラリをインストール
npm i graphql-upload-ts
Resolver を作成
あくまでサンプルなので、ディレクトリを作成する処理などを入れています。実業務では AWS S3 などに保存することになるでしょう。
import { Args, Mutation, Resolver } from '@nestjs/graphql';
import { createWriteStream } from 'fs';
import { FileUpload, GraphQLUpload } from 'graphql-upload-ts';
import { existsSync, mkdirSync } from 'fs';
@Resolver()
export class UploaderResolver {
@Mutation(() => Boolean)
async singleUpload(
@Args({ name: 'file', type: () => GraphQLUpload })
{ createReadStream, filename }: FileUpload,
) {
const writeDirName = './stores';
if (!existsSync(writeDirName)) {
mkdirSync(writeDirName);
}
return new Promise((resolve, reject) => {
createReadStream()
.pipe(createWriteStream(`${writeDirName}/${filename}`))
.on('finish', () => {
console.log('完了しました。');
resolve(true);
})
.on('error', (error) => {
console.error('File write error:', error);
reject(false);
});
});
}
}
上の UploaderResolver
は app.module.ts
の providers
に登録してください。
providers: [AppService, UploaderResolver],
main.ts に app.use(graphqlUploadExpress()); を設定する
app.use(graphqlUploadExpress());
を main.ts で設定しなければ、ファイルのアップロードはうまくいきませんでした。
GraphQL: POST body missing, invalid Content-Type, or JSON object has no keys
というエラーが出ました。
GraphQL: POST body missing, invalid Content-Type, or JSON object has no keys
main.ts
は以下のようになります。
import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { ConfigService } from '@nestjs/config';
import { ValidationPipe } from '@nestjs/common';
import { graphqlUploadExpress } from 'graphql-upload-ts';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
const configService = app.get(ConfigService);
const appPort = configService.get<number>('app.port');
app.useGlobalPipes(new ValidationPipe());
app.use(graphqlUploadExpress());
await app.listen(appPort);
console.log(`App is running on: ${await app.getUrl()}`);
}
bootstrap();
リクエストヘッダーに apollo-require-preflight: true を設定する
リクエストヘッダのキーに apollo-require-preflight
, value に true
を設定します。
ヘッダーに `apollo-require-preflight:true
を設定しなければ、リクエストがうまく通りませんでした。
apollo-server(v3系)は非推奨となったので、@apollo/server(v4系)に移行しましょう
クエリを投げる
mutation singleUpload($file: Upload!) {
singleUpload(file: $file)
}
{"file": Upload}
ファイルを設定する部分が一番迷うと思うのですが、 Altair GraphQL Client を使えば、下の方にある Add files
というボタンをクリックするだけでファイルを追加して、勝手にクエリを修正してくれました。
Postman を使う場合は以下の Stackoverflow の真似をします。
Is there any way to upload files via postman into a GraphQL API?
クエリを投げると、NestJS のプロジェクトのルートに stores/example.jpg
が保存されました。
Altair GraphQL Client を使う
Stackoverflow の何者かのスクリーンショットを手がかりに見つけた GraphQL クライアントです。
ファイルの設定が Postman より使いやすかったので、気分で乗り換えてもいいかもしれません。
https://www.xkoji.dev/blog/working-with-file-uploads-using-altair-graphql/
基本的には singed URL を使う
Apollo recommends handling the file upload itself out-of-band of your GraphQL server for the sake of simplicity and security. This “signed URL” approach allows your client to retrieve a URL from your GraphQL server (via S3 or other storage service) and upload the file to the URL rather than to your GraphQL server.
Apolloでは、シンプルさとセキュリティの観点から、ファイルのアップロード自体をGraphQLサーバーの帯域外で処理することを推奨しています。この「署名付きURL」アプローチにより、クライアントはGraphQLサーバーからURLを取得し(S3またはその他のストレージサービスを介して)、GraphQLサーバーではなくURLにファイルをアップロードすることができます。
https://www.apollographql.com/docs/apollo-server/v3/data/file-uploads/