Skip to content

Commit

Permalink
Merge pull request #189 from joelgriffith/feature/restify-integration
Browse files Browse the repository at this point in the history
Adding in restify integration, docs, tests more...
  • Loading branch information
helfer authored Jan 24, 2017
2 parents d4fd6e2 + eeb9f1d commit a29d8b6
Show file tree
Hide file tree
Showing 11 changed files with 248 additions and 3 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Changelog

### VNEXT
* **Restify integration** ([@joelgriffith](https://github.com/joelgriffith)) on [#189](https://github.com/apollostack/graphql-server/pull/189)
* run batched requests in parallel ([@DxCx](https://github.com/DxCx)) on [#273](https://github.com/apollostack/graphql-server/pull/273)
* Fix GraphiQL options variables. Issue #193. ([@alanchristensen](https://github.com/alanchristensen)) on
[PR #255](https://github.com/apollostack/apollo-server/pull/255)
Expand Down
1 change: 1 addition & 0 deletions DESIGN.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ GraphQL Server should come with a set of integrations for different Node.js serv
- Hapi
- Connect
- Koa
- Restify
- ...

Framework integrations take care of parsing requests, submitting them to GraphQL Server’s core runQuery function, and sending the response back to the client. These integrations should accept requests over HTTP, websockets or other means, then invoke `runQuery` as appropriate, and return the result. They should be written in such a way that makes it easy to add features, such as batched queries, subscriptions etc.
Expand Down
32 changes: 29 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# GraphQL Server for Express, Connect, Hapi and Koa
# GraphQL Server for Express, Connect, Hapi, Koa, and Restify

[![npm version](https://badge.fury.io/js/graphql-server-core.svg)](https://badge.fury.io/js/graphql-server-core)
[![Build Status](https://travis-ci.org/apollostack/graphql-server.svg?branch=master)](https://travis-ci.org/apollostack/graphql-server)
[![Coverage Status](https://coveralls.io/repos/github/apollostack/graphql-server/badge.svg?branch=master)](https://coveralls.io/github/apollostack/graphql-server?branch=master)
[![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](http://www.apollostack.com/#slack)

GraphQL Server is a community-maintained open-source GraphQL server. It works with all Node.js HTTP server frameworks: Express, Connect, Hapi and Koa.
GraphQL Server is a community-maintained open-source GraphQL server. It works with all Node.js HTTP server frameworks: Express, Connect, Hapi, Koa and Restify.

## Principles

Expand Down Expand Up @@ -129,6 +129,32 @@ app.use(router.allowedMethods());
app.listen(PORT);
```

### Restify
```js
import restify from 'restify';
import { graphqlRestify, graphiqlRestify } from 'graphql-server-restify';

const PORT = 3000;

const server = restify.createServer({
title: 'GraphQL Server'
});

const graphQLOptions = { schema: myGraphQLSchema };

server.use(restify.bodyParser());
server.use(restify.queryParser());

server.post('/graphql', graphqlRestify(graphQLOptions));
server.get('/graphql', graphqlRestify(graphQLOptions));

server.get('/graphiql', graphiqlRestify({ endpointURL: '/graphql' }));

server.listen(PORT, () => console.log(`Listening on ${PORT}`));
```

## Options

GraphQL Server can be configured with an options object with the the following fields:

* **schema**: the GraphQLSchema to be used
Expand Down Expand Up @@ -161,7 +187,7 @@ graphqlOptions = {

GraphQL Server and express-graphql are more or less the same thing (GraphQL middleware for Node.js), but there are a few key differences:

* express-graphql works with Express and Connect, GraphQL Server supports Express, Connect, Hapi and Koa.
* express-graphql works with Express and Connect, GraphQL Server supports Express, Connect, Hapi, Koa and Restify.
* express-graphql's main goal is to be a minimal reference implementation, whereas GraphQL Server's goal is to be a complete production-ready GraphQL server.
* Compared to express-graphql, GraphQL Server has a simpler interface and supports exactly one way of passing queries.
* GraphQL Server separates serving GraphiQL (GraphQL UI) from responding to GraphQL requests.
Expand Down
5 changes: 5 additions & 0 deletions packages/graphql-server-restify/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
*
!dist
!dist/**/*
dist/**/*.test.*
!package.json
3 changes: 3 additions & 0 deletions packages/graphql-server-restify/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# graphql-server-restify

This is the Restify integration for the Apollo community GraphQL Server. [Read the docs.](http://dev.apollodata.com/tools/apollo-server/index.html)
47 changes: 47 additions & 0 deletions packages/graphql-server-restify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
{
"name": "graphql-server-restify",
"version": "0.5.1",
"description": "Production-ready Node.js GraphQL server for Restify",
"main": "dist/index.js",
"scripts": {
"compile": "tsc",
"prepublish": "npm run compile"
},
"repository": {
"type": "git",
"url": "https://github.com/apollostack/graphql-server/tree/master/packages/graphql-server-restify"
},
"keywords": [
"GraphQL",
"Apollo",
"Server",
"Restify",
"Javascript"
],
"author": "Jonas Helfer <[email protected]>",
"license": "MIT",
"bugs": {
"url": "https://github.com/apollostack/graphql-server/issues"
},
"homepage": "https://github.com/apollostack/graphql-server#readme",
"dependencies": {
"graphql-server-core": "^0.5.1",
"graphql-server-module-graphiql": "^0.4.4"
},
"devDependencies": {
"@types/restify": "^2.0.38",
"graphql-server-integration-testsuite": "^0.5.1",
"restify": "^4.1.1"
},
"peerDependencies": {
"graphql": "^0.6.1 || ^0.7.0 || ^0.8.0"
},
"optionalDependencies": {
"@types/restify": "^2.0.38",
"@types/graphql": "^0.8.6"
},
"typings": "dist/index.d.ts",
"typescript": {
"definition": "dist/index.d.ts"
}
}
6 changes: 6 additions & 0 deletions packages/graphql-server-restify/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
export {
RestifyGraphQLOptionsFunction,
RestifyHandler,
graphqlRestify,
graphiqlRestify,
} from './restifyApollo';
47 changes: 47 additions & 0 deletions packages/graphql-server-restify/src/restifyApollo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import 'mocha';
import * as restify from 'restify';
import { graphiqlRestify, graphqlRestify } from './restifyApollo';
import testSuite, { schema, CreateAppOptions } from 'graphql-server-integration-testsuite';
import { expect } from 'chai';
import { GraphQLOptions } from 'graphql-server-core';
import 'mocha';

function createApp(options: CreateAppOptions = {}) {
const server = restify.createServer({
name: 'Restify Test Server',
});

options.graphqlOptions = options.graphqlOptions || { schema };
if (!options.excludeParser) {
server.use(restify.bodyParser());
server.use(restify.queryParser());
}

if (options.graphiqlOptions ) {
server.get('/graphiql', graphiqlRestify( options.graphiqlOptions ));
}

server.get('/graphql', graphqlRestify(options.graphqlOptions));
server.post('/graphql', graphqlRestify(options.graphqlOptions));

return server;
}

describe('graphqlRestify', () => {
it('throws error if called without schema', () => {
expect(() => graphqlRestify(undefined as GraphQLOptions)).to.throw('Apollo Server requires options.');
});

it('throws an error if called with more than one argument', () => {
expect(() => (<any>graphqlRestify)({}, 'x')).to.throw(
'Apollo Server expects exactly one argument, got 2');
});

it('generates a function if the options are ok', () => {
expect(() => graphqlRestify({ schema })).to.be.a('function');
});
});

describe('integration:Restify', () => {
testSuite(createApp);
});
83 changes: 83 additions & 0 deletions packages/graphql-server-restify/src/restifyApollo.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import * as restify from 'restify';
import * as url from 'url';
import { GraphQLOptions, HttpQueryError, runHttpQuery } from 'graphql-server-core';
import * as GraphiQL from 'graphql-server-module-graphiql';

export interface RestifyGraphQLOptionsFunction {
(req?: restify.Request, res?: restify.Response): GraphQLOptions | Promise<GraphQLOptions>;
}

// Design principles:
// - You can issue a GET or POST with your query.
// - simple, fast and secure
//

export interface RestifyHandler {
(req: restify.Request, res: restify.Response, next): void;
}

export function graphqlRestify(options: GraphQLOptions | RestifyGraphQLOptionsFunction): RestifyHandler {
if (!options) {
throw new Error('Apollo Server requires options.');
}

if (arguments.length > 1) {
throw new Error(`Apollo Server expects exactly one argument, got ${arguments.length}`);
}

return (req: restify.Request, res: restify.Response, next): void => {
runHttpQuery([req, res], {
method: req.method,
options: options,
query: req.method === 'POST' ? req.body : req.query,
}).then((gqlResponse) => {
res.setHeader('Content-Type', 'application/json');
res.write(gqlResponse);
res.end();
}, (error: HttpQueryError) => {
if ( 'HttpQueryError' !== error.name ) {
throw error;
}

if ( error.headers ) {
Object.keys(error.headers).forEach((header) => {
res.setHeader(header, error.headers[header]);
});
}

res.statusCode = error.statusCode;
res.write(error.message);
res.end();
});
};
}

/* This middleware returns the html for the GraphiQL interactive query UI
*
* GraphiQLData arguments
*
* - endpointURL: the relative or absolute URL for the endpoint which GraphiQL will make queries to
* - (optional) query: the GraphQL query to pre-fill in the GraphiQL UI
* - (optional) variables: a JS object of variables to pre-fill in the GraphiQL UI
* - (optional) operationName: the operationName to pre-fill in the GraphiQL UI
* - (optional) result: the result of the query to pre-fill in the GraphiQL UI
*/

export function graphiqlRestify(options: GraphiQL.GraphiQLData) {
return (req: restify.Request, res: restify.Response, next) => {
const q = req.url && url.parse(req.url, true).query || {};
const query = q.query || '';
const operationName = q.operationName || '';

const graphiQLString = GraphiQL.renderGraphiQL({
endpointURL: options.endpointURL,
query: query || options.query,
variables: q.variables && JSON.parse(q.variables) || options.variables,
operationName: operationName || options.operationName,
passHeader: options.passHeader,
});
res.setHeader('Content-Type', 'text/html');
res.write(graphiQLString);
res.end();
};
}
25 changes: 25 additions & 0 deletions packages/graphql-server-restify/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
{
"compilerOptions": {
"target": "es6",
"module": "commonjs",
"moduleResolution": "node",
"sourceMap": true,
"declaration": true,
"noImplicitAny": false,
"rootDir": "./src",
"outDir": "./dist",
"allowSyntheticDefaultImports": false,
"pretty": true,
"removeComments": true,
"typeRoots": [
"node_modules/@types"
],
"types": [
"@types/node"
]
},
"exclude": [
"node_modules",
"dist"
]
}
1 change: 1 addition & 0 deletions test/tests.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,5 @@ require('../packages/graphql-server-express/dist/expressApollo.test');
require('../packages/graphql-server-express/dist/connectApollo.test');
require('../packages/graphql-server-hapi/dist/hapiApollo.test');
require('../packages/graphql-server-koa/dist/koaApollo.test');
require('../packages/graphql-server-restify/dist/restifyApollo.test');
require('../packages/graphql-server-express/dist/apolloServerHttp.test');

0 comments on commit a29d8b6

Please sign in to comment.