File Upload
GQLoom supports file uploads through GraphQL's Upload scalar.
Below are two common integration approaches:
- Using the
GraphQLUploadscalar fromgraphql-uploadorgraphql-upload-minimal - Using the
Filetype provided bygraphql-yoga
Core Steps
- Use the
silkfunction to declare theUploadorFilescalar. - Use the
UploadorFilescalar in theinputof amutation.
Using GraphQLUpload
The following code example applies to graphql-upload or graphql-upload-minimal.
ts
import { mutation, resolver, silk, weave } from "@gqloom/core"
import { ValibotWeaver } from "@gqloom/valibot"
import { GraphQLNonNull } from "graphql"
import { type FileUpload, GraphQLUpload } from "graphql-upload-minimal"
import { createServer } from "node:http"
import { createWriteStream } from "node:fs"
import { pipeline } from "node:stream/promises"
import * as path from "node:path"
import * as fsPromises from "node:fs/promises"
import { createYoga } from "graphql-yoga"
import * as v from "valibot"
const Upload = silk<Promise<FileUpload>>(new GraphQLNonNull(GraphQLUpload))
const uploadResolver = resolver({
upload: mutation(v.string())
.input({
fileName: v.nullish(v.string()),
file: Upload,
})
.resolve(async ({ fileName, file }) => {
const { filename, createReadStream } = await file
const name = fileName ?? filename
const uploadsDir = path.join(import.meta.dirname, "uploads")
await fsPromises.mkdir(uploadsDir, { recursive: true })
const rs = createReadStream()
const ws = createWriteStream(path.join(uploadsDir, name))
await pipeline(rs, ws)
return `file uploaded: ${name}`
}),
})Key points:
UploadusesPromise<FileUpload>, so you need to await it before readingcreateReadStream.- You also need to add the parsing of
Uploadin the adapter, see:
Using File Type
The following code example applies to the File type from graphql-yoga.
ts
import { mutation, resolver, silk, weave } from "@gqloom/core"
import { ValibotWeaver } from "@gqloom/valibot"
import { GraphQLNonNull, GraphQLScalarType } from "graphql"
import { createServer } from "node:http"
import * as path from "node:path"
import * as fs from "node:fs/promises"
import { createYoga } from "graphql-yoga"
import * as v from "valibot"
const FileScalar = silk(
new GraphQLNonNull(
new GraphQLScalarType<File, File>({
name: "File",
description: "The `File` scalar type represents a file upload.",
})
)
)
const uploadResolver = resolver({
upload: mutation(v.string())
.input({
fileName: v.nullish(v.string()),
file: FileScalar,
})
.resolve(async ({ fileName, file }) => {
const name = fileName ?? file.name
const uploadsDir = path.join(import.meta.dirname, "uploads")
await fs.mkdir(uploadsDir, { recursive: true })
await fs.writeFile(
path.join(uploadsDir, name),
Buffer.from(await file.arrayBuffer())
)
return `file uploaded: ${name}`
}),
})
const schema = weave(ValibotWeaver, uploadResolver)
const yoga = createYoga({ schema })
createServer(yoga).listen(4000)Key points:
- The
Fileprovided by Yoga directly supportsarrayBuffer(), suitable for small to medium files or writing to disk before processing. - Similarly, wrap it as a non-null scalar using
silk, and add validation and permission control as needed.