Skip to content

Commit

Permalink
feat(graphqlExpressUpload): Initial release
Browse files Browse the repository at this point in the history
  • Loading branch information
HriBB committed Nov 15, 2016
1 parent 6d99b7d commit 94798b6
Show file tree
Hide file tree
Showing 4 changed files with 224 additions and 1 deletion.
18 changes: 18 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
sudo: false
language: node_js
cache:
directories:
- node_modules
notifications:
email: false
node_js:
- '4'
before_install:
- npm i -g npm@^2.0.0
before_script:
- npm prune
after_success:
- npm run semantic-release
branches:
except:
- /^v\d+\.\d+\.\d+$/
146 changes: 145 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,146 @@
# graphql-server-express-upload
Graphql Server Express file upload middleware

Graphql Server Express file upload middleware. Used together with [UploadNetworkInterface](https://github.com/HriBB/apollo-upload-network-interface/releases).

## Usage

#### 1. Add `graphqlExpressUpload` middleware to your Express GraphQL endpoint

```
import { graphqlExpress, graphiqlExpress } from 'graphql-server-express'
import graphqlExpressUpload from 'graphql-server-express-upload'
import multer from 'multer'
import schema from './schema'
const upload = multer({
dest: config.tmp.path,
})
app.use('/graphql',
upload.array('files'),
// after multer and before graphqlExpress
graphqlExpressUpload({ endpointURL: '/graphql' }),
graphqlExpress((req) => {
return {
schema,
context: {}
}
})
)
app.use('/graphiql', graphiqlExpress({
endpointURL: '/graphql',
}))
```

#### 2. Add `UploadedFile` scalar to your schema

```
scalar UploadedFile
```

#### 3. Add `UploadedFile` resolver

For now we simply use JSON. In the future we should improve this.

```
const resolvers = {
UploadedFile: {
__parseLiteral: parseJSONLiteral,
__serialize: value => value,
__parseValue: value => value,
}
...
}
function parseJSONLiteral(ast) {
switch (ast.kind) {
case Kind.STRING:
case Kind.BOOLEAN:
return ast.value;
case Kind.INT:
case Kind.FLOAT:
return parseFloat(ast.value);
case Kind.OBJECT: {
const value = Object.create(null);
ast.fields.forEach(field => {
value[field.name.value] = parseJSONLiteral(field.value);
});
return value;
}
case Kind.LIST:
return ast.values.map(parseJSONLiteral);
default:
return null;
}
}
```

#### 4. Add mutation on the server

Schema definition

```
uploadProfilePicture(id: Int!, files: [UploadedFile!]!): ProfilePicture
```

And the mutation function

```
async uploadProfilePicture(root, { id, files }, context) {
// you can now access files parameter from variables
console.log('uploadProfilePicture', { id, files })
//...
}
```

#### 5. Add mutation on the client

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.

```
import React, { Component, PropTypes } from 'react'
import { graphql } from 'react-apollo'
import gql from 'graphql-tag'
class UploadProfilePicture extends Component {
onSubmit = (fields) => {
const { user, uploadProfilePicture } = this.props
// fields.files is an instance of FileList
uploadProfilePicture(user.id, fields.files)
.then(({ data }) => {
console.log('data', data);
})
.catch(error => {
console.log('error', error.message);
})
}
render() {
return (
//...
)
}
}
const ADD_SALON_RESOURCE_PICTURE = gql`
mutation uploadProfilePicture($id: Int!, $files: [UploadedFile!]!) {
uploadProfilePicture(id: $id, files: $files) {
id url thumb square small medium large full
}
}`
const withFileUpload = graphql(ADD_SALON_RESOURCE_PICTURE, {
props: ({ ownProps, mutate }) => ({
uploadProfilePicture: (id, files) => mutate({
variables: { id, files },
}),
}),
})
export default withFileUpload(UploadProfilePicture)
```
31 changes: 31 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "graphql-server-express-upload",
"description": "GraphQL Server Express file upload middleware",
"main": "src/index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"semantic-release": "semantic-release pre && npm publish && semantic-release post"
},
"repository": {
"type": "git",
"url": "https://github.com/HriBB/graphql-server-express-upload.git"
},
"keywords": [
"apollo",
"graphql",
"server",
"express",
"file",
"upload",
"middleware"
],
"author": "Bojan Hribernik",
"license": "MIT",
"bugs": {
"url": "https://github.com/HriBB/graphql-server-express-upload/issues"
},
"homepage": "https://github.com/HriBB/graphql-server-express-upload#readme",
"devDependencies": {
"semantic-release": "^4.3.5"
}
}
30 changes: 30 additions & 0 deletions src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
module.exports = function graphqlServerExpressUpload(options) {
function isUpload(req) {
return Boolean(
req.baseUrl === options.endpointURL &&
req.method === 'POST' &&
req.is('multipart/form-data')
);
}
return function(req, res, next) {
if (!isUpload(req)) {
return next();
}
var files = req.files;
var body = req.body;
var variables = JSON.parse(body.variables);
// append files to variables
files.forEach(file => {
if (!variables[file.fieldname]) {
variables[file.fieldname] = [];
}
variables[file.fieldname].push(file);
})
req.body = {
operationName: body.operationName,
query: body.query,
variables: variables
};
return next();
}
}

0 comments on commit 94798b6

Please sign in to comment.