1 | # graphql upload typescript (graphql-upload-ts)
|
2 |
|
3 | [![NPM version](https://badgen.net/npm/v/graphql-upload-ts)](https://npm.im/graphql-upload-ts)
|
4 | [![Build Status](https://github.com/meabed/graphql-upload-ts/workflows/CI/badge.svg)](https://github.com/meabed/graphql-upload-ts/actions)
|
5 | [![Downloads](https://img.shields.io/npm/dm/graphql-upload-ts.svg)](https://www.npmjs.com/package/graphql-upload-ts)
|
6 | [![UNPKG](https://img.shields.io/badge/UNPKG-OK-179BD7.svg)](https://unpkg.com/browse/graphql-upload-ts@latest/)
|
7 |
|
8 | Minimalistic and developer friendly middleware and an [`Upload` scalar](#class-graphqlupload) to add support for [GraphQL multipart requests](https://github.com/jaydenseric/graphql-multipart-request-spec) (file uploads via queries and
|
9 | mutations) to various Node.js GraphQL servers.
|
10 |
|
11 | #### Acknowledgements
|
12 |
|
13 | This module was forked from the amazing [`graphql-upload-minimal`](https://npm.im/graphql-upload-minimal). The original module is exceptionally well documented and well written. It was very easy to fork and amend.
|
14 |
|
15 | I needed to support typescript to use it properly in typescript projects.
|
16 |
|
17 | #### This project is written in typescript
|
18 |
|
19 | - TypeScript support.
|
20 | - And using a bit less memory.
|
21 | - And a bit faster.
|
22 | - More Examples and documentation
|
23 |
|
24 |
|
25 | #### Does not follow strict [specification](https://github.com/jaydenseric/graphql-multipart-request-spec)
|
26 |
|
27 | You can't have same file referenced twice in a GraphQL query/mutation.
|
28 |
|
29 | ## Support
|
30 |
|
31 | The following environments are known to be compatible:
|
32 |
|
33 | - [Node.js](https://nodejs.org) versions 12, 14, 16, and 18. It works in Node 10 even though the unit tests fail.
|
34 | - [Koa](https://koajs.com)
|
35 | - [Express.js](https://expressjs.com)
|
36 |
|
37 | See also [GraphQL multipart request spec server implementations](https://github.com/jaydenseric/graphql-multipart-request-spec#server).
|
38 |
|
39 | ## Setup
|
40 |
|
41 | To install [`graphql-upload-ts`](https://npm.im/graphql-upload-ts) and the [`graphql`](https://npm.im/graphql) peer dependency from [npm](https://npmjs.com) run:
|
42 |
|
43 | ```shell
|
44 | npm install graphql-upload-ts graphql
|
45 | # or
|
46 | yarn add graphql-upload-ts graphql
|
47 | ```
|
48 |
|
49 | Use the [`graphqlUploadKoa`](#function-graphqluploadkoa) or [`graphqlUploadExpress`](#function-graphqluploadexpress) middleware just before GraphQL middleware. Alternatively, use [`processRequest`](#function-processrequest) to create a
|
50 | custom middleware.
|
51 |
|
52 | A schema built with separate SDL and resolvers (e.g. using [`makeExecutableSchema`](https://apollographql.com/docs/graphql-tools/generate-schema#makeExecutableSchema)) requires the [`Upload` scalar](#class-graphqlupload) to be setup.
|
53 |
|
54 | ## Usage
|
55 |
|
56 | [Clients implementing the GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec#client) upload files as [`Upload` scalar](#class-graphqlupload) query or mutation variables. Their resolver values are
|
57 | promises that resolve [file upload details](#type-fileupload) for processing and storage. Files are typically streamed into cloud storage but may also be stored in the filesystem.
|
58 |
|
59 | ### Express.js
|
60 |
|
61 | Minimalistic code example showing how to upload a file along with arbitrary GraphQL data and save it to an S3 bucket.
|
62 |
|
63 | Express.js middleware. You must put it before the main GraphQL sever middleware. Also, **make sure there is no other Express.js middleware which parses `multipart/form-data`** HTTP requests before the `graphqlUploadExpress` middleware!
|
64 |
|
65 | ```js
|
66 | const express = require('express');
|
67 | const expressGraphql = require('express-graphql');
|
68 | const { graphqlUploadExpress } = require('graphql-upload-ts');
|
69 |
|
70 | express()
|
71 | .use(
|
72 | '/graphql',
|
73 | graphqlUploadExpress({
|
74 | maxFileSize: 10000000,
|
75 | maxFiles: 10,
|
76 | // If you are using framework around express like [ NestJS or Apollo Serve ]
|
77 | // use this options overrideSendResponse to allow nestjs to handle response errors like throwing exceptions
|
78 | overrideSendResponse: false
|
79 | }),
|
80 | expressGraphql({ schema: require('./my-schema') })
|
81 | )
|
82 | .listen(3000);
|
83 | ```
|
84 |
|
85 | GraphQL schema:
|
86 |
|
87 | ```graphql
|
88 | scalar Upload
|
89 | input DocumentUploadInput {
|
90 | docType: String!
|
91 | file: Upload!
|
92 | }
|
93 |
|
94 | type SuccessResult {
|
95 | success: Boolean!
|
96 | message: String
|
97 | }
|
98 | type Mutations {
|
99 | uploadDocuments(docs: [DocumentUploadInput!]!): SuccessResult
|
100 | }
|
101 | ```
|
102 |
|
103 | GraphQL resolvers:
|
104 |
|
105 | ```js
|
106 | const { S3 } = require('aws-sdk');
|
107 |
|
108 | const resolvers = {
|
109 | Upload: require('graphql-upload-ts').GraphQLUpload,
|
110 |
|
111 | Mutations: {
|
112 | async uploadDocuments(root, { docs }, ctx) {
|
113 | try {
|
114 | const s3 = new S3({ apiVersion: '2006-03-01', params: { Bucket: 'my-bucket' } });
|
115 |
|
116 | for (const doc of docs) {
|
117 | const { createReadStream, filename /*, fieldName, mimetype, encoding */ } = await doc.file;
|
118 | const Key = `${ctx.user.id}/${doc.docType}-${filename}`;
|
119 | await s3.upload({ Key, Body: createReadStream() }).promise();
|
120 | }
|
121 |
|
122 | return { success: true };
|
123 | } catch (error) {
|
124 | console.log('File upload failed', error);
|
125 | return { success: false, message: error.message };
|
126 | }
|
127 | },
|
128 | },
|
129 | };
|
130 | ```
|
131 |
|
132 | ### Koa
|
133 |
|
134 | See the [example Koa server and client](https://github.com/jaydenseric/apollo-upload-examples).
|
135 |
|
136 |
|
137 | ### Uploading multiple files
|
138 |
|
139 | When uploading multiple files you can make use of the `fieldName` property to keep track of an identifier of the uploaded files. The fieldName is equal to the passed `name` property of the file in the `multipart/form-data` request. This can
|
140 | be modified to contain an identifier (like a UUID), for example using the `formDataAppendFile` in the commonly used [`apollo-upload-link`](https://github.com/jaydenseric/apollo-upload-client#function-formdataappendfile) library.
|
141 |
|
142 | GraphQL schema:
|
143 |
|
144 | ```graphql
|
145 | scalar Upload
|
146 | input DocumentUploadInput {
|
147 | docType: String!
|
148 | files: [Upload!]
|
149 | }
|
150 |
|
151 | type SuccessResult {
|
152 | success: Boolean!
|
153 | message: String
|
154 | }
|
155 | type Mutations {
|
156 | uploadDocuments(docs: [DocumentUploadInput!]!): SuccessResult
|
157 | }
|
158 | ```
|
159 |
|
160 | GraphQL resolvers:
|
161 |
|
162 | ```js
|
163 | const { S3 } = require('aws-sdk');
|
164 |
|
165 | const resolvers = {
|
166 | Upload: require('graphql-upload-ts').GraphQLUpload,
|
167 |
|
168 | Mutations: {
|
169 | async uploadDocuments(root, { docs }, ctx) {
|
170 | try {
|
171 | const s3 = new S3({ apiVersion: '2006-03-01', params: { Bucket: 'my-bucket' } });
|
172 |
|
173 | for (const doc of docs) {
|
174 | // fieldName contains the "name" property from the multipart/form-data request.
|
175 | // Use it to pass an identifier in order to store the file in a consistent manner.
|
176 | const { createReadStream, filename, fieldName /*, mimetype, encoding */ } = await doc.file;
|
177 | const Key = `${ctx.user.id}/${doc.docType}-${fieldName}`;
|
178 | await s3.upload({ Key, Body: createReadStream() }).promise();
|
179 | }
|
180 |
|
181 | return { success: true };
|
182 | } catch (error) {
|
183 | console.log('File upload failed', error);
|
184 | return { success: false, message: error.message };
|
185 | }
|
186 | },
|
187 | },
|
188 | };
|
189 | ```
|
190 |
|
191 | ## Tips
|
192 | - If you are using framework around express like [ NestJS or Apollo Serve ] use the option `overrideSendResponse` eg: `graphqlUploadExpress({ overrideSendResponse: false })` to allow nestjs to handle response errors like throwing exceptions.
|
193 |
|
194 | ## Architecture
|
195 |
|
196 | The [GraphQL multipart request spec](https://github.com/jaydenseric/graphql-multipart-request-spec) allows a file to be used for multiple query or mutation variables (file deduplication), and for variables to be used in multiple places.
|
197 | GraphQL's resolvers need to be able to manage independent file streams.
|
198 |
|
199 | [`busboy`](https://npm.im/busboy) parses multipart request streams. Once the `operations` and `map` fields have been parsed, [`Upload` scalar](#class-graphqlupload) values in the GraphQL operations are populated with promises, and the
|
200 | operations are passed down the middleware chain to GraphQL resolvers.
|