Implementing image uploading with Type-GraphQL, Apollo and TypeORM

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 with the release of Apollo Client 3. Adding to that, there wasn’…


This content originally appeared on DEV Community and was authored by Swayne

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 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 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. 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. 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:

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).

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:

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.

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.

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.

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

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' -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' --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

{"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
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.

//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?

<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:

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:

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

We now need the before-mentioned hook from Apollo

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.

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:

@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:

@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:

@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. In short, it generates fully-typed hooks corresponding to our GraphQL-mutation. Here is the mutation to create a post:

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:

const [createPost] = useCreatePostMutation();

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

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.

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 in posts.ts in the backend and create-post.tsx for the frnot-end. As always, let me know if you have any questions?


This content originally appeared on DEV Community and was authored by Swayne


Print Share Comment Cite Upload Translate Updates
APA

Swayne | Sciencx (2021-04-19T20:04:10+00:00) Implementing image uploading with Type-GraphQL, Apollo and TypeORM. Retrieved from https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/

MLA
" » Implementing image uploading with Type-GraphQL, Apollo and TypeORM." Swayne | Sciencx - Monday April 19, 2021, https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/
HARVARD
Swayne | Sciencx Monday April 19, 2021 » Implementing image uploading with Type-GraphQL, Apollo and TypeORM., viewed ,<https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/>
VANCOUVER
Swayne | Sciencx - » Implementing image uploading with Type-GraphQL, Apollo and TypeORM. [Internet]. [Accessed ]. Available from: https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/
CHICAGO
" » Implementing image uploading with Type-GraphQL, Apollo and TypeORM." Swayne | Sciencx - Accessed . https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/
IEEE
" » Implementing image uploading with Type-GraphQL, Apollo and TypeORM." Swayne | Sciencx [Online]. Available: https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/. [Accessed: ]
rf:citation
» Implementing image uploading with Type-GraphQL, Apollo and TypeORM | Swayne | Sciencx | https://www.scien.cx/2021/04/19/implementing-image-uploading-with-type-graphql-apollo-and-typeorm/ |

Please log in to upload a file.




There are no updates yet.
Click the Upload button above to add an update.

You must be logged in to translate posts. Please log in or register.