Skip to content
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 [WIP] #1760

Closed
Show file tree
Hide file tree
Changes from 2 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
23b5b32
feat(fastify): Integrate apollo-fastify plugin #626
addityasingh Oct 2, 2018
c9bf990
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Oct 3, 2018
f004237
feat(fastify): Fix review comments and broken tests, build #626
addityasingh Oct 3, 2018
69b619b
feat(fastify): Fix test #626
addityasingh Oct 3, 2018
aebbcf2
feat(fastify): Remove local package #626
addityasingh Oct 3, 2018
dd432e7
feat(fastify): Add fastify package to workspace #626
addityasingh Oct 3, 2018
be5907a
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Oct 7, 2018
4f5509d
feat(fastify): Update package lock #626
addityasingh Oct 7, 2018
55f2975
feat(fastify): Update package lock with latest install #626
addityasingh Oct 7, 2018
d78d51a
feat: Update package lock #636
addityasingh Oct 11, 2018
a7b5dc7
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Oct 11, 2018
82afdf2
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Oct 15, 2018
3d99e3b
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Nov 13, 2018
c4a8b58
feat: Update tsconfig #636
addityasingh Nov 13, 2018
2a74353
feat: Remove overriding tsc compile command #636
addityasingh Nov 14, 2018
efb835b
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Nov 14, 2018
9d22b2f
feat: Add jest config #636
addityasingh Nov 14, 2018
8901556
feat: Fix linting issue#636
addityasingh Nov 14, 2018
2977562
feat: Add tsconfig for fastify tests #636
addityasingh Nov 14, 2018
615a994
Merge branch 'master' into as-integrate-fastify-plugin
addityasingh Nov 14, 2018
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
6 changes: 6 additions & 0 deletions packages/apollo-server-fastify/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
*
!src/**/*
!dist/**/*
dist/**/*.test.*
!package.json
!README.md
42 changes: 42 additions & 0 deletions packages/apollo-server-fastify/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
---
title: Fastify
description: Setting up Apollo Server with Fastify
---

[![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)

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 { graphqlFastify } from 'apollo-server-fastify';

const myGraphQLSchema = // ... define or import your schema here!

const app = fastify();

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!
48 changes: 48 additions & 0 deletions packages/apollo-server-fastify/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
{
"name": "apollo-server-fastify",
"version": "2.0.0",
"description": "Production-ready Node.js GraphQL server for fastify",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"clean": "rm -rf dist",
"compile": "tsc",
"prepare": "npm run clean && npm run compile"
},
"repository": {
"type": "git",
"url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify"
},
"engines": {
"node": ">=6"
},
"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": {
"@apollographql/apollo-upload-server": "^5.0.3",
"@apollographql/graphql-playground-html": "^1.6.0",
"accept": "^3.0.2",
"apollo-server-core": "file:../apollo-server-core",
"fastify": "^1.11.2",
"graphql-subscriptions": "^0.5.8",
"graphql-tools": "^4.0.0"
},
"devDependencies": {
"@types/graphql": "0.12.7",
"apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite"
},
"peerDependencies": {
"graphql": "^0.12.0 || ^0.13.0 || ^14.0.0"
}
}
137 changes: 137 additions & 0 deletions packages/apollo-server-fastify/src/ApolloServer.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,137 @@
import * as fastify from 'fastify';
import { FastifyInstance } from 'fastify';
const { parseAll } = require('accept');
import {
renderPlaygroundPage,
RenderPageOptions as PlaygroundRenderPageOptions,
} from '@apollographql/graphql-playground-html';

import { graphqlFastify } from './fastifyApollo';

export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
import { ApolloServerBase, GraphQLOptions } from 'apollo-server-core';
import { IncomingMessage, OutgoingMessage, ServerResponse } from 'http';

