Skip to content

Commit 94798b6

Browse files
committed
feat(graphqlExpressUpload): Initial release
1 parent 6d99b7d commit 94798b6

File tree

4 files changed

+224
-1
lines changed

4 files changed

+224
-1
lines changed

.travis.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
sudo: false
2+
language: node_js
3+
cache:
4+
directories:
5+
- node_modules
6+
notifications:
7+
email: false
8+
node_js:
9+
- '4'
10+
before_install:
11+
- npm i -g npm@^2.0.0
12+
before_script:
13+
- npm prune
14+
after_success:
15+
- npm run semantic-release
16+
branches:
17+
except:
18+
- /^v\d+\.\d+\.\d+$/

README.md

Lines changed: 145 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,146 @@
11
# graphql-server-express-upload
2-
Graphql Server Express file upload middleware
2+
3+
Graphql Server Express file upload middleware. Used together with [UploadNetworkInterface](https://github.com/HriBB/apollo-upload-network-interface/releases).
4+
5+
## Usage
6+
7+
#### 1. Add `graphqlExpressUpload` middleware to your Express GraphQL endpoint
8+
9+
```
10+
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express'
11+
import graphqlExpressUpload from 'graphql-server-express-upload'
12+
import multer from 'multer'
13+
14+
import schema from './schema'
15+
16+
const upload = multer({
17+
dest: config.tmp.path,
18+
})
19+
20+
app.use('/graphql',
21+
upload.array('files'),
22+
// after multer and before graphqlExpress
23+
graphqlExpressUpload({ endpointURL: '/graphql' }),
24+
graphqlExpress((req) => {
25+
return {
26+
schema,
27+
context: {}
28+
}
29+
})
30+
)
31+
32+
app.use('/graphiql', graphiqlExpress({
33+
endpointURL: '/graphql',
34+
}))
35+
```
36+
37+
#### 2. Add `UploadedFile` scalar to your schema
38+
39+
```
40+
scalar UploadedFile
41+
```
42+
43+
#### 3. Add `UploadedFile` resolver
44+
45+
For now we simply use JSON. In the future we should improve this.
46+
47+
```
48+
const resolvers = {
49+
UploadedFile: {
50+
__parseLiteral: parseJSONLiteral,
51+
__serialize: value => value,
52+
__parseValue: value => value,
53+
}
54+
...
55+
}
56+
57+
function parseJSONLiteral(ast) {
58+
switch (ast.kind) {
59+
case Kind.STRING:
60+
case Kind.BOOLEAN:
61+
return ast.value;
62+
case Kind.INT:
63+
case Kind.FLOAT:
64+
return parseFloat(ast.value);
65+
case Kind.OBJECT: {
66+
const value = Object.create(null);
67+
ast.fields.forEach(field => {
68+
value[field.name.value] = parseJSONLiteral(field.value);
69+
});
70+
71+
return value;
72+
}
73+
case Kind.LIST:
74+
return ast.values.map(parseJSONLiteral);
75+
default:
76+
return null;
77+
}
78+
}
79+
```
80+
81+
#### 4. Add mutation on the server
82+
83+
Schema definition
84+
85+
```
86+
uploadProfilePicture(id: Int!, files: [UploadedFile!]!): ProfilePicture
87+
```
88+
89+
And the mutation function
90+
91+
```
92+
async uploadProfilePicture(root, { id, files }, context) {
93+
// you can now access files parameter from variables
94+
console.log('uploadProfilePicture', { id, files })
95+
//...
96+
}
97+
```
98+
99+
#### 5. Add mutation on the client
100+
101+
Example using `react-apollo`. Don't forget that you need to be using [UploadNetworkInterface](https://github.com/HriBB/apollo-upload-network-interface/releases), because `apollo-client` does not support `multipart/form-data` out of the box.
102+
103+
```
104+
import React, { Component, PropTypes } from 'react'
105+
import { graphql } from 'react-apollo'
106+
import gql from 'graphql-tag'
107+
108+
class UploadProfilePicture extends Component {
109+
110+
onSubmit = (fields) => {
111+
const { user, uploadProfilePicture } = this.props
112+
// fields.files is an instance of FileList
113+
uploadProfilePicture(user.id, fields.files)
114+
.then(({ data }) => {
115+
console.log('data', data);
116+
})
117+
.catch(error => {
118+
console.log('error', error.message);
119+
})
120+
}
121+
122+
render() {
123+
return (
124+
//...
125+
)
126+
}
127+
128+
}
129+
130+
const ADD_SALON_RESOURCE_PICTURE = gql`
131+
mutation uploadProfilePicture($id: Int!, $files: [UploadedFile!]!) {
132+
uploadProfilePicture(id: $id, files: $files) {
133+
id url thumb square small medium large full
134+
}
135+
}`
136+
137+
const withFileUpload = graphql(ADD_SALON_RESOURCE_PICTURE, {
138+
props: ({ ownProps, mutate }) => ({
139+
uploadProfilePicture: (id, files) => mutate({
140+
variables: { id, files },
141+
}),
142+
}),
143+
})
144+
145+
export default withFileUpload(UploadProfilePicture)
146+
```

package.json

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
{
2+
"name": "graphql-server-express-upload",
3+
"description": "GraphQL Server Express file upload middleware",
4+
"main": "src/index.js",
5+
"scripts": {
6+
"test": "echo \"Error: no test specified\" && exit 1",
7+
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
8+
},
9+
"repository": {
10+
"type": "git",
11+
"url": "https://github.com/HriBB/graphql-server-express-upload.git"
12+
},
13+
"keywords": [
14+
"apollo",
15+
"graphql",
16+
"server",
17+
"express",
18+
"file",
19+
"upload",
20+
"middleware"
21+
],
22+
"author": "Bojan Hribernik",
23+
"license": "MIT",
24+
"bugs": {
25+
"url": "https://github.com/HriBB/graphql-server-express-upload/issues"
26+
},
27+
"homepage": "https://github.com/HriBB/graphql-server-express-upload#readme",
28+
"devDependencies": {
29+
"semantic-release": "^4.3.5"
30+
}
31+
}

src/index.js

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
module.exports = function graphqlServerExpressUpload(options) {
2+
function isUpload(req) {
3+
return Boolean(
4+
req.baseUrl === options.endpointURL &&
5+
req.method === 'POST' &&
6+
req.is('multipart/form-data')
7+
);
8+
}
9+
return function(req, res, next) {
10+
if (!isUpload(req)) {
11+
return next();
12+
}
13+
var files = req.files;
14+
var body = req.body;
15+
var variables = JSON.parse(body.variables);
16+
// append files to variables
17+
files.forEach(file => {
18+
if (!variables[file.fieldname]) {
19+
variables[file.fieldname] = [];
20+
}
21+
variables[file.fieldname].push(file);
22+
})
23+
req.body = {
24+
operationName: body.operationName,
25+
query: body.query,
26+
variables: variables
27+
};
28+
return next();
29+
}
30+
}

0 commit comments

Comments
 (0)