GraphQL: File Upload & Troubleshooting
目录
GraphQL File Upload
All implementations and extensions are based on graphql-multipart-request-spec
Client
ApolloClient Setup
Client is using apollo-upload-client which implemented graphql-multipart-request-spec
Replace HttpLink with createUploadLink
Those two do the same thing, feel free to replace it!
import {
ApolloClient,
InMemoryCache
} from '@apollo/client';
import {
createUploadLink
} from 'apollo-upload-client';
const client = new ApolloClient(config);
Add Scalar
Upload scalar
Due to different dependencies, this may cause some error, see Troubleshooting below
Add Schema
type Mutation {
singleUpload(file: Upload!): File!
}
type File {
filename: String!
mimetype: String!
encoding: String!
}
Frontend
Provide an input or use other frontend components to select a file:
<input type="file" onchange={fileUpload}>
Then validate the selected file:
const uploadOnChange = async (files: File[]) => {
if (files.length === 0) return
if (files.filter((file) => file.size > 10 * 1024 * 1024).length > 0) {
/* throw error: file size exceed */
return
}
if (
files.filter(
(file) => [ `image/png` , `image/jpeg` ].findIndex(
(type) => file.type === type
) === -1
).length > 0
) {
/* throw error: unacceptable file type */
return
}
/* trigger mutation here */
}
Backend
GraphQL port it to assets server
If you are using Javascript, skip the import of
graphql-uploadIf you are using Typescript, you can use graphql-upload for type check, which implemented graphql-multipart-request-spec
import { FileUpload } from "graphql-upload";
const uploadFile = async (filePromise: {
file: FileUpload;
}): Promise<boolean> => {
try {
const { file }: { file: FileUpload } = await filePromise;
const fileReadStream = file.createReadStream(); // get the file readstream
/* ----------------------------- `*/
/* Option 1: You can save the file on current server */
/* const writeStream = fs.createWriteStream('fakepath/output.png') */
/* Convert stream to file */
/* readStream.pipe(writeStream) */
/* ----------------------------- */
/* Option 2: You can port the file to assets server if you need */
const formData = new FormData();
formData.append("attachmentData", fileReadStream, file.filename);
await http.post( `assetsServer/fileUpload` , formData, {
headers: {
...formData.getHeaders(),
},
timeout: 30000,
});
return true;
} catch (error) {
return false;
}
};
const resolvers = {
Query: {
files: () => {
// Return the record of files uploaded from your DB or API or filesystem.
}
},
Mutation: {
uploadFile
},
};
Troubleshooting
There can be only one type named “Upload”
Possibly you included one lib which ALREADY implemented Upload Type, so you just need to delete scalar Upload
Unknown type “Upload”. Did you mean “Float”?
You forget to add the scalar Upload
scalar Upload always causes error :(
- If I add it ->
Error: There can be only one type named "Upload" - If I remove it ->
Error: Unknown type "Upload". Did you mean "Float"?Oh you got some tricky dependencies.
Try use other names like:
scalar FileUpload
That may help your issue, GraphQL may regard it as custom scalar.
createReadStream() crashes-RangeError: Maximum call stack size exceeded
RangeError: Maximum call stack size exceeded
at _openReadFs (internal/fs/streams.js:1:1)
This is due to outdated dependency of fs-capacitor .
To prevent future compatibility issue, set resolutions in package.json :
"resolutions": {
"graphql-upload": "11.0.0"
},
Be aware that resolutions property is currently only handled by yarn package manager, not by npm
with npm, you have to preinstall an aditionnal module to force resolutions :
"scripts": {
"preinstall": "npx npm-force-resolutions",
}
References
- https://github.com/jaydenseric/apollo-upload-client#function-createuploadlink
- https://github.com/jaydenseric/graphql-multipart-request-spec
- https://www.apollographql.com/docs/apollo-server/data/file-uploads/
- https://medium.com/@enespalaz/file-upload-with-graphql-9a4927775ef7
- https://github.com/apollographql/apollo-server/issues/3508