Implementing image uploading with Type-GraphQL, Apollo and TypeORM
Tags: graphql, typescript, apollo, javascript
Published: 2021-04-19 20:04:10

This week I had the unfortunate experience of trying to implement image uploading. I quickly realized that most tutorials are outdated, as [Apollo Client stopped supporting image uploading](https://www.apollographql.com/docs/apollo-server/data/file-uploads/#uploads-in-node-14-and-later) with the release of Apollo Client 3. Adding to that, there wasn't much documentation for methods using TypeScript. I hope to add to that😇

You should be able to either intialize the repo with Ben Awads command `npx create-graphql-api graphql-example`  or you can also just clone this [starter GitHub Repo](https://github.com/lastnameswayne/startergraphqlserver/tree/main) I made. They are nearly the same, the GitHub repo doesn't have postgres though. 

My main problem was also that I wanted to integrate the image uploading with my PostgresSQL database. This (hopefully) won't be a problem anymore.

Let's implement the backend first. 

# Backend

First, you have to [create a Bucket on Google Cloud Platform](https://console.cloud.google.com/storage/create-bucket). I just chose the default settings after giving it a name. You might have to create a project first, if you don't already have one. You can get $300 worth of credits too.

Next, create a [service account](https://console.cloud.google.com/iam-admin/serviceaccounts?project=foodfinder-308619). You need a service account to get service keys, which you in turn need to add into your app. Click on your service account, navigate to keys, press "Add key" and select JSON. You now have an API key! Insert this into your project.

## Setup

For this app I want to create a blog post with an image. So in your `post.ts` postresolver (or wherever your resolver to upload the image is), specify where the API-key is located: 

```tsx
const storage = new Storage({
  keyFilename: path.join(
    __dirname,
    "/../../images/filenamehere.json"
  ),
});
const bucketName = "bucketnamehere";
```

Also make a `const` for your bucket-name. You can see the name on Google Cloud Platform if you forgot.

To upload images with GraphQL, make sure to add `[graphql-upload](https://github.com/jaydenseric/graphql-upload)`. 

```bash
yarn add graphql-upload
```

Navigate to `index.ts`. First disable `uploads` from Apollo-client, since we are using `graphql-upload` which conflicts with Apollo's own upload-property: 

```tsx
const apolloServer = new ApolloServer({
    uploads: false, // disable apollo upload property
    schema: await createSchema(),
    context: ({ req, res }) => ({
      req,
      res,
      redis,
      userLoader: createUserLoader(),
    }),
  });
```

Next, also in `index.ts` we need to use `graphqlUploadExpress`. `graphqlUploadExpress` is a middleware which allows us to upload files. 

```tsx
const app = express();
app.use(graphqlUploadExpress({ maxFileSize: 10000000, maxFiles: 10 }));
apolloServer.applyMiddleware({
        app
    });
```

We can now write our resolver. First, let's upload a single file.

```tsx
import { FileUpload, GraphQLUpload } from "graphql-upload";

@Mutation(() => Boolean)
  async singleUpload(
		//1
    @Arg("file", () => GraphQLUpload)
    { createReadStream, filename }: FileUpload
  ) {
		//2
    await new Promise(async (resolve, reject) =>
      createReadStream()
        .pipe(
          storage.bucket(bucketName).file(filename).createWriteStream({
            resumable: false,
            gzip: true,
          })
        )
		//3
        .on("finish", () =>
          storage
            .bucket(bucketName)
            .file(filename)
            .makePublic()
            .then((e) => {
              console.log(e[0].object);
              console.log(
                `https://storage.googleapis.com/${bucketName}/${e[0].object}`
              );
            })
        )
        .on("error", () => reject(false))
    );
  }
