File upload with urql and apollo-server

Yuchen Zhang
3 min readDec 10, 2020

Graphql is getting really popular these days, which enables developers to build flexible data querying endpoints. I have followed a great tutorial from Ben Awad (https://www.youtube.com/watch?v=I6ypD7qv3Z8&t=47035s) and build the forum with the basic post and vote functions.

Some context: I am using Next.js and urql for the frontend. Urql is a light weighted framework GraphQL client. Express and apollo-server-express for the backend. AWS S3 for holding static files.

As the eager to pick up new technology, I decided to keep adding some common features for the forum to help me learn more about GraphQL. So the first feature coming into my mind is uploading images.

How to handle file uploading with GraphQL?

It is not well supported. Let’s see what we need to do with urql and our backend.

The upload flow for my case is sending the file from the React app to our GraphQL server and then upload to the S3 bucket, finally recording the location of the file to our database.

Frontend

I am using graphql-codegen , so my mutation looks like this. If you are only doing file uploading, we can remove text and title.

mutation createPost($text: String!, $title:String!, $file: Upload!) {  createPost(text: $text, title: $title, file: $file) {    id  }}

Upload is a scalar provided by graphql-upload which is needed on the backend, you don’t need to worry about it here. And we need to make an HTTP post request with multipart/form-data containing our meta-data and our source file. For urql case, we need to add @urql/exchange-multipart-fetch to enable file uploading. Then you should be able to

import { multipartFetchExchange } from '@urql/exchange-multipart-fetch';

and replace the original fetchExchange to the multipartFetchExchange . (reference:

The final step on the frontend is adding file upload input into our form component.

<Formik ...some formik stuff>{({ values, setFieldValue, isSubmitting }) => (<Form>
...some other input fields
<Inputtype='file'accept='image/*'onChange={({ target: { validity, files } }) => {if (validity.valid && files) {setFieldValue('file', files[0]);
// set 'file' of the form data as files[0]
}}}/>...here should have a button
</Form>
)}
</Formik>

Backend

Apollo-server(-express) has built-in graphql-upload, but it is version 8 which is really outdated. Now it is version 11, so we need to disable the old built-in graphql-upload , and use the nowadays implementation of the library

const apolloServer = new ApolloServer({schema: await buildSchema({resolvers: [],validate: false}),context: ({ req, res }) => ({req,res}),uploads: false // here});

After disabled the uploads from apollo-server, we can add the middleware

app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));

Then, we should be able to get proper file input from our client-side. Next step, we are gonna accept the file and upload it to AWS S3.

@Mutation(() => Post)@UseMiddleware(isAuth)async createPost(@Arg('text', () => String) text: string,@Arg('title', () => String) title: string,@Arg('file', () => GraphQLUpload, { nullable: true }) file: FileUpload, // take care of here@Ctx() { req }: MyContext // a self defined context type): Promise<(Post & UploadedFileResponse) | Post> {let fileRet;const post = await Post.create({...}).save();// save post to database via typeORMif (file) {const s3Uploader = new AWSS3Uploader({accessKeyId: process.env.AWS_ACCESS_KEY_ID as string,secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY as string,destinationBucketName: process.env.AWS_BUCKET_NAME as string,region: 'us-east-2'});fileRet = await s3Uploader.singleFileUpload.bind(s3Uploader)(file);const fileObj = await File.create({postId: post.id,...fileRet} as Object).save() // save file url to database via typeORM}const retObject = { ...post, ...fileRet };return retObject;}

Two parts needed to take care of are GraphQLUpload and FileUpload , we should import them from graphql-upload not apollo-server or apollo-server-express . AWSS3Uploader is a class taking AWS config as input and create an S3 instance and having a file upload function.

Finally, we should be able to see our file on the S3 console and also some information in our database.

--

--