-
Notifications
You must be signed in to change notification settings - Fork 2k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat(fastify): Integrate apollo-fastify plugin #626 #1013
Changes from 13 commits
d1f7d96
73cd6f2
c8ec07e
46b7951
dc8b4aa
6ebe74f
5169229
73eb7ac
e0ae046
63e30be
d1fdf78
cec04da
459594e
b48d08a
e91e015
57589bb
530bce1
79a27d1
032220b
b91b93b
ad29216
3a72e86
6a075dc
71b2a2f
34f210c
5d96da3
b0b5c7d
9b9caf7
38208f3
8f4920f
b3ceac8
f82fd09
67fe5e1
524e711
05ecbc5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
* | ||
!src/**/* | ||
!dist/**/* | ||
dist/**/*.test.* | ||
!package.json | ||
!README.md |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
--- | ||
title: Fastify | ||
description: Setting up Apollo Server with Fastify | ||
--- | ||
|
||
[![npm version](https://badge.fury.io/js/apollo-server-core.svg)](https://badge.fury.io/js/apollo-server-core) [![Build Status](https://circleci.com/gh/apollographql/apollo-cache-control-js.svg?style=svg)](https://circleci.com/gh/apollographql/apollo-cache-control-js) [![Coverage Status](https://coveralls.io/repos/github/apollographql/apollo-server/badge.svg?branch=master)](https://coveralls.io/github/apollographql/apollo-server?branch=master) [![Get on Slack](https://img.shields.io/badge/slack-join-orange.svg)](https://www.apollographql.com/#slack) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Aside from the fact that one of these badges is referring to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Makes sense. I removed all the badges except the |
||
|
||
This is the Fastify integration of GraphQL Server. Apollo Server is a community-maintained open-source GraphQL server that works with all Node.js HTTP server frameworks: Express, Connect, Fastify, Hapi, Koa and Restify. [Read the docs](https://www.apollographql.com/docs/apollo-server/). [Read the CHANGELOG.](https://github.com/apollographql/apollo-server/blob/master/CHANGELOG.md) | ||
|
||
```sh | ||
npm install apollo-server-fastify | ||
``` | ||
|
||
## Fastify | ||
|
||
```js | ||
import fastify from 'fastify'; | ||
import jsonParser from 'fast-json-body'; | ||
import { graphqlFastify } from 'apollo-server-fastify'; | ||
|
||
const myGraphQLSchema = // ... define or import your schema here! | ||
const PORT = 3000; | ||
|
||
const app = fastify(); | ||
|
||
// jsonParser is needed for POST. | ||
app.addContentTypeParser('application/json', function(req, done) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hi! I'm one of the Fastify maintainers :) There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @delvedor Thanks a lot for the pointer :) But I implemented this 4 months back and for some reason I had to explicitly add the content parser. But I am working on V2 of this plugin and will take your point into consideration while doing that |
||
jsonParser(req, function(err, body) { | ||
done(err, body); | ||
}); | ||
}); | ||
app.register(graphqlFastify, { schema: myGraphQLSchema }); | ||
|
||
try { | ||
await app.listen(3007); | ||
} catch (err) { | ||
app.log.error(err); | ||
process.exit(1); | ||
} | ||
``` | ||
|
||
## Principles | ||
|
||
GraphQL Server is built with the following principles in mind: | ||
|
||
* **By the community, for the community**: GraphQL Server's development is driven by the needs of developers | ||
* **Simplicity**: by keeping things simple, GraphQL Server is easier to use, easier to contribute to, and more secure | ||
* **Performance**: GraphQL Server is well-tested and production-ready - no modifications needed | ||
|
||
Anyone is welcome to contribute to GraphQL Server, just read [CONTRIBUTING.md](https://github.com/apollographql/apollo-server/blob/master/CONTRIBUTING.md), take a look at the [roadmap](https://github.com/apollographql/apollo-server/blob/master/ROADMAP.md) and make your first PR! |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
{ | ||
"name": "apollo-server-fastify", | ||
"version": "1.0.0", | ||
"description": "Production-ready Node.js GraphQL server for fastify", | ||
"main": "dist/index.js", | ||
"scripts": { | ||
"compile": "tsc", | ||
"prepublish": "npm run compile" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify" | ||
}, | ||
"keywords": [ | ||
"GraphQL", | ||
"Apollo", | ||
"Server", | ||
"Fastify", | ||
"Javascript" | ||
], | ||
"author": "Aditya pratap Singh <adisinghrajput@gmail.com>", | ||
"license": "MIT", | ||
"bugs": { | ||
"url": "https://github.com/apollographql/apollo-server/issues" | ||
}, | ||
"homepage": "https://github.com/apollographql/apollo-server#readme", | ||
"dependencies": { | ||
"apollo-server-core": "^1.3.6", | ||
"apollo-server-module-graphiql": "^1.3.4", | ||
"fastify": "1.3.1" | ||
}, | ||
"devDependencies": { | ||
"@types/graphql": "0.12.7", | ||
"apollo-server-integration-testsuite": "^1.3.6", | ||
"fast-json-body": "^1.1.0", | ||
"http2": "^3.3.7" | ||
}, | ||
"typings": "dist/index.d.ts", | ||
"typescript": { | ||
"definition": "dist/index.d.ts" | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,70 @@ | ||
import * as fastify from 'fastify'; | ||
import { FastifyInstance } from 'fastify'; | ||
const jsonParser = require('fast-json-body'); | ||
import { graphqlFastify, graphiqlFastify } from './fastifyApollo'; | ||
import testSuite, { | ||
schema, | ||
CreateAppOptions, | ||
} from 'apollo-server-integration-testsuite'; | ||
import { expect } from 'chai'; | ||
import { GraphQLOptions } from 'apollo-server-core'; | ||
import 'mocha'; | ||
|
||
async function createApp(options: CreateAppOptions = {}) { | ||
const app = fastify(); | ||
const graphqlOptions = options.graphqlOptions || { schema }; | ||
|
||
if (!options.excludeParser) { | ||
// @ts-ignore: Dynamic addContentTypeParser error | ||
app.addContentTypeParser('application/json', function(req, done) { | ||
jsonParser(req, function(err, body) { | ||
done(err, body); | ||
}); | ||
}); | ||
} | ||
|
||
if (options.graphiqlOptions) { | ||
app.register(graphiqlFastify, options.graphiqlOptions); | ||
} | ||
app.register(graphqlFastify, { graphqlOptions }); | ||
|
||
try { | ||
await app.listen(3007); | ||
} catch (err) { | ||
app.log.error(err); | ||
process.exit(1); | ||
} | ||
|
||
return app.server; | ||
} | ||
|
||
async function destroyApp(app) { | ||
if (!app || !app.close) { | ||
return; | ||
} | ||
await new Promise(cb => app.close(cb)); | ||
} | ||
|
||
describe('Fastify', () => { | ||
describe('fastifyApollo', () => { | ||
it('throws error if called without schema', function() { | ||
expect(() => | ||
graphqlFastify( | ||
{} as FastifyInstance, | ||
undefined as CreateAppOptions, | ||
undefined, | ||
), | ||
).to.throw('Apollo Server requires options.'); | ||
}); | ||
|
||
it('throws an error if called with argument not equal to 3', function() { | ||
expect(() => (<any>graphqlFastify)({}, { graphqlOptions: {} })).to.throw( | ||
'Apollo Server expects exactly 3 argument, got 2', | ||
); | ||
}); | ||
}); | ||
|
||
describe('integration:Fastify', () => { | ||
testSuite(createApp, destroyApp); | ||
}); | ||
}); |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,136 @@ | ||
import * as fastify from 'fastify'; | ||
import { | ||
runHttpQuery, | ||
HttpQueryRequest, | ||
GraphQLOptions, | ||
} from 'apollo-server-core'; | ||
import * as GraphiQL from 'apollo-server-module-graphiql'; | ||
import { | ||
FastifyInstance, | ||
FastifyRequest, | ||
FastifyReply, | ||
Middleware, | ||
} from 'fastify'; | ||
import { IncomingMessage, ServerResponse, Server } from 'http'; | ||
|
||
export function graphqlFastify( | ||
fastify: FastifyInstance, | ||
options: any, | ||
next: (err?: Error) => void, | ||
) { | ||
if (!options || !options.graphqlOptions) { | ||
throw new Error('Apollo Server requires options.'); | ||
} | ||
|
||
if (arguments.length !== 3) { | ||
throw new Error( | ||
`Apollo Server expects exactly 3 argument, got ${arguments.length}`, | ||
); | ||
} | ||
|
||
async function handler<HttpResponse extends ServerResponse>( | ||
request: any, | ||
reply: FastifyReply<HttpResponse>, | ||
) { | ||
const { method } = request.raw; | ||
try { | ||
const gqlResponse = await runHttpQuery([request], { | ||
method: method, | ||
options: options.graphqlOptions, | ||
query: method === 'POST' ? request.body : request.query, | ||
}); | ||
reply | ||
.type('application/json') | ||
.code(200) | ||
.header( | ||
'Content-Length', | ||
Buffer.byteLength(JSON.stringify(gqlResponse), 'utf8'), | ||
) | ||
.send(JSON.parse(gqlResponse)); | ||
} catch (error) { | ||
if ('HttpQueryError' !== error.name) { | ||
return next(error); | ||
} | ||
|
||
if (error.headers) { | ||
Object.keys(error.headers).forEach(header => { | ||
reply.header(header, error.headers[header]); | ||
}); | ||
} | ||
|
||
let errMessage; | ||
try { | ||
errMessage = JSON.parse(error.message); | ||
} catch { | ||
errMessage = error.message; | ||
} | ||
|
||
reply.code(error.statusCode).send(errMessage); | ||
} | ||
} | ||
|
||
fastify.route({ | ||
method: ['GET', 'POST'], | ||
url: options.url || '/graphql', | ||
handler, | ||
}); | ||
|
||
// This is a workaround because of this issue https://github.com/fastify/fastify/pull/862 | ||
fastify.route({ | ||
method: ['HEAD', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], | ||
url: options.url || '/graphql', | ||
handler: async (req, reply) => { | ||
reply | ||
.code(405) | ||
.header('allow', 'GET, POST') | ||
.send(); | ||
}, | ||
}); | ||
|
||
next(); | ||
return fastify; | ||
} | ||
|
||
export function graphiqlFastify( | ||
fastify: FastifyInstance, | ||
options: any, | ||
next: (err?: Error) => void, | ||
) { | ||
const handler = async (request, reply) => { | ||
try { | ||
const query = request.query; | ||
const giqlResponse = await GraphiQL.resolveGraphiQLString( | ||
query, | ||
options, | ||
request, | ||
); | ||
reply | ||
.header('Content-Type', 'text/html') | ||
.code(200) | ||
.send(giqlResponse); | ||
} catch (error) { | ||
reply.code(500).send(error); | ||
} | ||
}; | ||
|
||
fastify.route({ | ||
method: ['GET', 'POST'], | ||
url: options.url || '/graphiql', | ||
handler, | ||
}); | ||
|
||
// This is a workaround because of this issue https://github.com/fastify/fastify/pull/862 | ||
fastify.route({ | ||
method: ['HEAD', 'PUT', 'DELETE', 'OPTIONS', 'PATCH'], | ||
url: options.url || '/graphiql', | ||
handler: async (req, reply) => { | ||
reply | ||
.code(405) | ||
.header('allow', 'GET, POST') | ||
.send(); | ||
}, | ||
}); | ||
|
||
next(); | ||
return fastify; | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
// Expose types which can be used by both middleware flavors. | ||
export { GraphQLOptions } from 'apollo-server-core'; | ||
|
||
// Fastify Middleware | ||
export { graphqlFastify, graphiqlFastify } from './fastifyApollo'; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
{ | ||
"compilerOptions": { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is an existing problem at times with extending the |
||
"target": "es5", | ||
"module": "commonjs", | ||
"moduleResolution": "node", | ||
"sourceMap": true, | ||
"declaration": true, | ||
"noImplicitAny": false, | ||
"allowSyntheticDefaultImports": false, | ||
"pretty": true, | ||
"removeComments": true, | ||
"lib": ["es6", "esnext.asynciterable"], | ||
"rootDir": "./src", | ||
"outDir": "./dist", | ||
"typeRoots": ["node_modules/@types"] | ||
}, | ||
"exclude": ["node_modules", "dist"] | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
The `graphql-server-fastify` package is now called [`apollo-server-fastify`](https://www.npmjs.com/package/apollo-server-fastify). We are continuing to release matching versions of the package under the old name, but we recommend you switch to using the new name. The API is identical. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Let's keep this as a separate concern outside of this PR since it's not directly related.