mirror of
https://github.com/vale981/apollo-server
synced 2025-03-04 17:21:42 -05:00
Throw error at startup when file uploads are enabled on Node.js < 8.5.0.
Due to changes in the third-party `graphql-upload` package which Apollo Server utilizes to implement out-of-the-box file upload functionality, we must drop support for file uploads in versions of the Node.js engine prior to v8.5.0. Since file uploads are supported by default in Apollo Server 2.x, and there is an explicit dependency on `graphql-upload`, we must prevent users who are affected by this mid-major-release deprecation by being surprised by the sudden lack of upload support. By `throw`-ing an error at server startup for affected users, we certainly are breaking a semantic versioning agreement for these users, however with a relatively simple ergonomic (setting `uploads: false`) we allow those users who are NOT utilizing file uploads (as we believe is the case with a majority) to continue using their version of Node.js until it reaches the end of its supported lifetime (as dictated by its Long Term Support agreement with the Node.js Foundation). If we did not `throw` the error at server start-up, those affected may not notice since they may update and start their updated server without noticing the impending chance of failure when someone tries updating! Apollo Server 2.x has attempted to maintain full compatibility with versions of Node.js which are still under Long Term Support agreements with the Node.js Foundation. While this continues to mostly be true, file uploads are an exception which we've now had to make. Third-party open-source projects must absolutely do what's best for their project. From an architecture standpoint, I suspect that we (the designers behind Apollo Server) are mostly to blame for this. Namely, it's unfortunate that we had made such an incredibly coupled integration with a third-party package that we restricted our users from incrementally adopting the changes (and new/improved functionality) of, in this particular case, the `graphql-upload` package. I hope we can take better care with decisions like this in the future! Lastly, this commit also adds documentation to help those affected.
This commit is contained in:
parent
0c2fbc3dec
commit
9c563fae50
16 changed files with 159 additions and 26 deletions
|
@ -2,6 +2,14 @@
|
|||
|
||||
### vNEXT
|
||||
|
||||
- **BREAKING FOR NODE.JS <= 8.5.0 ONLY**: To continue using Apollo Server 2.x in versions of Node.js prior to v8.5.0, file uploads must be disabled by setting `uploads: false` on the `ApolloServer` constructor options. Without explicit disabling file-uploads, the server will `throw` at launch (with instructions and a link to our documentation).
|
||||
|
||||
This early deprecation is due to changes in the third-party `graphql-upload` package which Apollo Server utilizes to implement out-of-the-box file upload functionality. While, in general, Apollo Server 2.x aims to support all Node.js versions which were under an LTS policy at the time of its release, we felt this required an exception. By `throw`-ing when `uploads` is not explicitly set to `false`, we aim to make it clear immediately (rather than surprisingly) that this deprecation has taken effect.
|
||||
|
||||
While Node.js 6.x is covered by a [Long Term Support agreement by the Node.js Foundation](https://github.com/nodejs/Release#release-schedule) until April 2019, there are substantial performance (e.g. [V8](https://v8.dev/) improvements) and language changes (e.g. "modern" ECMAScript support) offered by newer Node.js engines (e.g. 8.x, 10.x). We encourage _all users_ of Apollo Server to update to newer LTS versions of Node.js prior to the "end-of-life" dates for their current server version.
|
||||
|
||||
**We intend to drop support for Node.js 6.x in the next major version of Apollo Server.**
|
||||
|
||||
### v2.2.5
|
||||
|
||||
- Follow-up on the update to `graphql-playground-html` in previous release by also bumping the minor version of the `graphql-playground-react` dependency to `1.7.10` — which is the version requested from the from the CDN bundle by `graphql-playground-html`. [PR #2037](https://github.com/apollographql/apollo-server/pull/2037)
|
||||
|
|
|
@ -44,6 +44,7 @@ sidebar_categories:
|
|||
Migration:
|
||||
- migration-two-dot
|
||||
- migration-engine
|
||||
- migration-file-uploads
|
||||
|
||||
github_repo: apollographql/apollo-server
|
||||
content_root: docs/source
|
||||
|
|
29
docs/source/migration-file-uploads.md
Normal file
29
docs/source/migration-file-uploads.md
Normal file
|
@ -0,0 +1,29 @@
|
|||
---
|
||||
title: File uploads in Node.js < v8.5.0
|
||||
---
|
||||
|
||||
File uploads are supported in Apollo Server 2.x through the third-party [`graphql-upload`](https://npm.im/graphql-upload/) package. While Apollo Server 2.x aims to support Node.js LTS versions prior to v8.5.0, the `graphql-upload` project no longer supports file uploads on versions of Node.js prior to v8.5.0 due to changes in the underlying architecture.
|
||||
|
||||
While Node.js versions prior to v8.5.0 are still under [_Long Term Support_ (LTS) agreements](https://github.com/nodejs/Release#release-schedule) from the Node.js Foundation, **we encourage _all users_ of Apollo Server to update to newer LTS versions of Node.js** prior to the "end-of-life" dates for their current server version.
|
||||
|
||||
For example, while Node.js 6.x is covered by Long Term Support until April 2019, there are substantial performance (e.g. [V8](https://v8.dev/) improvements) and language changes (e.g. "modern" ECMAScript support) offered by newer Node.js engines (e.g. 8.x, 10.x). Switching to newer Long Term Support versions of Node.js comes with long-term benefits; Node.js 10.x LTS releases are covered by the Node.js Foundation through April 2021.
|
||||
|
||||
Since file upload support for Node.js versions prior to v8.5.0 is no longer offered by `graphql-upload`, users of those versions must must disable file uploads to continue using newer Apollo Server 2.x versions.
|
||||
|
||||
**To disable file uploads and continue using Apollo Server 2.x on Node.js 6.x**, add the `uploads: false` setting to the options when constructing the server. For example:
|
||||
|
||||
```js
|
||||
const { typeDefs, resolvers } = require('./anOutsideDependency');
|
||||
const server = new ApolloServer({
|
||||
/* Existing Apollo Server settings — e.g. type definitions */
|
||||
typeDefs,
|
||||
resolvers,
|
||||
|
||||
/* Add this line to disable upload support! */
|
||||
uploads: false,
|
||||
|
||||
/* ... other Apollo Server settings ... */
|
||||
})
|
||||
```
|
||||
|
||||
For additional assistance, please [search for existing issues](https://github.com/apollographql/apollo-server/issues?q=uploads) or file a [new issue](https://github.com/apollographql/apollo-server/issues/new) on the Apollo Server GitHub repository.
|
6
package-lock.json
generated
6
package-lock.json
generated
|
@ -2203,7 +2203,6 @@
|
|||
"cors": "^2.8.4",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"type-is": "^1.6.16"
|
||||
},
|
||||
"dependencies": {
|
||||
|
@ -2221,8 +2220,7 @@
|
|||
"apollo-server-core": "file:packages/apollo-server-core",
|
||||
"boom": "^7.1.0",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0"
|
||||
"graphql-tools": "^4.0.0"
|
||||
}
|
||||
},
|
||||
"apollo-server-integration-testsuite": {
|
||||
|
@ -2246,7 +2244,6 @@
|
|||
"apollo-server-core": "file:packages/apollo-server-core",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"koa": "2.6.2",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-router": "^7.4.0",
|
||||
|
@ -2280,7 +2277,6 @@
|
|||
"@apollographql/graphql-playground-html": "^1.6.6",
|
||||
"accept": "^3.0.2",
|
||||
"apollo-server-core": "file:packages/apollo-server-core",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"micro": "^9.3.2"
|
||||
},
|
||||
"dependencies": {
|
||||
|
|
|
@ -14,6 +14,7 @@ import { GraphQLExtension } from 'graphql-extensions';
|
|||
import { EngineReportingAgent } from 'apollo-engine-reporting';
|
||||
import { InMemoryLRUCache } from 'apollo-server-caching';
|
||||
import { ApolloServerPlugin } from 'apollo-server-plugin-base';
|
||||
import supportsUploadsInNode from './utils/supportsUploadsInNode';
|
||||
|
||||
import {
|
||||
SubscriptionServer,
|
||||
|
@ -201,6 +202,14 @@ export class ApolloServerBase {
|
|||
|
||||
if (uploads !== false) {
|
||||
if (this.supportsUploads()) {
|
||||
if (!supportsUploadsInNode) {
|
||||
printNodeFileUploadsMessage();
|
||||
throw new Error(
|
||||
'`graphql-upload` is no longer supported on Node.js < v8.5.0. ' +
|
||||
'See https://bit.ly/gql-upload-node-6.',
|
||||
);
|
||||
}
|
||||
|
||||
if (uploads === true || typeof uploads === 'undefined') {
|
||||
this.uploadsConfig = {};
|
||||
} else {
|
||||
|
@ -540,3 +549,32 @@ export class ApolloServerBase {
|
|||
return processGraphQLRequest(options, requestCtx);
|
||||
}
|
||||
}
|
||||
|
||||
function printNodeFileUploadsMessage() {
|
||||
console.error(
|
||||
[
|
||||
'*****************************************************************',
|
||||
'* *',
|
||||
'* ERROR! Manual intervention is necessary for Node.js < v8.5.0! *',
|
||||
'* *',
|
||||
'*****************************************************************',
|
||||
'',
|
||||
'The third-party `graphql-upload` package, which is used to implement',
|
||||
'file uploads in Apollo Server 2.x, no longer supports Node.js LTS',
|
||||
'versions prior to Node.js v8.5.0.',
|
||||
'',
|
||||
'If this server DOES NOT USE file uploads, it is necessary to add:',
|
||||
'',
|
||||
' uploads: false,',
|
||||
'',
|
||||
'to the options for Apollo Server and re-deploy to disable file uploads',
|
||||
'and continue using this version of Node.js.',
|
||||
'',
|
||||
'Deployments which need file upload capabilities should update to',
|
||||
'Node.js >= v8.5.0 to continue using newer Apollo Server versions.',
|
||||
'',
|
||||
'For more information, see https://bit.ly/gql-upload-node-6.',
|
||||
'',
|
||||
].join('\n'),
|
||||
);
|
||||
}
|
||||
|
|
|
@ -41,6 +41,18 @@ export const gql: (
|
|||
...substitutions: any[]
|
||||
) => DocumentNode = gqlTag;
|
||||
|
||||
import supportsUploadsInNode from './utils/supportsUploadsInNode';
|
||||
import { GraphQLScalarType } from 'graphql';
|
||||
import { GraphQLUpload as UploadScalar } from 'graphql-upload';
|
||||
export const GraphQLUpload = UploadScalar as GraphQLScalarType;
|
||||
export { default as processFileUploads } from './processFileUploads';
|
||||
|
||||
// This is a conditional export intended to avoid traversing the
|
||||
// entire module tree of `graphql-upload`. This only defined if the
|
||||
// version of Node.js is >= 8.5.0 since those are the only Node.js versions
|
||||
// which are supported by `graphql-upload@8`. Since the source of
|
||||
// `graphql-upload` is not transpiled for older targets (in fact, it includes
|
||||
// experimental ECMAScript modules), this conditional export is necessary
|
||||
// to avoid modern ECMAScript from failing to parse by versions of Node.js
|
||||
// which don't support it (yet — eg. Node.js 6 and async/await).
|
||||
export const GraphQLUpload = supportsUploadsInNode
|
||||
? (require('graphql-upload').GraphQLUpload as GraphQLScalarType)
|
||||
: undefined;
|
||||
|
|
16
packages/apollo-server-core/src/processFileUploads.ts
Normal file
16
packages/apollo-server-core/src/processFileUploads.ts
Normal file
|
@ -0,0 +1,16 @@
|
|||
import supportsUploadsInNode from './utils/supportsUploadsInNode';
|
||||
|
||||
// We'll memoize this function once at module load time since it should never
|
||||
// change during runtime. In the event that we're using a version of Node.js
|
||||
// less than 8.5.0, we'll
|
||||
const processFileUploads:
|
||||
| typeof import('graphql-upload').processRequest
|
||||
| undefined = (() => {
|
||||
if (supportsUploadsInNode) {
|
||||
return require('graphql-upload')
|
||||
.processRequest as typeof import('graphql-upload').processRequest;
|
||||
}
|
||||
return undefined;
|
||||
})();
|
||||
|
||||
export default processFileUploads;
|
|
@ -0,0 +1,21 @@
|
|||
const supportsUploadsInNode = (() => {
|
||||
if (
|
||||
process &&
|
||||
process.release &&
|
||||
process.release.name === 'node' &&
|
||||
process.versions &&
|
||||
typeof process.versions.node === 'string'
|
||||
) {
|
||||
const [nodeMajor, nodeMinor] = process.versions.node
|
||||
.split('.', 2)
|
||||
.map(segment => parseInt(segment, 10));
|
||||
|
||||
if (nodeMajor < 8 || (nodeMajor === 8 && nodeMinor < 5)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
})();
|
||||
|
||||
export default supportsUploadsInNode;
|
|
@ -37,7 +37,6 @@
|
|||
"cors": "^2.8.4",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"type-is": "^1.6.16"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -10,14 +10,13 @@ import {
|
|||
FileUploadOptions,
|
||||
ApolloServerBase,
|
||||
formatApolloErrors,
|
||||
processFileUploads,
|
||||
} from 'apollo-server-core';
|
||||
import accepts from 'accepts';
|
||||
import typeis from 'type-is';
|
||||
|
||||
import { graphqlExpress } from './expressApollo';
|
||||
|
||||
import { processRequest as processFileUploads } from 'graphql-upload';
|
||||
|
||||
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
|
||||
|
||||
export interface ServerRegistration {
|
||||
|
@ -44,7 +43,10 @@ const fileUploadMiddleware = (
|
|||
next: express.NextFunction,
|
||||
) => {
|
||||
// Note: we use typeis directly instead of via req.is for connect support.
|
||||
if (typeis(req, ['multipart/form-data'])) {
|
||||
if (
|
||||
typeof processFileUploads === 'function' &&
|
||||
typeis(req, ['multipart/form-data'])
|
||||
) {
|
||||
processFileUploads(req, res, uploadsConfig)
|
||||
.then(body => {
|
||||
req.body = body;
|
||||
|
@ -134,7 +136,7 @@ export class ApolloServer extends ApolloServerBase {
|
|||
}
|
||||
|
||||
let uploadsMiddleware;
|
||||
if (this.uploadsConfig) {
|
||||
if (this.uploadsConfig && typeof processFileUploads === 'function') {
|
||||
uploadsMiddleware = fileUploadMiddleware(this.uploadsConfig, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -30,8 +30,7 @@
|
|||
"apollo-server-core": "file:../apollo-server-core",
|
||||
"boom": "^7.1.0",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0"
|
||||
"graphql-tools": "^4.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"apollo-server-integration-testsuite": "file:../apollo-server-integration-testsuite"
|
||||
|
|
|
@ -4,7 +4,6 @@ import {
|
|||
renderPlaygroundPage,
|
||||
RenderPageOptions as PlaygroundRenderPageOptions,
|
||||
} from '@apollographql/graphql-playground-html';
|
||||
import { processRequest as processFileUploads } from 'graphql-upload';
|
||||
|
||||
import { graphqlHapi } from './hapiApollo';
|
||||
|
||||
|
@ -13,11 +12,15 @@ import {
|
|||
ApolloServerBase,
|
||||
GraphQLOptions,
|
||||
FileUploadOptions,
|
||||
processFileUploads,
|
||||
} from 'apollo-server-core';
|
||||
|
||||
function handleFileUploads(uploadsConfig: FileUploadOptions) {
|
||||
return async (request: hapi.Request, _h?: hapi.ResponseToolkit) => {
|
||||
if (request.mime === 'multipart/form-data') {
|
||||
if (
|
||||
typeof processFileUploads === 'function' &&
|
||||
request.mime === 'multipart/form-data'
|
||||
) {
|
||||
Object.defineProperty(request, 'payload', {
|
||||
value: await processFileUploads(
|
||||
request,
|
||||
|
@ -68,7 +71,7 @@ export class ApolloServer extends ApolloServerBase {
|
|||
return h.continue;
|
||||
}
|
||||
|
||||
if (this.uploadsConfig) {
|
||||
if (this.uploadsConfig && typeof processFileUploads === 'function') {
|
||||
await handleFileUploads(this.uploadsConfig)(request);
|
||||
}
|
||||
|
||||
|
|
|
@ -37,7 +37,6 @@
|
|||
"apollo-server-core": "file:../apollo-server-core",
|
||||
"graphql-subscriptions": "^1.0.0",
|
||||
"graphql-tools": "^4.0.0",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"koa": "2.6.2",
|
||||
"koa-bodyparser": "^3.0.0",
|
||||
"koa-router": "^7.4.0",
|
||||
|
|
|
@ -6,14 +6,16 @@ import {
|
|||
renderPlaygroundPage,
|
||||
RenderPageOptions as PlaygroundRenderPageOptions,
|
||||
} from '@apollographql/graphql-playground-html';
|
||||
import { ApolloServerBase, formatApolloErrors } from 'apollo-server-core';
|
||||
import {
|
||||
ApolloServerBase,
|
||||
formatApolloErrors,
|
||||
processFileUploads,
|
||||
} from 'apollo-server-core';
|
||||
import accepts from 'accepts';
|
||||
import typeis from 'type-is';
|
||||
|
||||
import { graphqlKoa } from './koaApollo';
|
||||
|
||||
import { processRequest as processFileUploads } from 'graphql-upload';
|
||||
|
||||
export { GraphQLOptions, GraphQLExtension } from 'apollo-server-core';
|
||||
import { GraphQLOptions, FileUploadOptions } from 'apollo-server-core';
|
||||
|
||||
|
@ -138,7 +140,7 @@ export class ApolloServer extends ApolloServerBase {
|
|||
}
|
||||
|
||||
let uploadsMiddleware;
|
||||
if (this.uploadsConfig) {
|
||||
if (this.uploadsConfig && typeof processFileUploads === 'function') {
|
||||
uploadsMiddleware = fileUploadMiddleware(this.uploadsConfig, this);
|
||||
}
|
||||
|
||||
|
|
|
@ -26,7 +26,6 @@
|
|||
"@apollographql/graphql-playground-html": "^1.6.6",
|
||||
"accept": "^3.0.2",
|
||||
"apollo-server-core": "file:../apollo-server-core",
|
||||
"graphql-upload": "^8.0.0",
|
||||
"micro": "^9.3.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
|
@ -1,5 +1,8 @@
|
|||
import { ApolloServerBase, GraphQLOptions } from 'apollo-server-core';
|
||||
import { processRequest as processFileUploads } from 'graphql-upload';
|
||||
import {
|
||||
ApolloServerBase,
|
||||
GraphQLOptions,
|
||||
processFileUploads,
|
||||
} from 'apollo-server-core';
|
||||
import { ServerResponse } from 'http';
|
||||
import { send } from 'micro';
|
||||
import { renderPlaygroundPage } from '@apollographql/graphql-playground-html';
|
||||
|
@ -39,7 +42,9 @@ export class ApolloServer extends ApolloServerBase {
|
|||
|
||||
await promiseWillStart;
|
||||
|
||||
if (typeof processFileUploads === 'function') {
|
||||
await this.handleFileUploads(req, res);
|
||||
}
|
||||
|
||||
(await this.handleHealthCheck({
|
||||
req,
|
||||
|
@ -162,6 +167,10 @@ export class ApolloServer extends ApolloServerBase {
|
|||
// If file uploads are detected, prepare them for easier handling with
|
||||
// the help of `graphql-upload`.
|
||||
private async handleFileUploads(req: MicroRequest, res: ServerResponse) {
|
||||
if (typeof processFileUploads !== 'function') {
|
||||
return;
|
||||
}
|
||||
|
||||
const contentType = req.headers['content-type'];
|
||||
if (
|
||||
this.uploadsConfig &&
|
||||
|
|
Loading…
Add table
Reference in a new issue