export class ApolloServer extends ApolloServerBase {
async createGraphQLServerOptions(
request: fastify.FastifyRequest<IncomingMessage>,
reply: fastify.FastifyReply<OutgoingMessage>,
): Promise<GraphQLOptions> {
const options = await super.graphQLServerOptions({ request, reply });
return options;
}

protected supportsSubscriptions(): boolean {
return true;
}

protected supportsUploads(): boolean {
return true;
}

public async applyMiddleware({
app,
cors,
path,
route,
disableHealthCheck,
onHealthCheck,
}: ServerRegistration) {
if (!path) path = '/graphql';

await app.addHook(
'onRequest',
async function(
this: any,
request: any,
response: ServerResponse,
next: any,
) {
if (request.path !== path) {
return next();
}

if (this.playgroundOptions && request.method === 'get') {
// perform more expensive content-type check only if necessary
const accept = parseAll(request.headers);
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think this is working right now, the usage is different.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@mcollina I need a next() method along with async/await but based on the docs

Notice: the next callback is not available when using async/await or returning a Promise. If you do invoke a next callback in this situation unexpected behavior may occur, e.g. duplicate invocation of handlers.

we can't use next() along with async/await. What would be an alternative in this case?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do you need it?

const types = accept.mediaTypes as string[];
const prefersHTML =
types.find(
(x: string) => x === 'text/html' || x === 'application/json',
) === 'text/html';

if (prefersHTML) {
const playgroundRenderPageOptions: PlaygroundRenderPageOptions = {
endpoint: path,
subscriptionEndpoint: this.subscriptionsPath,
version: this.playgroundVersion,
...this.playgroundOptions,
};

response.setHeader('Content-Type', 'text/html');
return response.end(
renderPlaygroundPage(playgroundRenderPageOptions),
);
}
}
return next();
}.bind(this),
addityasingh marked this conversation as resolved.
Show resolved Hide resolved
);

if (!disableHealthCheck) {
await app.route({
method: [
'HEAD',
'PUT',
'DELETE',
'OPTIONS',
'PATCH',
] as fastify.HTTPMethod[],
url: '/.well-known/apollo/server-health',
handler: async function(request, reply) {
if (onHealthCheck) {
try {
await onHealthCheck(request);
} catch {
const response = reply
.send({ status: 'fail' })
.code(503)
.type('application/health+json');
return response;
}
}
const response = reply
.send({ status: 'pass' })
.type('application/health+json');
return response;
},
});
}

await app.register(graphqlFastify as any, {
url: path,
graphqlOptions: this.createGraphQLServerOptions.bind(this),
route: route || { cors },
});

this.graphqlPath = path;
}
}

export interface ServerRegistration {
app?: FastifyInstance;
path?: string;
cors?: boolean;
route?: string;
onHealthCheck?: (
request: fastify.FastifyRequest<IncomingMessage>,
) => Promise<any>;
disableHealthCheck?: boolean;
uploads?: boolean | Record<string, any>;
}

export const registerServer = () => {
throw new Error(
'Please use server.applyMiddleware instead of registerServer. This warning will be removed in the next release',
);
};
67 changes: 67 additions & 0 deletions packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
const NODE_VERSION = process.version.split('.');
const NODE_MAJOR_VERSION = parseInt(NODE_VERSION[0].replace(/^v/, ''));

// Skip fastify tests for unsupported versions of node
if (NODE_MAJOR_VERSION < 8) {
it('does not run for node versions < 8', () => {});
return;
}

import fastify from 'fastify';
import http from 'http';

import { gql, Config } from 'apollo-server-core';
import { ApolloServer, ServerRegistration } from '../ApolloServer';

import { createServerInfo } from 'apollo-server-integration-testsuite';

const typeDefs = gql`
type Query {
hello: String
}
`;

const resolvers = {
Query: {
hello: () => 'hi',
},
};

const port = 6666;

describe('apollo-server-fastify', () => {
let server: ApolloServer;

let app: fastify.FastifyInstance;
let httpServer: http.Server;

async function createServer(
serverOptions: Config,
options: Partial<ServerRegistration> = {},
) {
server = new ApolloServer(serverOptions);
app = fastify();

server.applyMiddleware({ ...options, app });
addityasingh marked this conversation as resolved.
Show resolved Hide resolved

try {
await app.listen(port);
} catch (err) {
app.log.error('error in starting server', err);
process.exit(1);
}
httpServer = await app.server;
return createServerInfo(server, httpServer);
}

afterEach(async () => {
if (server) await server.stop();
if (httpServer) await httpServer.close();
});

describe('constructor', () => {
it('accepts typeDefs and resolvers', () => {
return createServer({ typeDefs, resolvers });
});
});
});
57 changes: 57 additions & 0 deletions packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
import fastify from 'fastify';
import { FastifyInstance } from 'fastify';
import { ApolloServer } from '../ApolloServer';
import { graphqlFastify } from '../fastifyApollo';
import testSuite, {
schema as Schema,
CreateAppOptions,
} from 'apollo-server-integration-testsuite';
import { GraphQLOptions, Config } from 'apollo-server-core';

async function createApp(options: CreateAppOptions = {}) {
const app = fastify();

const server = new ApolloServer(
(options.graphqlOptions as Config) || { schema: Schema },
);
await server.applyMiddleware({ app });

try {
await app.listen(3007);
} catch (err) {
app.log.error('error in starting server', err);
process.exit(1);
}

return app.server;
}

async function destroyApp(app: any) {
if (!app || !app.close) {
return;
}
await new Promise(cb => app.close(cb));
}

describe('fastifyApollo', () => {
it('throws error if called without schema', function() {
expect(() =>
graphqlFastify(
{} as FastifyInstance,
undefined as GraphQLOptions,
undefined,
),
).toThrow('Apollo Server requires options.');
});

it('throws an error if called with argument not equal to 3', function() {
expect(() => (<any>graphqlFastify)({}, { graphqlOptions: {} })).toThrow(
'Apollo Server expects exactly 3 argument, got 2',
);
});
});

// Uncomment this to see the breaking tests
addityasingh marked this conversation as resolved.
Show resolved Hide resolved
// describe('integration:Fastify', () => {
// testSuite(createApp, destroyApp);
// });