```

1. The arguments are a little different. The Type-GraphQL type is `GraphQLUpload` which is from `graphql-upload`. The TypeScript type is declared as `{ createReadStream, filename }: FileUpload` with `FileUpload` also being a type from `graphql-upload`. 
2. We await a new promise, and using a `createReadStream()`, we `pipe()` to our bucket. Remember that we defined `storage` and `bucketName` earlier to our own bucket-values. We can then create a `writeStream` on our bucket. 
3. When we are done uploading, we make the files public on our buckets and print the file uploaded. The public link to view the image uploaded is `[https://storage.googleapis.com/${bucketName}/${e[0].object`,](https://storage.googleapis.com/${bucketName}/${e[0].object,) so you would want to display this link on the front-end if needed. You can also just view the contents of your bucket on the GCP website.

Unfortunately, we can't verify that this works with the graphQL-playground, since it doesn't support file uploads. This is a job for Postman, which you can download [here](https://www.postman.com/downloads/).

First, you need a suitable CURL-request for your resolver. Write this query into the GraphQL-playground:

```graphql
mutation UploadImage($file: Upload!) {
 singleUpload(file: $file)
}
```

In the top right corner you should press the "Copy CURL"-button. You should get something like this: 

curl '[http://localhost:4000/graphql](http://localhost:4000/graphql)' -H 'Accept-Encoding: gzip, deflate, br' -H 'Content-Type: application/json' -H 'Accept: application/json' -H 'Connection: keep-alive' -H 'DNT: 1' -H 'Origin: [http://localhost:4000](http://localhost:4000/)' --data-binary '**{"query":"mutation UploadImage($file: Upload!) {\n singleUpload(file: $file)\n}"}**' --compressed

You **only** want to keep the highlighted part. This leaves me with

```graphql
{"query":"mutation UploadImage($file: Upload!) {\n singleUpload(file: $file)\n}\n"}
```

Which is the operation I want. Now, back to Postman. Create a new POST-request and use the "Form-data" configuration under "Body":

![Postman](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/n9pycxuzwywsdlxij6u3.png)
Fill in this data:


| key        | value                                                                                |
|------------|--------------------------------------------------------------------------------------|
| operations | {"query":"mutation UploadImage($file: Upload!) {\n  singleUpload(file: $file)\n}\n"} |
| map        | {"0":["variables.file"]}                                                             |
| 0          | GraphQL_Logo.svg.png                                                                 |


press the "file"-configuration under the last row, "0". This will allow you to upload files. 

Upload your desired file and send the request. The response should return "true". You can now view the image on Google Cloud!🔥

I will now show how to create a front-end for your application. If you want to save the image to a database, there is a section at the end on this.

# Front-end

Setting up the front-end is a little more complicated. First, you have to setup your apollo-client.

```tsx
//other unrelated imports up here
import { createUploadLink } from "apollo-upload-client";

new ApolloClient({
    //@ts-ignore
    link: createUploadLink({
      uri: process.env.NEXT_PUBLIC_API_URL as string,
      headers: {
        cookie:
          (typeof window === "undefined"
            ? ctx?.req?.headers.cookie
            : undefined) || "",
      },
      fetch,
      fetchOptions: { credentials: "include" },
    }),
    credentials: "include",
    headers: {
      cookie:
        (typeof window === "undefined"
          ? ctx?.req?.headers.cookie
          : undefined) || "",
    },
		//...cache:...
)}
```

My apollo client is a little overcomplicated because I needed to make sure that cookies worked😅 But the most important part is that you create an ``upload-link`` with apollo rather than a normal ``http``-link.

Next, you have to implement the actual input-field where users can drop their file. My favorite fileinput-library is`[react-dropzone](https://github.com/react-dropzone/react-dropzone)`. All react-dropzone needs is a div and an input😄 

```tsx
<div
	{...getRootProps()}
            >
	<input accept="image/*" {...getInputProps()} />
	<InputDrop></InputDrop>
</div>
```

You can control what happens when a user drops a file/chooses one with their `useDropzone` hook:

```tsx
const onDrop = useCallback(
    ([file]) => {
      onFileChange(file);
    },
    [onFileChange]
  );
  

const { getRootProps, getInputProps, isDragActive } = useDropzone({ onDrop });
```

When the user drops a file, I call `onFileChange()` with the file that was just dropped in. Instead of `onFileChange` you could also have an updater function called `setFileToUpload()` using `useState()`. Since I have also implemented cropping of my images, I need to process the image through some other functions before it's ready to be uploaded. But before this feature, I just uploaded the file directly.

I actually used Apollos `useMutation()`-hook to implement uploading the image. First I define the mutation:

```graphql
const uploadFileMutation = gql`
  mutation UploadImage($file: Upload!) {
    singleUpload(file: $file)
  }
`;
```

We now need the before-mentioned hook from Apollo

```tsx
const [uploadFile] = useUploadImageMutation();
```

Now, to actually upload the file, you can call this function. I am using this in the context of a form with Formik, so in my case it would be when the user submits the form.

```tsx
await uploadFile(fileToUpload);
```

This should be enough to upload the image to your bucket. Let me know if you want the code to cropping, and I will write a little on that. For now, I deem it out of scope for this tutorial.

I promised to show how to store the image in a database, so here it is🤩

# Integrating with a database and TypeORM on the backend

First you need to update your (in my case) `Post.ts`-entity:

```tsx
@Field()
@Column()
img!: string
```

I added a new Field where I save the image as a string. This is possible, since we are actually just saving the link to our image stored in our Google Bucket. Remember to run any migrations you might need. I am telling you since I forgot to at first😅 

We then need to update our resolver on the backend:

```tsx
@Mutation(() => Boolean)
  @UseMiddleware(isAuth)
  async createPost(
    @Arg("file", () => GraphQLUpload)
    { createReadStream, filename }: FileUpload,
    @Arg("input") input: PostInput,
    @Ctx() { req }: MyContext
  ): Promise<Boolean> {
    console.log("starts");
    let imgURL = "";
    const post = new Promise((reject) =>
      createReadStream()
        .pipe(
          storage.bucket(bucketName).file(filename).createWriteStream({
            resumable: false,
            gzip: true,
          })
        )
        .on("error", reject)
        .on("finish", () =>
          storage
            .bucket(bucketName)
            .file(filename)
            .makePublic()
            .then((e) => {
              imgURL = `https://storage.googleapis.com/foodfinder-bucket/${e[0].object}`;
              Post.create({
                ...input,
                creatorId: req.session.userId,
                img: imgURL,
              }).save();
            })
        )
    );
    return true;
  }
