Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
34 changes: 32 additions & 2 deletions src/use/express.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,12 @@ import { RequestParams } from '../common';
* @category Server/express
*/
export interface RequestContext {
/**
* The Express response object for the current request.
*
* This can be used to manipulate the HTTP response, for example to set
* response headers or cookies.
*/
res: Response;
}

Expand Down Expand Up @@ -82,16 +88,40 @@ 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 through `req.context.res`. To expose it
* to GraphQL resolvers, return it from the `context` option. 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