Upload Files to S3 Object Storage (or MinIo) with Apollo Server
Tags: node, javascript, graphql, tutorial
Published: 2022-05-08 10:02:31

In the past I've done articles on how to upload images to Cloudinary and S3 (or MinIO), but they were always REST articles. So this week I decided to make a small tutorial on how to create an API in GraphQL to upload files to S3.

And so that it is accessible to more people, that is, so that more people can do this tutorial, they can use MinIO. However, what will be used is the AWS SDK and not a MinIO client.

In addition to all this, at the end of the article I will share with you a link to the github repository with the code that will be shown in this article as well as a React application so you can try uploading a file.

## Let's code 

First let's install the necessary dependencies:

```sh
npm install express apollo-server-express graphql-upload aws-sdk

npm install --dev nodemon
```

Now let's create some modules that will be used in our resolvers, starting first with the S3 bucket configuration:

```js
// @/src/modules/bucket.js
import AWS from "aws-sdk";

export const bucket = "dev-gql-s3-bucket";

export const s3 = new AWS.S3({
  endpoint: "http://localhost:9000",
  accessKeyId: "ly1y6iMtYf",
  secretAccessKey: "VNcmMuDARGGstqzkXF1Van1Mlki5HGU9",
  sslEnabled: false,
  s3ForcePathStyle: true,
});
```

As you may have noticed in our bucket configuration, our bucket name is `dev-gql-s3-bucket`.

One important thing I want to point out is that the S3's Access Key corresponds to the MinIo's root user, just as the S3's Secret Access Key corresponds to the root password.

Now we will need to create a function that will be used to upload the file:

```js
// @/src/modules/streams.js
import stream from "stream";

import { bucket, s3 } from "./bucket.js";

export const createUploadStream = (key) => {
  const pass = new stream.PassThrough();
  return {
    writeStream: pass,
    promise: s3
      .upload({
        Bucket: bucket,
        Key: key,
        Body: pass,
      })
      .promise(),
  };
};
```

With the modules created, we can start defining our GraphQL Schema:

```js
// @/src/graphql/typeDefs.js
import { gql } from "apollo-server-express";

export const typeDefs = gql`
  scalar Upload

  type FileUploadResponse {
    ETag: String!
    Location: String!
    key: String!
    Key: String!
    Bucket: String!
  }

  type Query {
    otherFields: Boolean!
  }

  type Mutation {
    fileUpload(file: Upload!): FileUploadResponse!
  }
`;
```

As you may have noticed in our schema, a scalar called Upload has been defined, which will be "mapped" to the implementation of the `graphql-upload` dependency.

With our schema defined, we can start working on our resolvers. First, let's import the necessary modules and dependencies:

```js
// @/src/graphql/resolvers.js
import { ApolloError } from "apollo-server-express";
import { GraphQLUpload } from "graphql-upload";

import { createUploadStream } from "../modules/streams.js";

export const resolvers = {
  // ...
};
```

Then we'll map our scalar Upload with the graphql-upload implementation:

```js
// @/src/graphql/resolvers.js
import { ApolloError } from "apollo-server-express";
import { GraphQLUpload } from "graphql-upload";

import { createUploadStream } from "../modules/streams.js";

export const resolvers = {
  Upload: GraphQLUpload,
  // ...
};
```

Now we can start working on our mutation, which we go to our arguments to get the file:

```js
// @/src/graphql/resolvers.js
import { ApolloError } from "apollo-server-express";
import { GraphQLUpload } from "graphql-upload";

import { createUploadStream } from "../modules/streams.js";

export const resolvers = {
  Upload: GraphQLUpload,
  Mutation: {
    fileUpload: async (parent, { file }) => {
      const { filename, createReadStream } = await file;

      // ...
    },
  },
};
```

Then we will upload the file and it should be noted that the file/image key corresponds to the file name.

```js
// @/src/graphql/resolvers.js
import { ApolloError } from "apollo-server-express";
import { GraphQLUpload } from "graphql-upload";

import { createUploadStream } from "../modules/streams.js";

export const resolvers = {
  Upload: GraphQLUpload,
  Mutation: {
    fileUpload: async (parent, { file }) => {
      const { filename, createReadStream } = await file;

      const stream = createReadStream();

      let result;

      try {
        const uploadStream = createUploadStream(filename);
        stream.pipe(uploadStream.writeStream);
        result = await uploadStream.promise;
      } catch (error) {
        console.log(
          `[Error]: Message: ${error.message}, Stack: ${error.stack}`
        );
        throw new ApolloError("Error uploading file");
      }

      return result;
    },
  },
};
```

With everything set up and defined, we can start working on our entry file. That is, we need to create our Apollo Server, start the server and implement the graphql upload middleware.

```js
// @/src/main.js
import express from "express";
import { ApolloServer } from "apollo-server-express";
import { graphqlUploadExpress } from "graphql-upload";

import { typeDefs } from './graphql/typeDefs.js'
import { resolvers } from './graphql/resolvers.js'

async function startServer() {
  const server = new ApolloServer({
    typeDefs,
    resolvers,
  });

  await server.start();

  const app = express();

  app.use(graphqlUploadExpress());

  server.applyMiddleware({ app });

  await new Promise((r) => app.listen({ port: 4000 }, r));

  console.log(`🚀 Server ready at http://localhost:4000${server.graphqlPath}`);
}

startServer();
```

While in our `package.json` just add the following properties:

```js
{
  // ...
  "main": "main.js",
  "type": "module",
  "scripts": {
    "dev": "nodemon src/main.js"
  },
  // ...
}
```

As promised at the beginning of the article, [here](https://github.com/FranciscoMendes10866/file-upload-gql) is the repository link.

## Conclusion

As always, I hope you found it interesting. If you noticed any errors in this article, please mention them in the comments. 🧑🏻‍💻

Hope you have a great day! 👌