Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 29 additions & 2 deletions src/use/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,10 @@ import { RequestParams } from '../common';
/**
* The context in the request for the handler.
*
* The `res` property is the Express response object for the current request.
* It can be used to manipulate the HTTP response from the `context` option
* or from GraphQL resolvers, for example to set response headers or cookies.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

I'd also move the documentation for the res property to the res property itself, rather than the interface as a whole

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Thanks for the feedback, @benjie . I've moved the res documentation to the res property itself and clarified that req.context.res needs to be returned from the context option to make it available to GraphQL resolvers. I've pushed the updates to the PR.

*
* @category Server/express
*/
export interface RequestContext {
Expand Down Expand Up @@ -82,16 +86,39 @@ export type HandlerOptions<Context extends OperationContext = undefined> =
* Create a GraphQL over HTTP spec compliant request handler for
* the express framework.
*
* The Express response is available to GraphQL resolvers through
* `req.context.res`. This can be used to manipulate the HTTP response,
* for example to set response headers or cookies.
*
* ```js
* import express from 'express'; // yarn add express
* import { createHandler } from 'graphql-http/lib/use/express';
* import { schema } from './my-graphql-schema';
*
* const app = express();
* app.all('/graphql', createHandler({ schema }));
*
* app.all(
* '/graphql',
* createHandler({
* schema,
* context(req) {
* return {
* res: req.context.res,
* };
* },
* }),
* );
*
* app.listen({ port: 4000 });
* console.log('Listening to port 4000');
* ```
*
* A resolver can then manipulate the response:
*
* ```js
* resolve(_, __, { res }) {
* res.setHeader('set-cookie', 'session=; Max-Age=0; Path=/');
* return true;
* }
* ```
*
* @category Server/express
Expand Down
61 changes: 61 additions & 0 deletions tests/use.test.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
import { GraphQLBoolean, GraphQLObjectType, GraphQLSchema } from 'graphql';
import net from 'net';
import { fetch } from '@whatwg-node/fetch';
import { serverAudits } from '../src/audits';
Expand Down Expand Up @@ -137,6 +138,66 @@ describe('express', () => {

await dispose();
});

it('should allow manipulating the response from a resolver', async () => {
const responseSchema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLBoolean,
resolve: () => true,
},
},
}),
mutation: new GraphQLObjectType({
name: 'Mutation',
fields: {
logout: {
type: GraphQLBoolean,
resolve: (_, __, context) => {
context.res.setHeader('x-test', 'test-x');
return true;
},
},
},
}),
});

const app = express();

app.all(
'/',
createExpressHandler({
schema: responseSchema,
context(req) {
return {
res: req.context.res,
};
},
}),
);

const [url, , dispose] = startDisposableServer(app.listen(0));

const res = await fetch(url, {
method: 'POST',
headers: {
'content-type': 'application/json',
},
body: JSON.stringify({
query: 'mutation { logout }',
}),
});

await expect(res.text()).resolves.toMatchInlineSnapshot(
`"{\"data\":{\"logout\":true}}"`,
);

expect(res.headers.get('x-test')).toBe('test-x');

await dispose();
});
});

describe('fastify', () => {
Expand Down