Skip to content

Commit

Permalink
docs(migration): minor fixes and additions (content-type header and `…
Browse files Browse the repository at this point in the history
…didResolveOperation` errors) (#7423)

* Add note about content-type response header change
* Add note about status code behavior change when errors are thrown from
`didResolveOperation`
* Add example for handling doubly-encoded `variables` (and show it works
in a test)

---------

Co-authored-by: David Glasser <glasser@apollographql.com>
  • Loading branch information
trevor-scheer and glasser committed Mar 7, 2023
1 parent b28b633 commit 175acef
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 1 deletion.
2 changes: 2 additions & 0 deletions .changeset/good-flies-peel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
26 changes: 25 additions & 1 deletion docs/source/migration.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -466,7 +466,7 @@ class MoviesAPI extends RESTDataSource { // highlight-line
}

override willSendRequest(path: string, request: AugmentedRequest) {
request.headers['authorization'] = this.token;
request.headers.authorization = this.token;
}

async getMovie(id: string): Promise<Movie> {
Expand Down Expand Up @@ -1080,6 +1080,24 @@ Whereas this query would be invalid:

(Moreover, Apollo Server 4 responds with a 400 status code if `variables` and `extensions` are provided in a `POST` body with any type other than object, such as array, boolean, or null. Similarly, it responds with a 400 status code if `operationName` is provided in a `POST` body with any type other than string.)

If you'd like to restore the previous behavior, you can `JSON.parse` the `variables` and `extensions` fields after your framework has parsed the request body. In Express that might look like:

```ts
app.use(json());
app.use((req, res, next) => {
if (typeof req.body?.variables === 'string') {
try {
req.body.variables = JSON.parse(req.body.variables);
} catch (e) {
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#json-parsing-failure
res.status(400).send(e instanceof Error ? e.message : e);
}
}
next();
});
app.use(expressMiddleware(server));
```


## Changed features

Expand Down Expand Up @@ -1504,6 +1522,10 @@ new ApolloServer({
});
```

### Content-Type response header

In Apollo Server 3, the `Content-Type` response header is `application/json`. Apollo Server 4 includes the encoding via the `charset` parameter: `application/json; charset=utf-8` as recommended by the [GraphQL over HTTP spec](https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#media-types).

## "Disabled" plugins cannot be combined with their enabled counterpart

Apollo Server has several plugins that are installed by default in certain conditions. To override this behavior, each of these plugins has a "disabled" counterpart that prevents this default installation.
Expand Down Expand Up @@ -1618,6 +1640,8 @@ In Apollo Server 4, `requestDidStart` hooks are called in parallel rather than i

Apollo Server 4 more consistently handles errors thrown by multiple plugin hooks. Each error is wrapped in an "Unexpected error handling request" error and invoked using the new `unexpectedErrorProcessingRequest` plugin hook.

In Apollo Server 3, if a `didResolveOperation` hook threw a `GraphQLError`, the server would return a 400 error by default. In Apollo Server 4, the server will return a 500 by default, but this can be configured by setting [`extensions.http`](./data/errors/#setting-http-status-code-and-headers) on the `GraphQLError` object.

### Custom `gateway` and `GraphQLDataSource` implementations

The `gateway` option to the `ApolloServer` constructor is designed to be used with the `ApolloGateway` class from the `@apollo/gateway` package. Apollo Server 4 changes the details of how Apollo Server interacts with this object. If you use [any version of `@apollo/gateway` that supports `graphql` 16](#graphql) as your server's `gateway`, these changes won't affect you. However, if you provide something _other_ than an `ApolloGateway` instance to this option, you might need to adjust your custom code.
Expand Down
56 changes: 56 additions & 0 deletions packages/server/src/__tests__/express4/expressSpecific.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -141,3 +141,59 @@ it('incremental delivery works with compression', async () => {

await server.stop();
});

it('supporting doubly-encoded variables example from migration guide', async () => {
const server = new ApolloServer({
typeDefs: 'type Query {hello(s: String!): String!}',
resolvers: {
Query: {
hello: (_root, { s }) => s,
},
},
});
await server.start();
const app = express();

app.use(json());

// Test will fail if you remove this middleware.
app.use((req, res, next) => {
if (typeof req.body?.variables === 'string') {
try {
req.body.variables = JSON.parse(req.body.variables);
} catch (e) {
// https://github.com/graphql/graphql-over-http/blob/main/spec/GraphQLOverHTTP.md#json-parsing-failure
res.status(400).send(e instanceof Error ? e.message : e);
}
}
next();
});

app.use(expressMiddleware(server));

await request(app)
.post('/')
.send({
query: 'query Hello($s: String!){hello(s: $s)}',
variables: { s: 'normally encoded' },
})
.expect(200, { data: { hello: 'normally encoded' } });

await request(app)
.post('/')
.send({
query: 'query Hello($s: String!){hello(s: $s)}',
variables: JSON.stringify({ s: 'doubly-encoded' }),
})
.expect(200, { data: { hello: 'doubly-encoded' } });

await request(app)
.post('/')
.send({
query: 'query Hello($s: String!){hello(s: $s)}',
variables: '{malformed JSON}',
})
.expect(400, 'Unexpected token m in JSON at position 1');

await server.stop();
});

0 comments on commit 175acef

Please sign in to comment.