diff --git a/CHANGELOG.md b/CHANGELOG.md index 71b41dec8a2..1f79805ec58 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ client reference ID, Apollo Server will now default to the values present in the of the request (`apollographql-client-name`, `apollographql-client-reference-id` and `apollographql-client-version` respectively). As a last resort, when those headers are not set, the query extensions' `clientInfo` values will be used. [PR #1960](https://github.com/apollographql/apollo-server/pull/1960) +- Added `apollo-server-fastify` integration ([@rkorrelboom](https://github.com/rkorrelboom) in [#1971](https://github.com/apollostack/apollo-server/pull/1971)) ### v2.2.2 diff --git a/README.md b/README.md index a5c413a7b95..d2153a300fe 100644 --- a/README.md +++ b/README.md @@ -86,6 +86,7 @@ Often times, Apollo Server needs to be run with a particular integration. To sta - `apollo-server-express` - `apollo-server-koa` - `apollo-server-hapi` +- `apollo-server-fastify` - `apollo-server-lambda` - `apollo-server-azure-functions` - `apollo-server-cloud-functions` @@ -235,6 +236,25 @@ new ApolloServer({ }) ``` +## Fastify + +```js +const { ApolloServer, gql } = require('apollo-server-fastify'); +const { typeDefs, resolvers } = require('./module'); + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +const app = require('fastify')(); + +(async function () { + app.register(server.createHandler()); + await app.listen(3000); +})(); +``` + ### AWS Lambda Apollo Server can be run on Lambda and deployed with AWS Serverless Application Model (SAM). It requires an API Gateway with Lambda Proxy Integration. diff --git a/package-lock.json b/package-lock.json index cdb22ae8db1..a6a4951c5a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -77,7 +77,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -109,7 +109,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -1501,6 +1501,16 @@ "@types/node": "*" } }, + "@types/pino": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@types/pino/-/pino-4.16.1.tgz", + "integrity": "sha512-uYEhZ3jsuiYFsPcR34fbxVlrqzqphc+QQ3fU4rWR6PXH8ka2TKvPBjtkNqj8oBHouVGf4GCRfyPb7FG2TEtPZA==", + "dev": true, + "requires": { + "@types/events": "*", + "@types/node": "*" + } + }, "@types/podium": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/@types/podium/-/podium-1.0.0.tgz", @@ -1616,6 +1626,12 @@ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", "dev": true }, + "abstract-logging": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/abstract-logging/-/abstract-logging-1.0.0.tgz", + "integrity": "sha1-i33q/TEFWbwo93ck3RuzAXcnjBs=", + "dev": true + }, "accept": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/accept/-/accept-3.0.2.tgz", @@ -2246,6 +2262,17 @@ "type-is": "^1.6.16" } }, + "apollo-server-fastify": { + "version": "file:packages/apollo-server-fastify", + "requires": { + "@apollographql/graphql-playground-html": "^1.6.6", + "apollo-server-core": "file:packages/apollo-server-core", + "fastify-accepts": "^0.5.0", + "fastify-cors": "^0.2.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + } + }, "apollo-server-hapi": { "version": "file:packages/apollo-server-hapi", "requires": { @@ -2554,6 +2581,33 @@ "integrity": "sha512-Wm6ukoaOGJi/73p/cl2GvLjTI5JM1k/O14isD73YML8StrH/7/lRFgmg8nICZgD3bZZvjwCGxtMOD3wWNAu8cg==", "dev": true }, + "avvio": { + "version": "5.9.0", + "resolved": "https://registry.npmjs.org/avvio/-/avvio-5.9.0.tgz", + "integrity": "sha512-bzgrSPRdU1T/AkhEuXWAA6cJCFA3zApLk+5fkpcQt4US9YAI52AFYnsGX1HSCF2bHSltEYfk7fbffYu4WnazmA==", + "dev": true, + "requires": { + "debug": "^3.1.0", + "fastq": "^1.6.0" + }, + "dependencies": { + "debug": { + "version": "3.2.6", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.6.tgz", + "integrity": "sha512-mel+jf7nrtEl5Pn1Qx46zARXKDpBbvzezse7p7LqINmdoIk8PYP5SySaxEmYv6TZ0JyEKA1hsCId6DIhgITtWQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + }, + "ms": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.1.tgz", + "integrity": "sha512-tgp+dl5cGk28utYktBsrFqA7HKgrhgPsg6Z/EfhWI4gl1Hwq8B/GmY/0oXZ6nF8hDVesS/FpnYaD/kOWhYQvyg==", + "dev": true + } + } + }, "aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", @@ -3342,7 +3396,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -4105,6 +4159,12 @@ "integrity": "sha1-s2nW+128E+7PUk+RsHD+7cNXzzQ=", "dev": true }, + "deepmerge": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-3.0.0.tgz", + "integrity": "sha512-a8z8bkgHsAML+uHLqmMS83HHlpy3PvZOOuiTQqaa3wu8ZVg3h0hqHk6aCsGdOnZV2XMM/FRimNGjUh0KCcmHBw==", + "dev": true + }, "default-require-extensions": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/default-require-extensions/-/default-require-extensions-1.0.0.tgz", @@ -4200,7 +4260,7 @@ "dependencies": { "globby": { "version": "6.1.0", - "resolved": "https://registry.npmjs.org/globby/-/globby-6.1.0.tgz", + "resolved": "http://registry.npmjs.org/globby/-/globby-6.1.0.tgz", "integrity": "sha1-9abXDoOV4hyFj7BInWTfAkJNUGw=", "dev": true, "requires": { @@ -4213,7 +4273,7 @@ "dependencies": { "pify": { "version": "2.3.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "resolved": "http://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", "dev": true } @@ -4748,6 +4808,12 @@ "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", "dev": true }, + "fast-decode-uri-component": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fast-decode-uri-component/-/fast-decode-uri-component-1.0.0.tgz", + "integrity": "sha512-WQSYVKn6tDW/3htASeUkrx5LcnuTENQIZQPCVlwdnvIJ7bYtSpoJYq38MgUJnx1CQIR1gjZ8HJxAEcN4gqugBg==", + "dev": true + }, "fast-deep-equal": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-1.1.0.tgz", @@ -5080,17 +5146,157 @@ } } }, + "fast-json-parse": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/fast-json-parse/-/fast-json-parse-1.0.3.tgz", + "integrity": "sha512-FRWsaZRWEJ1ESVNbDWmsAlqDk96gPQezzLghafp5J4GUKjbCz3OkAHuZs5TuPEtkbVQERysLp9xv6c24fBm8Aw==", + "dev": true + }, "fast-json-stable-stringify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.0.0.tgz", "integrity": "sha1-1RQsDK7msRifh9OnYREGT4bIu/I=" }, + "fast-json-stringify": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/fast-json-stringify/-/fast-json-stringify-1.10.0.tgz", + "integrity": "sha512-qO+GSdwCQHXJjoRbS/pYJzzz8BNUrCk0jdPhDg68mdIG/hOC0k66PUKFz300LAH42vNQkuPFvpwa0JV44ZG3Uw==", + "dev": true, + "requires": { + "ajv": "^6.5.4", + "deepmerge": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, "fast-levenshtein": { "version": "2.0.6", "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=", "dev": true }, + "fast-safe-stringify": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-1.2.3.tgz", + "integrity": "sha512-QJYT/i0QYoiZBQ71ivxdyTqkwKkQ0oxACXHYxH2zYHJEgzi2LsbjgvtzTbLi1SZcF190Db2YP7I7eTsU2egOlw==", + "dev": true + }, + "fastify": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastify/-/fastify-1.13.0.tgz", + "integrity": "sha512-0aqsHEk2WtgGxPVDTOqDLv5XLHQE2EuH7eCq4XRLLnktLehNvr3/Afi/nEn6pPoLiVGMrbWHv4l1+wDhiSIFoA==", + "dev": true, + "requires": { + "@types/pino": "^4.16.0", + "abstract-logging": "^1.0.0", + "ajv": "^6.5.4", + "avvio": "^5.8.0", + "end-of-stream": "^1.4.1", + "fast-json-stringify": "^1.8.0", + "find-my-way": "^1.15.3", + "flatstr": "^1.0.8", + "light-my-request": "^3.0.0", + "middie": "^3.1.0", + "pino": "^4.17.3", + "proxy-addr": "^2.0.3", + "tiny-lru": "^1.6.1" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + } + } + }, + "fastify-accepts": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/fastify-accepts/-/fastify-accepts-0.5.0.tgz", + "integrity": "sha1-wXwgEnjyv8Ub+5P/5I78T8QP02M=", + "requires": { + "accepts": "^1.3.3", + "fastify-plugin": "^0.2.1" + } + }, + "fastify-cors": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/fastify-cors/-/fastify-cors-0.2.0.tgz", + "integrity": "sha512-bw14FmjHm8oF4TDLkwj2TpssH6O2gE0NpsRqLe7F1Gh9Jf30Lx9ZzIznhqaAKOYS+LJqLIt5snurv7urgqYntA==", + "requires": { + "fastify-plugin": "^1.2.0", + "vary": "^1.1.2" + }, + "dependencies": { + "fastify-plugin": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/fastify-plugin/-/fastify-plugin-1.4.0.tgz", + "integrity": "sha512-l6uqDyBp3gBjLQRAi3j2NwSvlbe9LuqULZugnO9iRFfYHWd2SpsZBLI1l4Jakk0VMGfYlB322JPIPYh/2qSHig==", + "requires": { + "semver": "^5.5.0" + } + } + } + }, + "fastify-plugin": { + "version": "0.2.2", + "resolved": "http://registry.npmjs.org/fastify-plugin/-/fastify-plugin-0.2.2.tgz", + "integrity": "sha512-oRJdjdudgCkQQUARNeh2rkbxFAmj2OhCJSVBNBLUbhS0orF+IMQ4u/bc661N1jh/wDI2J+YKmXmmHSVFQI4e7A==", + "requires": { + "semver": "^5.4.1" + } + }, + "fastq": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.6.0.tgz", + "integrity": "sha512-jmxqQ3Z/nXoeyDmWAzF9kH1aGZSis6e/SbfPmJpUnyZ0ogr6iscHQaml4wsEepEWSdtmpy+eVXmCRIMpxaXqOA==", + "dev": true, + "requires": { + "reusify": "^1.0.0" + } + }, "fb-watchman": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.0.tgz", @@ -5179,6 +5385,17 @@ } } }, + "find-my-way": { + "version": "1.17.0", + "resolved": "https://registry.npmjs.org/find-my-way/-/find-my-way-1.17.0.tgz", + "integrity": "sha512-V/ROUAESJakNZXmvgJmaCwhB8d4M79/RgWWiKn4tu/6FGjiySYfJuYXip1rV7fORWEzbL0pmfg9smkRQ+tmXjg==", + "dev": true, + "requires": { + "fast-decode-uri-component": "^1.0.0", + "safe-regex": "^1.1.0", + "semver-store": "^0.3.0" + } + }, "find-npm-prefix": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/find-npm-prefix/-/find-npm-prefix-1.0.2.tgz", @@ -5200,6 +5417,12 @@ "locate-path": "^3.0.0" } }, + "flatstr": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/flatstr/-/flatstr-1.0.9.tgz", + "integrity": "sha512-qFlJnOBWDfIaunF54/lBqNKmXOI0HqNhu+mHkLmbaBXlS71PUd9OjFOdyevHt/aHoHB1+eW7eKHgRKOG5aHSpw==", + "dev": true + }, "flush-write-stream": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/flush-write-stream/-/flush-write-stream-1.0.3.tgz", @@ -5423,7 +5646,8 @@ "version": "2.1.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true + "dev": true, + "optional": true }, "aproba": { "version": "1.2.0", @@ -5447,13 +5671,15 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true + "dev": true, + "optional": true }, "brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, + "optional": true, "requires": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5470,19 +5696,22 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", - "dev": true + "dev": true, + "optional": true }, "concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true + "dev": true, + "optional": true }, "console-control-strings": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", - "dev": true + "dev": true, + "optional": true }, "core-util-is": { "version": "1.0.2", @@ -5613,7 +5842,8 @@ "version": "2.0.3", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true + "dev": true, + "optional": true }, "ini": { "version": "1.3.5", @@ -5627,6 +5857,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, + "optional": true, "requires": { "number-is-nan": "^1.0.0" } @@ -5643,6 +5874,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, + "optional": true, "requires": { "brace-expansion": "^1.1.7" } @@ -5651,13 +5883,15 @@ "version": "0.0.8", "resolved": "https://registry.npmjs.org/minimist/-/minimist-0.0.8.tgz", "integrity": "sha1-hX/Kv8M5fSYluCKCYuhqp6ARsF0=", - "dev": true + "dev": true, + "optional": true }, "minipass": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.2.4.tgz", "integrity": "sha512-hzXIWWet/BzWhYs2b+u7dRHlruXhwdgvlTMDKC6Cb1U7ps6Ac6yQlR39xsbjWJE377YTCtKwIXIpJ5oP+j5y8g==", "dev": true, + "optional": true, "requires": { "safe-buffer": "^5.1.1", "yallist": "^3.0.0" @@ -5678,6 +5912,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.1.tgz", "integrity": "sha1-MAV0OOrGz3+MR2fzhkjWaX11yQM=", "dev": true, + "optional": true, "requires": { "minimist": "0.0.8" } @@ -5766,7 +6001,8 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", - "dev": true + "dev": true, + "optional": true }, "object-assign": { "version": "4.1.1", @@ -5780,6 +6016,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, + "optional": true, "requires": { "wrappy": "1" } @@ -5875,7 +6112,8 @@ "version": "5.1.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==", - "dev": true + "dev": true, + "optional": true }, "safer-buffer": { "version": "2.1.2", @@ -5917,6 +6155,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, + "optional": true, "requires": { "code-point-at": "^1.0.0", "is-fullwidth-code-point": "^1.0.0", @@ -5938,6 +6177,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, + "optional": true, "requires": { "ansi-regex": "^2.0.0" } @@ -5986,13 +6226,15 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true + "dev": true, + "optional": true }, "yallist": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.0.2.tgz", "integrity": "sha1-hFK0u36Dx8GI2AQcGoN8dz1ti7k=", - "dev": true + "dev": true, + "optional": true } } }, @@ -8807,6 +9049,62 @@ } } }, + "light-my-request": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/light-my-request/-/light-my-request-3.1.0.tgz", + "integrity": "sha512-ZSFO3XnQNSKsHR/E2ZMga5btdiIa3sNoT6CZIZ8Hr1VHJWBNcRRurVYpQlaJqvQqwg3aOl09QpVOnjB9ajnYHQ==", + "dev": true, + "requires": { + "ajv": "^6.0.0", + "readable-stream": "^3.0.0" + }, + "dependencies": { + "ajv": { + "version": "6.6.2", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.6.2.tgz", + "integrity": "sha512-FBHEW6Jf5TB9MGBgUUA9XHkTbjXYfAUjY43ACMfmdMRHniyoMHjHjzD50OK8LGDWQwp4rWEsIq5kEqq7rvIM1g==", + "dev": true, + "requires": { + "fast-deep-equal": "^2.0.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "fast-deep-equal": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-2.0.1.tgz", + "integrity": "sha1-ewUhjd+WZ79/Nwv3/bLLFf3Qqkk=", + "dev": true + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true + }, + "readable-stream": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.1.1.tgz", + "integrity": "sha512-DkN66hPyqDhnIQ6Jcsvx9bFjhw214O4poMBcIMgPVpQvNy9a0e0Uhg5SqySyDKAmUlwt8LonTBz1ezOnM8pUdA==", + "dev": true, + "requires": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + } + }, + "string_decoder": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.2.0.tgz", + "integrity": "sha512-6YqyX6ZWEYguAxgZzHGL7SsCeGx3V2TtOTqZz1xSTSWnqsbWwbptafNyvf/ACquZUXV3DANr5BDIwNYe1mN42w==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.0" + } + } + } + }, "lint-staged": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-8.1.0.tgz", @@ -9216,7 +9514,7 @@ }, "chalk": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "resolved": "http://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { @@ -9248,7 +9546,7 @@ }, "strip-ansi": { "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "resolved": "http://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { @@ -9828,6 +10126,24 @@ "regex-cache": "^0.4.2" } }, + "middie": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/middie/-/middie-3.2.0.tgz", + "integrity": "sha512-anXJ0QJfQcgneQvcWAJBwVvNckRLI68zWNEUv/7/7z/Wb/UMFTHmugpM93T4Q75+DclC9FHdms8cTseDQEV3yA==", + "dev": true, + "requires": { + "path-to-regexp": "^2.0.0", + "reusify": "^1.0.2" + }, + "dependencies": { + "path-to-regexp": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-2.4.0.tgz", + "integrity": "sha512-G6zHoVqC6GGTQkZwF4lkuEyMbVOjoBKAEybQUypI1WTkqinCOrq2x6U2+phkJ1XsEMTy4LjtwPI7HW+NVrRR2w==", + "dev": true + } + } + }, "mime": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", @@ -10877,6 +11193,41 @@ "pinkie": "^2.0.0" } }, + "pino": { + "version": "4.17.6", + "resolved": "https://registry.npmjs.org/pino/-/pino-4.17.6.tgz", + "integrity": "sha512-LFDwmhyWLBnmwO/2UFbWu1jEGVDzaPupaVdx0XcZ3tIAx1EDEBauzxXf2S0UcFK7oe+X9MApjH0hx9U1XMgfCA==", + "dev": true, + "requires": { + "chalk": "^2.4.1", + "fast-json-parse": "^1.0.3", + "fast-safe-stringify": "^1.2.3", + "flatstr": "^1.0.5", + "pino-std-serializers": "^2.0.0", + "pump": "^3.0.0", + "quick-format-unescaped": "^1.1.2", + "split2": "^2.2.0" + }, + "dependencies": { + "chalk": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + } + } + }, + "pino-std-serializers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-2.3.0.tgz", + "integrity": "sha512-klfGoOsP6sJH7ON796G4xoUSx2fkpFgKHO4YVVO2zmz31jR+etzc/QzGJILaOIiCD6HTCFgkPx+XN8nk+ruqPw==", + "dev": true + }, "pkg-dir": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-3.0.0.tgz", @@ -11160,6 +11511,15 @@ } } }, + "quick-format-unescaped": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-1.1.2.tgz", + "integrity": "sha1-DKWB3jF0vs7yWsPC6JVjQjgdtpg=", + "dev": true, + "requires": { + "fast-safe-stringify": "^1.0.8" + } + }, "quick-lru": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-1.1.0.tgz", @@ -11552,6 +11912,12 @@ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", "integrity": "sha1-G0KmJmoh8HQh0bC1S33BZ7AcATs=" }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, "rimraf": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.6.2.tgz", @@ -11927,8 +12293,7 @@ "semver": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", - "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==", - "dev": true + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "semver-compare": { "version": "1.0.0", @@ -11936,6 +12301,12 @@ "integrity": "sha1-De4hahyUGrN+nvsXiPavxf9VN/w=", "dev": true }, + "semver-store": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/semver-store/-/semver-store-0.3.0.tgz", + "integrity": "sha512-TcZvGMMy9vodEFSse30lWinkj+JgOBvPn8wRItpQRSayhc+4ssDs335uklkfvQQJgL/WvmHLVj4Ycv2s7QCQMg==", + "dev": true + }, "send": { "version": "0.16.2", "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", @@ -12080,7 +12451,7 @@ }, "slice-ansi": { "version": "0.0.4", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", + "resolved": "http://registry.npmjs.org/slice-ansi/-/slice-ansi-0.0.4.tgz", "integrity": "sha1-7b+JA/ZvfOL46v1s7tZeJkyDGzU=", "dev": true }, @@ -12760,6 +13131,12 @@ } } }, + "tiny-lru": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/tiny-lru/-/tiny-lru-1.6.4.tgz", + "integrity": "sha512-Et+J3Css66XPSLWjLF9wmgbECsGiExlEL+jxsFerTQF6N6dpxswDTPAfIrAbQKO5c1uhgq2xvo5zMk1W+kBDNA==", + "dev": true + }, "tmp": { "version": "0.0.33", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.0.33.tgz", @@ -12907,7 +13284,7 @@ }, "minimist": { "version": "1.2.0", - "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", + "resolved": "http://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=", "dev": true }, @@ -13161,6 +13538,23 @@ } } }, + "uri-js": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.2.2.tgz", + "integrity": "sha512-KY9Frmirql91X2Qgjry0Wd4Y+YTdrdZheS8TFwvkbLWf/G5KNJDCh6pKL5OZctEW4+0Baa5idK2ZQuELRwPznQ==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + }, + "dependencies": { + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + } + } + }, "urijs": { "version": "1.19.1", "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.1.tgz", diff --git a/package.json b/package.json index 434526f5523..ba133dfbdd8 100644 --- a/package.json +++ b/package.json @@ -51,6 +51,7 @@ "apollo-server-env": "file:packages/apollo-server-env", "apollo-server-errors": "file:packages/apollo-server-errors", "apollo-server-express": "file:packages/apollo-server-express", + "apollo-server-fastify": "file:packages/apollo-server-fastify", "apollo-server-hapi": "file:packages/apollo-server-hapi", "apollo-server-integration-testsuite": "file:packages/apollo-server-integration-testsuite", "apollo-server-koa": "file:packages/apollo-server-koa", @@ -94,6 +95,7 @@ "codecov": "3.1.0", "connect": "3.6.6", "express": "4.16.4", + "fastify": "1.13.0", "fibers": "3.1.1", "form-data": "2.3.3", "graphql": "14.1.1", diff --git a/packages/apollo-server-fastify/.npmignore b/packages/apollo-server-fastify/.npmignore new file mode 100644 index 00000000000..a165046d359 --- /dev/null +++ b/packages/apollo-server-fastify/.npmignore @@ -0,0 +1,6 @@ +* +!src/**/* +!dist/**/* +dist/**/*.test.* +!package.json +!README.md diff --git a/packages/apollo-server-fastify/README.md b/packages/apollo-server-fastify/README.md new file mode 100644 index 00000000000..39a322c49af --- /dev/null +++ b/packages/apollo-server-fastify/README.md @@ -0,0 +1,41 @@ +--- +title: Fastify +description: Setting up Apollo Server with Fastify +--- + +[![npm version](https://badge.fury.io/js/apollo-server-fastify.svg)](https://badge.fury.io/js/apollo-server-fastify) [![Build Status](https://circleci.com/gh/apollographql/apollo-server.svg?style=svg)](https://circleci.com/gh/apollographql/apollo-server) [![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 many Node.js HTTP server frameworks. [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 +const { ApolloServer, gql } = require('apollo-server-fastify'); +const { typeDefs, resolvers } = require('./module'); + +const server = new ApolloServer({ + typeDefs, + resolvers, +}); + +const app = require('fastify')(); + +(async function () { + app.register(server.createHandler()); + await app.listen(3000); +})(); +``` + +## 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! diff --git a/packages/apollo-server-fastify/jest.config.js b/packages/apollo-server-fastify/jest.config.js new file mode 100644 index 00000000000..a383fbc925f --- /dev/null +++ b/packages/apollo-server-fastify/jest.config.js @@ -0,0 +1,3 @@ +const config = require('../../jest.config.base'); + +module.exports = Object.assign(Object.create(null), config); diff --git a/packages/apollo-server-fastify/package.json b/packages/apollo-server-fastify/package.json new file mode 100644 index 00000000000..1fe504ba193 --- /dev/null +++ b/packages/apollo-server-fastify/package.json @@ -0,0 +1,41 @@ +{ + "name": "apollo-server-fastify", + "version": "2.2.2", + "description": "Production-ready Node.js GraphQL server for Fastify", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "repository": { + "type": "git", + "url": "https://github.com/apollographql/apollo-server/tree/master/packages/apollo-server-fastify" + }, + "keywords": [ + "GraphQL", + "Apollo", + "Server", + "Fastify", + "Javascript" + ], + "author": "opensource@apollographql.com", + "license": "MIT", + "bugs": { + "url": "https://github.com/apollographql/apollo-server/issues" + }, + "homepage": "https://github.com/apollographql/apollo-server#readme", + "engines": { + "node": ">=6" + }, + "dependencies": { + "@apollographql/graphql-playground-html": "^1.6.6", + "apollo-server-core": "file:../apollo-server-core", + "fastify-accepts": "^0.5.0", + "fastify-cors": "^0.2.0", + "graphql-subscriptions": "^1.0.0", + "graphql-tools": "^4.0.0" + }, + "devDependencies": { + "apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite" + }, + "peerDependencies": { + "graphql": "^0.12.0 || ^0.13.0 || ^14.0.0" + } +} diff --git a/packages/apollo-server-fastify/src/ApolloServer.ts b/packages/apollo-server-fastify/src/ApolloServer.ts new file mode 100644 index 00000000000..d8a6f73d50c --- /dev/null +++ b/packages/apollo-server-fastify/src/ApolloServer.ts @@ -0,0 +1,191 @@ +import { renderPlaygroundPage } from '@apollographql/graphql-playground-html'; +import { Accepts } from 'accepts'; +import { + ApolloServerBase, + FileUploadOptions, + formatApolloErrors, + PlaygroundRenderPageOptions, + processFileUploads, +} from 'apollo-server-core'; +import { FastifyInstance, FastifyReply, FastifyRequest } from 'fastify'; +import { IncomingMessage, OutgoingMessage, Server } from 'http'; +import { graphqlFastify } from './fastifyApollo'; + +const kMultipart = Symbol('multipart'); +const fastJson = require('fast-json-stringify'); + +export interface ServerRegistration { + path?: string; + cors?: object | boolean; + onHealthCheck?: (req: FastifyRequest) => Promise; + disableHealthCheck?: boolean; +} + +const stringifyHealthCheck = fastJson({ + type: 'object', + properties: { + status: { + type: 'string', + }, + }, +}); + +const fileUploadMiddleware = ( + uploadsConfig: FileUploadOptions, + server: ApolloServerBase, +) => ( + req: FastifyRequest, + reply: FastifyReply, + done: (err: Error | null, body?: any) => void, +) => { + if ( + (req.req as any)[kMultipart] && + typeof processFileUploads === 'function' + ) { + processFileUploads(req.req, reply.res, uploadsConfig) + .then(body => { + req.body = body; + done(null); + }) + .catch(error => { + if (error.status && error.expose) reply.status(error.status); + + throw formatApolloErrors([error], { + formatter: server.requestOptions.formatError, + debug: server.requestOptions.debug, + }); + }); + } else { + done(null); + } +}; + +export class ApolloServer extends ApolloServerBase { + protected supportsSubscriptions(): boolean { + return true; + } + + protected supportsUploads(): boolean { + return true; + } + + public createHandler({ + path, + cors, + disableHealthCheck, + onHealthCheck, + }: ServerRegistration = {}) { + this.graphqlPath = path ? path : '/graphql'; + const promiseWillStart = this.willStart(); + + return async ( + app: FastifyInstance, + ) => { + await promiseWillStart; + + if (!disableHealthCheck) { + app.get('/.well-known/apollo/server-health', async (req, res) => { + // Response follows https://tools.ietf.org/html/draft-inadarei-api-health-check-01 + res.type('application/health+json'); + + if (onHealthCheck) { + try { + await onHealthCheck(req); + res.send(stringifyHealthCheck({ status: 'pass' })); + } catch (e) { + res.status(503).send(stringifyHealthCheck({ status: 'fail' })); + } + } else { + res.send(stringifyHealthCheck({ status: 'pass' })); + } + }); + } + + app.register( + async instance => { + instance.register(require('fastify-accepts')); + + if (cors === true) { + instance.register(require('fastify-cors')); + } else if (cors !== false) { + instance.register(require('fastify-cors'), cors); + } + + instance.setNotFoundHandler((_request, reply) => { + reply.code(405); + reply.header('allow', 'GET, POST'); + reply.send(); + }); + + const beforeHandlers = [ + ( + req: FastifyRequest, + reply: FastifyReply, + done: () => void, + ) => { + // Note: if you enable playground in production and expect to be able to see your + // schema, you'll need to manually specify `introspection: true` in the + // ApolloServer constructor; by default, the introspection query is only + // enabled in dev. + if (this.playgroundOptions && req.req.method === 'GET') { + // perform more expensive content-type check only if necessary + const accept = (req as any).accepts() as Accepts; + const types = accept.types() as string[]; + const prefersHTML = + types.find( + (x: string) => + x === 'text/html' || x === 'application/json', + ) === 'text/html'; + + if (prefersHTML) { + const playgroundRenderPageOptions: PlaygroundRenderPageOptions = { + endpoint: this.graphqlPath, + subscriptionEndpoint: this.subscriptionsPath, + ...this.playgroundOptions, + }; + reply.type('text/html'); + const playground = renderPlaygroundPage( + playgroundRenderPageOptions, + ); + reply.send(playground); + return; + } + } + done(); + }, + ]; + + if (typeof processFileUploads === 'function' && this.uploadsConfig) { + instance.addContentTypeParser( + 'multipart', + ( + request: IncomingMessage, + done: (err: Error | null, body?: any) => void, + ) => { + (request as any)[kMultipart] = true; + done(null); + }, + ); + beforeHandlers.push(fileUploadMiddleware(this.uploadsConfig, this)); + } + + instance.route({ + method: ['GET', 'POST'], + url: '/', + beforeHandler: beforeHandlers, + handler: await graphqlFastify(this.graphQLServerOptions.bind(this)), + }); + }, + { + prefix: this.graphqlPath, + }, + ); + }; + } +} + +export const registerServer = () => { + throw new Error( + 'Please use server.createHandler instead of registerServer. This warning will be removed in the next release', + ); +}; diff --git a/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts new file mode 100644 index 00000000000..356e005e3e1 --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/ApolloServer.test.ts @@ -0,0 +1,836 @@ +import { FastifyInstance } from 'fastify'; +import fastify from 'fastify'; + +import http from 'http'; + +import request from 'request'; +import FormData from 'form-data'; +import fs from 'fs'; +import { createApolloFetch } from 'apollo-fetch'; + +import { gql, AuthenticationError, Config } from 'apollo-server-core'; +import { ApolloServer, ServerRegistration } from '../ApolloServer'; + +import { + NODE_MAJOR_VERSION, + testApolloServer, + createServerInfo, +} from 'apollo-server-integration-testsuite'; + +const typeDefs = gql` + type Query { + hello: String + } +`; + +const resolvers = { + Query: { + hello: () => 'hi', + }, +}; + +const port = 9999; + +describe('apollo-server-fastify', () => { + let server: ApolloServer; + let httpServer: http.Server; + let app: FastifyInstance; + + testApolloServer( + async options => { + server = new ApolloServer(options); + app = fastify(); + app.register(server.createHandler()); + await app.listen(port); + return createServerInfo(server, app.server); + }, + async () => { + if (server) await server.stop(); + if (app) await new Promise(resolve => app.close(() => resolve())); + if (httpServer && httpServer.listening) await httpServer.close(); + }, + ); +}); + +describe('apollo-server-fastify', () => { + let server: ApolloServer; + let app: FastifyInstance; + let httpServer: http.Server; + + async function createServer( + serverOptions: Config, + options: Partial = {}, + ) { + server = new ApolloServer(serverOptions); + app = fastify(); + + app.register(server.createHandler(options)); + await app.listen(port); + + return createServerInfo(server, app.server); + } + + afterEach(async () => { + if (server) await server.stop(); + if (app) await new Promise(resolve => app.close(() => resolve())); + if (httpServer) await httpServer.close(); + }); + + describe('constructor', async () => { + it('accepts typeDefs and resolvers', () => { + return createServer({ typeDefs, resolvers }); + }); + }); + + describe('applyMiddleware', async () => { + it('can be queried', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + }); + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: '{hello}' }); + + expect(result.data).toEqual({ hello: 'hi' }); + expect(result.errors).toBeUndefined(); + }); + + // XXX Unclear why this would be something somebody would want (vs enabling + // introspection without graphql-playground, which seems reasonable, eg you + // have your own graphql-playground setup with a custom link) + it('can enable playground separately from introspection during production', async () => { + const INTROSPECTION_QUERY = ` + { + __schema { + directives { + name + } + } + } +`; + + const { url: uri } = await createServer({ + typeDefs, + resolvers, + introspection: false, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ query: INTROSPECTION_QUERY }); + + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual( + 'GRAPHQL_VALIDATION_FAILED', + ); + + return new Promise((resolve, reject) => { + request( + { + url: uri, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('renders GraphQL playground by default when browser requests', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url } = await createServer({ + typeDefs, + resolvers, + }); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + process.env.NODE_ENV = nodeEnv; + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(body).not.toMatch('settings'); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + const playgroundPartialOptionsTest = async () => { + const defaultQuery = 'query { foo { bar } }'; + const endpoint = '/fumanchupacabra'; + const { url } = await createServer( + { + typeDefs, + resolvers, + playground: { + // https://github.com/apollographql/graphql-playground/blob/0e452d2005fcd26f10fbdcc4eed3b2e2af935e3a/packages/graphql-playground-html/src/render-playground-page.ts#L16-L24 + // must be made partial + settings: { + 'editor.theme': 'light', + } as any, + tabs: [ + { + query: defaultQuery, + }, + { + endpoint, + } as any, + ], + }, + }, + {}, + ); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + Folo: 'bar', + }, + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toMatch('GraphQLPlayground'); + expect(body).toMatch(`"editor.theme": "light"`); + expect(body).toMatch(defaultQuery); + expect(body).toMatch(endpoint); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }; + + it('accepts partial GraphQL Playground Options in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + await playgroundPartialOptionsTest(); + process.env.NODE_ENV = nodeEnv; + }); + + it( + 'accepts partial GraphQL Playground Options when an environment is ' + + 'not specified', + async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + await playgroundPartialOptionsTest(); + process.env.NODE_ENV = nodeEnv; + }, + ); + + it('accepts playground options as a boolean', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url } = await createServer( + { + typeDefs, + resolvers, + playground: false, + }, + {}, + ); + + return new Promise((resolve, reject) => { + request( + { + url, + method: 'GET', + headers: { + accept: + 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8', + }, + }, + (error, response, body) => { + process.env.NODE_ENV = nodeEnv; + if (error) { + reject(error); + } else { + expect(body).not.toMatch('GraphQLPlayground'); + expect(response.statusCode).not.toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('accepts cors configuration', async () => { + const { url: uri } = await createServer( + { + typeDefs, + resolvers, + }, + { + cors: { origin: 'apollographql.com' }, + }, + ); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect( + response.response.headers.get('access-control-allow-origin'), + ).toEqual('apollographql.com'); + next(); + }, + ); + await apolloFetch({ query: '{hello}' }); + }); + + describe('healthchecks', () => { + afterEach(async () => { + await server.stop(); + }); + + it('creates a healthcheck endpoint', async () => { + const { port } = await createServer({ + typeDefs, + resolvers, + }); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toEqual(JSON.stringify({ status: 'pass' })); + expect(response.statusCode).toEqual(200); + resolve(); + } + }, + ); + }); + }); + + it('provides a callback for the healthcheck', async () => { + const { port } = await createServer( + { + typeDefs, + resolvers, + }, + { + onHealthCheck: async () => { + throw Error("can't connect to DB"); + }, + }, + ); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response, body) => { + if (error) { + reject(error); + } else { + expect(body).toEqual(JSON.stringify({ status: 'fail' })); + expect(response.statusCode).toEqual(503); + resolve(); + } + }, + ); + }); + }); + + it('can disable the healthCheck', async () => { + const { port } = await createServer( + { + typeDefs, + resolvers, + }, + { + disableHealthCheck: true, + }, + ); + + return new Promise((resolve, reject) => { + request( + { + url: `http://localhost:${port}/.well-known/apollo/server-health`, + method: 'GET', + }, + (error, response) => { + if (error) { + reject(error); + } else { + expect(response.statusCode).toEqual(404); + resolve(); + } + }, + ); + }); + }); + }); + // NODE: Skip Node.js 6, but only because `graphql-upload` + // doesn't support it. + (NODE_MAJOR_VERSION === 6 ? describe.skip : describe)( + 'file uploads', + () => { + it('enabled uploads', async () => { + const { port } = await createServer({ + typeDefs: gql` + type File { + filename: String! + mimetype: String! + encoding: String! + } + + type Query { + uploads: [File] + } + + type Mutation { + singleUpload(file: Upload!): File! + } + `, + resolvers: { + Query: { + uploads: () => {}, + }, + Mutation: { + singleUpload: async (_, args) => { + expect((await args.file).stream).toBeDefined(); + return args.file; + }, + }, + }, + }); + + const body = new FormData(); + + body.append( + 'operations', + JSON.stringify({ + query: ` + mutation($file: Upload!) { + singleUpload(file: $file) { + filename + encoding + mimetype + } + } + `, + variables: { + file: null, + }, + }), + ); + + body.append('map', JSON.stringify({ 1: ['variables.file'] })); + body.append('1', fs.createReadStream('package.json')); + + try { + const resolved = await fetch(`http://localhost:${port}/graphql`, { + method: 'POST', + body: body as any, + }); + const text = await resolved.text(); + const response = JSON.parse(text); + + expect(response.data.singleUpload).toEqual({ + filename: 'package.json', + encoding: '7bit', + mimetype: 'application/json', + }); + } catch (error) { + // This error began appearing randomly and seems to be a dev dependency bug. + // https://github.com/jaydenseric/apollo-upload-server/blob/18ecdbc7a1f8b69ad51b4affbd986400033303d4/test.js#L39-L42 + if (error.code !== 'EPIPE') throw error; + } + }); + }, + ); + + describe('errors', () => { + it('returns thrown context error as a valid graphql result', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + const typeDefs = gql` + type Query { + hello: String + } + `; + const resolvers = { + Query: { + hello: () => { + throw Error('never get here'); + }, + }, + }; + const { url: uri } = await createServer({ + typeDefs, + resolvers, + context: () => { + throw new AuthenticationError('valid result'); + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: '{hello}' }); + expect(result.errors.length).toEqual(1); + expect(result.data).toBeUndefined(); + + const e = result.errors[0]; + expect(e.message).toMatch('valid result'); + expect(e.extensions).toBeDefined(); + expect(e.extensions.code).toEqual('UNAUTHENTICATED'); + expect(e.extensions.exception.stacktrace).toBeDefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes in dev mode', async () => { + const nodeEnv = process.env.NODE_ENV; + delete process.env.NODE_ENV; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeDefined(); + expect(result.data).toEqual({ error: null }); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeDefined(); + expect(result.errors[0].extensions.exception.stacktrace).toBeDefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeDefined(); + expect(result.data).toEqual({ error: null }); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeUndefined(); + + process.env.NODE_ENV = nodeEnv; + }); + + it('propogates error codes with null response in production', async () => { + const nodeEnv = process.env.NODE_ENV; + process.env.NODE_ENV = 'production'; + + const { url: uri } = await createServer({ + typeDefs: gql` + type Query { + error: String! + } + `, + resolvers: { + Query: { + error: () => { + throw new AuthenticationError('we the best music'); + }, + }, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + + const result = await apolloFetch({ query: `{error}` }); + expect(result.data).toBeNull(); + + expect(result.errors).toBeDefined(); + expect(result.errors.length).toEqual(1); + expect(result.errors[0].extensions.code).toEqual('UNAUTHENTICATED'); + expect(result.errors[0].extensions.exception).toBeUndefined(); + + process.env.NODE_ENV = nodeEnv; + }); + }); + }); + + describe('extensions', () => { + const books = [ + { + title: 'H', + author: 'J', + }, + ]; + + const typeDefs = gql` + type Book { + title: String + author: String + } + + type Cook @cacheControl(maxAge: 200) { + title: String + author: String + } + + type Pook @cacheControl(maxAge: 200) { + title: String + books: [Book] @cacheControl(maxAge: 20, scope: PRIVATE) + } + + type Query { + books: [Book] + cooks: [Cook] + pooks: [Pook] + } + `; + + const resolvers = { + Query: { + books: () => books, + cooks: () => books, + pooks: () => [{ title: 'pook', books }], + }, + }; + + describe('Cache Control Headers', () => { + it('applies cacheControl Headers and strips out extension', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toEqual( + 'max-age=200, public', + ); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ cooks { title author } }`, + }); + expect(result.data).toEqual({ cooks: books }); + expect(result.extensions).toBeUndefined(); + }); + + it('contains no cacheControl Headers and keeps extension with engine proxy', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + cacheControl: true, + }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ cooks { title author } }`, + }); + expect(result.data).toEqual({ cooks: books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.cacheControl).toBeDefined(); + }); + + it('contains no cacheControl Headers when uncachable', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeUndefined(); + }); + + it('contains private cacheControl Headers when scoped', async () => { + const { url: uri } = await createServer({ typeDefs, resolvers }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toEqual( + 'max-age=20, private', + ); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ pooks { title books { title author } } }`, + }); + expect(result.data).toEqual({ + pooks: [{ title: 'pook', books }], + }); + expect(result.extensions).toBeUndefined(); + }); + + it('runs when cache-control is false', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + cacheControl: false, + }); + + const apolloFetch = createApolloFetch({ uri }).useAfter( + (response, next) => { + expect(response.response.headers.get('cache-control')).toBeNull(); + next(); + }, + ); + const result = await apolloFetch({ + query: `{ pooks { title books { title author } } }`, + }); + expect(result.data).toEqual({ + pooks: [{ title: 'pook', books }], + }); + expect(result.extensions).toBeUndefined(); + }); + }); + + describe('Tracing', () => { + const typeDefs = gql` + type Book { + title: String + author: String + } + + type Query { + books: [Book] + } + `; + + const resolvers = { + Query: { + books: () => books, + }, + }; + + it('applies tracing extension', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + + it('applies tracing extension with cache control enabled', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + cacheControl: true, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + + xit('applies tracing extension with engine enabled', async () => { + const { url: uri } = await createServer({ + typeDefs, + resolvers, + tracing: true, + engine: { + apiKey: 'service:my-app:secret', + maxAttempts: 0, + endpointUrl: 'l', + reportErrorFunction: () => {}, + }, + }); + + const apolloFetch = createApolloFetch({ uri }); + const result = await apolloFetch({ + query: `{ books { title author } }`, + }); + expect(result.data).toEqual({ books }); + expect(result.extensions).toBeDefined(); + expect(result.extensions.tracing).toBeDefined(); + }); + }); + }); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/datasource.test.ts b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts new file mode 100644 index 00000000000..d7c6d9f5f7d --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/datasource.test.ts @@ -0,0 +1,140 @@ +import fastify, { FastifyInstance } from 'fastify'; + +import { RESTDataSource } from 'apollo-datasource-rest'; + +import { createApolloFetch } from 'apollo-fetch'; +import { ApolloServer } from '../ApolloServer'; + +import { createServerInfo } from 'apollo-server-integration-testsuite'; +import { gql } from '../index'; + +const restPort = 4001; + +export class IdAPI extends RESTDataSource { + baseURL = `http://localhost:${restPort}/`; + + async getId(id: string) { + return this.get(`id/${id}`); + } + + async getStringId(id: string) { + return this.get(`str/${id}`); + } +} + +const typeDefs = gql` + type Query { + id: String + stringId: String + } +`; + +const resolvers = { + Query: { + id: async (_source, _args, { dataSources }) => { + return (await dataSources.id.getId('hi')).id; + }, + stringId: async (_source, _args, { dataSources }) => { + return dataSources.id.getStringId('hi'); + }, + }, +}; + +let restCalls = 0; +const restAPI = fastify(); + +restAPI.get('/id/:id', (req, res) => { + const id = req.params.id; + restCalls++; + res.header('Content-Type', 'application/json'); + res.header('Cache-Control', 'max-age=2000, public'); + res.send({ id }); +}); + +restAPI.get('/str/:id', (req, res) => { + const id = req.params.id; + restCalls++; + res.header('Content-Type', 'text/plain'); + res.header('Cache-Control', 'max-age=2000, public'); + res.send(id); +}); + +describe('apollo-server-fastify', () => { + let restServer: FastifyInstance; + let app: FastifyInstance; + + beforeAll(async () => { + await restAPI.listen(restPort); + }); + + afterAll(async () => { + await new Promise(resolve => restServer.close(() => resolve())); + }); + + let server: ApolloServer; + + beforeEach(() => { + restCalls = 0; + }); + + afterEach(async () => { + await server.stop(); + await new Promise(resolve => app.close(() => resolve())); + }); + + it('uses the cache', async () => { + server = new ApolloServer({ + typeDefs, + resolvers, + dataSources: () => ({ + id: new IdAPI(), + }), + }); + app = fastify(); + + app.register(server.createHandler()); + await app.listen(6667); + const { url: uri } = createServerInfo(server, app.server); + + const apolloFetch = createApolloFetch({ uri }); + const firstResult = await apolloFetch({ query: '{ id }' }); + + expect(firstResult.data).toEqual({ id: 'hi' }); + expect(firstResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + + const secondResult = await apolloFetch({ query: '{ id }' }); + + expect(secondResult.data).toEqual({ id: 'hi' }); + expect(secondResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + }); + + it('can cache a string from the backend', async () => { + server = new ApolloServer({ + typeDefs, + resolvers, + dataSources: () => ({ + id: new IdAPI(), + }), + }); + app = fastify(); + + app.register(server.createHandler()); + await app.listen(6668); + const { url: uri } = createServerInfo(server, app.server); + + const apolloFetch = createApolloFetch({ uri }); + const firstResult = await apolloFetch({ query: '{ id: stringId }' }); + + expect(firstResult.data).toEqual({ id: 'hi' }); + expect(firstResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + + const secondResult = await apolloFetch({ query: '{ id: stringId }' }); + + expect(secondResult.data).toEqual({ id: 'hi' }); + expect(secondResult.errors).toBeUndefined(); + expect(restCalls).toEqual(1); + }); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts new file mode 100644 index 00000000000..d81d4d0126b --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/fastifyApollo.test.ts @@ -0,0 +1,40 @@ +import fastify from 'fastify'; +import { Server } from 'http'; +import { ApolloServer } from '../ApolloServer'; +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 }, + ); + + app.register(server.createHandler()); + await app.listen(); + + return app.server; +} + +async function destroyApp(app: Server) { + if (!app || !app.close) { + return; + } + await new Promise(resolve => app.close(resolve)); +} + +describe('fastifyApollo', () => { + it('throws error if called without schema', function() { + expect(() => new ApolloServer(undefined as GraphQLOptions)).toThrow( + 'ApolloServer requires options.', + ); + }); +}); + +describe('integration:Fastify', () => { + testSuite(createApp, destroyApp); +}); diff --git a/packages/apollo-server-fastify/src/__tests__/tsconfig.json b/packages/apollo-server-fastify/src/__tests__/tsconfig.json new file mode 100644 index 00000000000..86b8a49b265 --- /dev/null +++ b/packages/apollo-server-fastify/src/__tests__/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../../../tsconfig.test.base", + "include": ["**/*"], + "references": [ + { "path": "../../" }, + { "path": "../../../apollo-server-integration-testsuite" } + ] +} diff --git a/packages/apollo-server-fastify/src/fastifyApollo.ts b/packages/apollo-server-fastify/src/fastifyApollo.ts new file mode 100644 index 00000000000..ee8945a526c --- /dev/null +++ b/packages/apollo-server-fastify/src/fastifyApollo.ts @@ -0,0 +1,59 @@ +import { + convertNodeHttpToRequest, + GraphQLOptions, + runHttpQuery, +} from 'apollo-server-core'; +import { FastifyReply, FastifyRequest, RequestHandler } from 'fastify'; +import { IncomingMessage, OutgoingMessage } from 'http'; + +export async function graphqlFastify( + options: ( + req?: FastifyRequest, + res?: FastifyReply, + ) => GraphQLOptions | Promise, +): Promise> { + if (!options) { + throw new Error('Apollo Server requires options.'); + } + + return async ( + request: FastifyRequest, + reply: FastifyReply, + ) => { + try { + const { graphqlResponse, responseInit } = await runHttpQuery( + [request, reply], + { + method: request.req.method as string, + options, + query: request.req.method === 'POST' ? request.body : request.query, + request: convertNodeHttpToRequest(request.raw), + }, + ); + + if (responseInit.headers) { + for (const [name, value] of Object.entries( + responseInit.headers, + )) { + reply.header(name, value); + } + } + reply.serializer((payload: string) => payload); + reply.send(graphqlResponse); + } catch (error) { + if ('HttpQueryError' !== error.name) { + throw error; + } + + if (error.headers) { + Object.keys(error.headers).forEach(header => { + reply.header(header, error.headers[header]); + }); + } + + reply.code(error.statusCode); + reply.serializer((payload: string) => payload); + reply.send(error.message); + } + }; +} diff --git a/packages/apollo-server-fastify/src/index.ts b/packages/apollo-server-fastify/src/index.ts new file mode 100644 index 00000000000..38374b22e5f --- /dev/null +++ b/packages/apollo-server-fastify/src/index.ts @@ -0,0 +1,29 @@ +export { + GraphQLUpload, + GraphQLOptions, + GraphQLExtension, + Config, + gql, + // Errors + ApolloError, + toApolloError, + SyntaxError, + ValidationError, + AuthenticationError, + ForbiddenError, + UserInputError, + // playground + defaultPlaygroundOptions, + PlaygroundConfig, + PlaygroundRenderPageOptions, +} from 'apollo-server-core'; + +export * from 'graphql-tools'; +export * from 'graphql-subscriptions'; + +// ApolloServer integration. +export { + ApolloServer, + registerServer, + ServerRegistration, +} from './ApolloServer'; diff --git a/packages/apollo-server-fastify/tsconfig.json b/packages/apollo-server-fastify/tsconfig.json new file mode 100644 index 00000000000..71b94f32842 --- /dev/null +++ b/packages/apollo-server-fastify/tsconfig.json @@ -0,0 +1,12 @@ +{ + "extends": "../../tsconfig.base", + "compilerOptions": { + "rootDir": "./src", + "outDir": "./dist", + }, + "include": ["src/**/*"], + "exclude": ["**/__tests__", "**/__mocks__"], + "references": [ + { "path": "../apollo-server-core" }, + ] +} diff --git a/tsconfig.build.json b/tsconfig.build.json index 24a42b86b9e..cd6cd85dea1 100644 --- a/tsconfig.build.json +++ b/tsconfig.build.json @@ -19,6 +19,7 @@ { "path": "./packages/apollo-server-core" }, { "path": "./packages/apollo-server-errors" }, { "path": "./packages/apollo-server-express" }, + { "path": "./packages/apollo-server-fastify" }, { "path": "./packages/apollo-server-hapi" }, { "path": "./packages/apollo-server-koa" }, { "path": "./packages/apollo-server-lambda" }, diff --git a/tsconfig.test.json b/tsconfig.test.json index 1d906e03b7a..aa6f00ceeb8 100644 --- a/tsconfig.test.json +++ b/tsconfig.test.json @@ -16,6 +16,7 @@ { "path": "./packages/apollo-server-cloud-functions/src/__tests__/" }, { "path": "./packages/apollo-server-core/src/__tests__/" }, { "path": "./packages/apollo-server-express/src/__tests__/" }, + { "path": "./packages/apollo-server-fastify/src/__tests__/" }, { "path": "./packages/apollo-server-hapi/src/__tests__/" }, { "path": "./packages/apollo-server-koa/src/__tests__/" }, { "path": "./packages/apollo-server-lambda/src/__tests__/" },