```

A lot of the code is the same as uploading a single file. I call `Post.create({})` from TypeORM, which let's me save the new `imgURL` which I get after uploading the image. I also save the current user's `userId`, as well as the input from the form they just filled in. I get this from my `PostInput`-class:

```tsx
@InputType()
class PostInput {
  @Field()
  title: string;
  @Field()
  text: string;
}
```

This is just `title` and `text` strings, that is passed to our resolver.

The last step is to actually call the resolver. This time I will use graphQL code-gen, which I also [have a tutorial about](https://dev.to/lastnameswayne/fetching-and-displaying-data-with-graphql-on-a-next-js-front-end-5ceo). In short, it generates fully-typed hooks corresponding to our GraphQL-mutation. Here is the mutation to create a post:

```graphql
mutation CreatePost($input: PostInput!, $file: Upload!) {
  createPost(input: $input, file: $file)
}
```

Takes the input of the post (title and text) aswell as a file. GraphQL codegen generates this hook, for the above mutation:

```tsx
const [createPost] = useCreatePostMutation();
```

Simple as that! Remember to pass in the file and any other fields you might want to save:

```tsx
await createPost({
 variables: {
  input: {
    title: values.title,
    text: values.text,
   },
 file: fileToUpload,
},
```

Now we are using our resolver to save the file and the other data from the form-input🔥

That's all done. If you want to know how to display the image, you can check out my [other tutorial](https://dev.to/lastnameswayne/fetching-and-displaying-data-with-graphql-on-a-next-js-front-end-5ceo). 

# Conclusion

Great! Our users are now allowed to upload images to our application using Google Cloud Storage and GraphQL🎉🤩

I don't have a repo with this code isolated, but you can check it out on [my side-project, FoodFinder](https://github.com/lastnameswayne/foodfinder) in `posts.ts` in the backend and `create-post.tsx` for the frnot-end. As always, let me know if you have any questions😃