Skip to content

Commit

Permalink
feat: Integrate webfunc
Browse files Browse the repository at this point in the history
  • Loading branch information
nicolasdao committed Jul 17, 2017
1 parent f159205 commit 375ac7e
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 105 deletions.
58 changes: 41 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
<a href="https://neap.co" target="_blank"><img src="https://neap.co/img/neap_black_small_logo.png" alt="Neap Pty Ltd logo" title="Neap" align="right" height="50" width="120"/></a>

# GraphiQL For Google Cloud Functions
## Table Of Content
# GraphQL For Google Cloud Functions
## Table Of Contents
* [TL;DR](#tldr)
* [Overview](#overview)
* [Step A - Configure Your Google Cloud Functions Environment](#step-a---create-a-new-google-cloud-functions-on-gcp)
Expand All @@ -15,6 +15,7 @@
- [A.4. Why You Need To Add ``` npm dedupe ``` As a Post Install Hook](#a4-why-you-need-to-add-npm-dedupe-as-a-post-install-hook)
* [License](#license)


## TL;DR
If you're already familiar with Google Cloud Functions, GraphQl, and GraphiQl, then this TL;DR might be good enough. Otherwise, jump to the next [Overview](#overview) section, and follow each steps.

Expand All @@ -34,22 +35,30 @@ exports.helloWorld = function(req, res) { ... }
```
with:
```js
const graphQl = require('google-graphql-functions');
const graphQl = require('google-graphql-functions')

const executableSchema = ... // schema you should have built using the standard graphql.js or Apollo's graphql-tools.js.
const graphql_options = {
schema: executableSchema,
graphiql: true,
endpointURL: "/graphiql"
};
}

exports.helloWorld = graphQl.serveHTTP(graphql_options, (req, res, results) => {
//Some code to inspect req, res, or results
});
exports.helloWorld = graphQl.serveHTTP(graphql_options)
```
If you need to support CORS, add a _**webconfig.json**_ file under your project's root folder and add a configuration similar to the following:
```js
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
```

#### WARNING
In the piece of code above, you won't be able to do a ``` res.status(200).send("Hello World") ``` as the http header will already be set the graphQl interpreter. This is a GraphQl server. Therefore, it only returns GraphQl responses. If you need to manipulate data, you will have to do this inside the resolver. This is beyond the scope of this document. You can find a simple example below, under the [Step C](#step-c---create-&-deploy-your-graphql-dummy-api-to-your-local-machine), and read more about it on the awesome [Apollo's website](http://dev.apollodata.com/tools/), as well as on the official Facebook [GraphQl website](http://graphql.org/learn/).
Google-graphql-functions is built on top of the [_**webfunc**_](https://github.com/nicolasdao/webfunc) package. [_**webfunc**_](https://github.com/nicolasdao/webfunc) is a lightweight HTTP handler & project setup tool for Google Cloud Functions. For more details on how to configure its _webconfig.json_ file, as well as how to use it to easily deploy your project to your Google Cloud Account, please visit its GitHub page [here](https://github.com/nicolasdao/webfunc).


## Overview
Expand Down Expand Up @@ -150,7 +159,7 @@ type Query {
schema {
query: Query
}
`;
`

const productResolver = {

Expand All @@ -170,7 +179,7 @@ const productResolver = {
}

// This `findBy` method simulates a database query, hence it returning a promise.
const findBy = (field, value) => Promise.resolve(product.filter(product => product[field] === value));
const findBy = (field, value) => Promise.resolve(product.filter(product => product[field] === value))

const product = [{
name: 'Magic Wand',
Expand All @@ -187,29 +196,27 @@ const product = [{
id: 3,
brandRefId: 'm2',
shortDescription: "Weird thing I wear when I'm drunk."
}];
}]


const executableSchema = makeExecutableSchema({
typeDefs: schema,
resolvers: _.merge(productResolver.root) // merge using lodash, for example
});
})

const graphql_options = {
schema: executableSchema,
graphiql: true,
endpointURL: "/graphiql"
};
}

/**
* Responds to any HTTP request that can provide a "message" field in the body.
*
* @param {!Object} req Cloud Function request context.
* @param {!Object} res Cloud Function response context.
*/
exports.main = graphQl.serveHTTP(graphql_options, (req, res, results) => {
//Some code to inspect req, res, or results
});
exports.main = graphQl.serveHTTP(graphql_options)

// WARNING:
// In the piece of code above, you won't be able to do a 'res.status(200).send("Hello World")'
Expand Down Expand Up @@ -246,6 +253,23 @@ The steps to deploy:
```bash
gcloud beta functions deploy [FUNCTION-NAME] --stage-bucket [BUCKET-NAME] --trigger-http --entry-point main
```

**6** - Adding CORS Support

If you need to support CORS, add a _**webconfig.json**_ file under your project's root folder and add a configuration similar to the following:
```js
{
"headers": {
"Access-Control-Allow-Methods": "GET, HEAD, OPTIONS, POST",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept",
"Access-Control-Allow-Origin": "*",
"Access-Control-Max-Age": "1296000"
}
}
```
Google-graphql-functions is built on top of the [_**webfunc**_](https://github.com/nicolasdao/webfunc) package. [_**webfunc**_](https://github.com/nicolasdao/webfunc) is a lightweight HTTP handler & project setup tool for Google Cloud Functions. For more details on how to configure its _webconfig.json_ file, as well as how to use it to easily deploy your project to your Google Cloud Account, please visit its GitHub page [here](https://github.com/nicolasdao/webfunc).
## This Is What We re Up To
We are Neap, an Australian Technology consultancy powering the startup ecosystem in Sydney. We simply love building Tech and also meeting new people, so don't hesitate to connect with us at [https://neap.co](https://neap.co).

Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"raw-body": "^2.2.0",
"standard-version": "^4.2.0",
"url": "^0.11.0",
"webfunc": "0.1.0-alpha.6"
"webfunc": "^0.1.0-alpha.6"
},
"devDependencies": {
"eslint": "^4.1.1"
Expand Down
107 changes: 20 additions & 87 deletions src/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,100 +19,33 @@

const accepts = require('accepts')
const graphql = require('graphql')
const { serveHttp } = require('webfunc')
const httpError = require('http-errors')
const url = require('url')
const path = require('path')
const fs = require('fs')

const parseBody = require('./parseBody')
const renderGraphiQL = require('./renderGraphiQL')

/*eslint-disable */
const webconfigPath = path.join(process.cwd(), 'webconfig.json')
/*eslint-enable */
const webconfig = fs.existsSync(webconfigPath) ? require(webconfigPath) : null

let headersCollection = null
const getHeadersCollection = (headers = {}) => {
if (headersCollection == null) {
headersCollection = []
for (let key in headers)
headersCollection.push({ key, value: headers[key] })
}
return headersCollection
}

let allowedOrigins = null
const getAllowedOrigins = (headers = {}) => {
if (allowedOrigins == null) {
allowedOrigins = (headers['Access-Control-Allow-Origin'] || '').split(',')
.reduce((a, s) => {
if (s)
a[s.trim().toLowerCase().replace(/\/$/,'')] = true
return a
}, {})
}
return allowedOrigins
}

let allowedMethods = null
const getAllowedMethods = (headers = {}) => {
if (allowedMethods == null) {
allowedMethods = (headers['Access-Control-Allow-Methods'] || '').split(',')
.reduce((a, s) => {
if (s)
a[s.trim().toLowerCase()] = true
return a
}, {})
}
return allowedMethods
}

const setResponse = (res, webconfig, statusCode, data = '') => Promise.resolve(webconfig && webconfig.headers ? webconfig.headers : null)
.then(headers => getHeadersCollection(headers).reduce((response, header) => res.set(header.key, header.value), res))
.then(res => statusCode ? res.status(statusCode).send(data) : res)

const serveHttp = (req, res, webconfig) => Promise.resolve(webconfig && webconfig.headers ? webconfig.headers : {})
.then(headers => {
const origins = getAllowedOrigins(headers)
const methods = getAllowedMethods(headers)
const origin = new String(req.headers.origin)
const method = new String(req.method).toLowerCase()

// Check CORS
if (!origins['*'] && Object.keys(origins).length != 0 && !(origin in origins))
return setResponse(res, webconfig, 403, `Forbidden - CORS issue. Origin '${origin} is not allowed.'`)
if (Object.keys(methods).length != 0 && method != 'get' && method != 'head' && !(method in methods))
return setResponse(res, webconfig, 403, `Forbidden - CORS issue. Method '${method.toUpperCase()}' is not allowed.`)

if (method == 'head' || method == 'options')
return setResponse(res, webconfig, 200)
})

exports.serveHTTP = (getOptions, fn) => (req, res) => serveHttp(req, res, webconfig).then(() => {
if (!res.headersSent) {
if (!getOptions)
throw new Error('GraphQL middleware requires getOptions.')
else {
const optionType = typeof(getOptions)
const getHttpHandler =
optionType == 'object' ? Promise.resolve(graphqlHTTP(getOptions)) :
optionType == 'function' ? getOptions(req, res).then(options => !res.headersSent ? graphqlHTTP(options) : null) :
null

if (!getHttpHandler)
throw new Error(`GraphQL middleware requires a valid 'getOptions' argument(allowed types: 'object', 'function'). Type '${typeof(getOptions)}' is invalid.`)

if (!res.headersSent)
return getHttpHandler.then(httpHandler => httpHandler(req, res).then(results => fn(req, res, results)))
exports.serveHTTP = getOptions => serveHttp(
(req, res) => {
if (!res.headersSent) {
if (!getOptions)
throw httpError(500, 'GraphQL middleware requires getOptions.')
else {
const optionType = typeof(getOptions)
const getHttpHandler =
optionType == 'object' ? Promise.resolve(graphqlHTTP(getOptions)) :
optionType == 'function' ? getOptions(req, res).then(options => !res.headersSent ? graphqlHTTP(options) : null) :
null

if (!getHttpHandler)
throw httpError(500, `GraphQL middleware requires a valid 'getOptions' argument(allowed types: 'object', 'function'). Type '${typeof(getOptions)}' is invalid.`)

return getHttpHandler.then(httpHandler => httpHandler(req, res))
}
}
}
})
})

/**
* Middleware for your server; takes an options object or function as input to
* configure behavior, and returns an express middleware.
*/
exports.graphqlHTTP = graphqlHTTP

function graphqlHTTP(options) {
Expand Down Expand Up @@ -385,7 +318,7 @@ function canDisplayGraphiQL(request, params) {
*/
function sendResponse(response, data) {
if (typeof response.send === 'function') {
setResponse(response, webconfig, 200, data)
response.status(200).send(data)
} else {
response.end(data)
}
Expand Down

0 comments on commit 375ac7e

Please sign in to comment.