Skip to content

Commit

Permalink
Merge pull request #109 from marnusw/fetchResponses
Browse files Browse the repository at this point in the history
Provide fetch responses to earlier links in the chain
  • Loading branch information
fbartho committed May 22, 2018
2 parents 3ac5855 + 4577a51 commit fe3b0e9
Show file tree
Hide file tree
Showing 3 changed files with 97 additions and 14 deletions.
37 changes: 25 additions & 12 deletions docs/rest.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ Calling REST APIs from a GraphQL client opens the benefits GraphQL for more peop
* You have an existing codebase, but you're looking to evaluate whether GraphQL can work for your needs.
* You have a large codebase, and the GraphQL migration is happening on the backend, but you want to use GraphQL *now* without waiting!

With `apollo-link-rest`, you can now call your endpoints inside your GraphQL queries and have all your data managed by [`ApolloClient`](../../react/basics/setup.html#ApolloClient). `apollo-link-rest` is suitable for just dipping your toes in the water, or doing a full-steam ahead integration, and then later on migrating to a backend-driven GraphQL experience. `apollo-link-rest` combines well with other links such as [`apollo-link-context`](./context.html), [`apollo-link-state`](./state.html), and others! _For complex back-ends, you may want to consider consider using [`apollo-server`](/docs/apollo-server/) which you can try out at [launchpad.graphql.com](https://launchpad.graphql.com/)_
With `apollo-link-rest`, you can now call your endpoints inside your GraphQL queries and have all your data managed by [`ApolloClient`](../../react/basics/setup.html#ApolloClient). `apollo-link-rest` is suitable for just dipping your toes in the water, or doing a full-steam ahead integration, and then later on migrating to a backend-driven GraphQL experience. `apollo-link-rest` combines well with other links such as [`apollo-link-context`](./context.html), [`apollo-link-state`](./state.html), and others! _For complex back-ends, you may want to consider using [`apollo-server`](/docs/apollo-server/) which you can try out at [launchpad.graphql.com](https://launchpad.graphql.com/)_

You can start using ApolloClient in your app today, let's see how!

Expand Down Expand Up @@ -203,22 +203,35 @@ Here is one way you might customize `RestLink`:
* `headers?: Headers`: Additional headers provided in this `context-link` [Values documented here](https://developer.mozilla.org/en-US/docs/Web/API/Request/headers)
* `headersToOverride?: string[]` If you provide this array, we will merge the headers you provide in this link, by replacing any matching headers that exist in the root `RestLink` configuration. Alternatively you can use `headersMergePolicy` for more fine-grained customization of the merging behavior.
* `headersMergePolicy?: RestLink.HeadersMergePolicy`: This is a function that decide how the headers returned in this `contextLink` are merged with headers defined at the `RestLink`-level. If you don't provide this, the headers will be simply appended. To use this option, you can provide your own function that decides how to process the headers. [Code references](https://github.com/apollographql/apollo-link-rest/blob/8e57cabb5344209d9cfa391c1614fe8880efa5d9/src/restLink.ts#L462-L510)
* `restResponses?: Response[]`: This will be populated after the operation has completed with the [Responses](https://developer.mozilla.org/en-US/docs/Web/API/Response) of every REST url fetched during the operation. This can be useful if you need to access the response headers to grab an authorization token for example.

<h3 id="context.headers">Example</h3>
`RestLink` uses the `headers` field on the [`apollo-link-context`](./context.html) so you can compose other links that provide additional & dynamic headers to a given query. These headers will be merged with

Here is one way to add `headers` to the context:
`RestLink` uses the `headers` field on the [`apollo-link-context`](./context.html) so you can compose other links that provide additional & dynamic headers to a given query.

Here is one way to add request `headers` to the context and retrieve the response headers of the operation:

```js
const authRestLink = setContext(async () => {
const token = await localStorage.getItem("token");
return {
headers: {
Accept: "application/json",
Authorization: `Bearer ${token}`
}
};
);
const authRestLink = new ApolloLink((operation, forward) => {
operation.setContext(async ({headers}) => {
const token = await localStorage.getItem("token");
return {
headers: {
...headers,
Accept: "application/json",
Authorization: token
}
};
});
return forward(operation).map(result => {
const { restResponses } = operation.getContext();
const authTokenResponse = restResponses.find(res => res.headers.has("Authorization"));
// You might also filter on res.url to find the response of a specific API call
return authTokenResponse
? localStorage.setItem("token", authTokenResponse.headers.get('Authorization')).then(() => result)
: result;
});
});

const restLink = new RestLink({ uri: "uri" });

Expand Down
55 changes: 55 additions & 0 deletions src/__tests__/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1134,6 +1134,61 @@ describe('Query options', () => {
const credentials = fetchMock.lastCall()[1].credentials;
expect(credentials).toBe('my-credentials');
});

it('sets the fetch responses on context.restResponses', async () => {
expect.assertions(5);

const credentialsMiddleware = new ApolloLink((operation, forward) => {
return forward(operation).map(result => {
const { restResponses } = operation.getContext();
expect(restResponses).toHaveLength(2);
expect(restResponses[0].url).toBe('/api/post/1');
expect(restResponses[0].headers.get('Header1')).toBe('Header1');
expect(restResponses[1].url).toBe('/api/tags');
expect(restResponses[1].headers.get('Header2')).toBe('Header2');
return result;
});
});

const link = ApolloLink.from([
credentialsMiddleware,
new RestLink({ uri: '/api' }),
]);

const context: { restResponses?: Response[] } = {};

const post = { id: '1', title: 'Love apollo' };
fetchMock.get('/api/post/1', {
body: post,
headers: { Header1: 'Header1' },
});

const tags = [{ name: 'apollo' }, { name: 'graphql' }];
fetchMock.get('/api/tags', {
body: tags,
headers: { Header2: 'Header2' },
});

const postAndTags = gql`
query postAndTags {
post @rest(type: "Post", path: "/post/1") {
id
title
tags @rest(type: "[Tag]", path: "/tags") {
name
}
}
}
`;

await makePromise<Result>(
execute(link, {
operationName: 'postAndTags',
query: postAndTags,
context,
}),
);
});
});
describe('method', () => {
it('works for GET requests', async () => {
Expand Down
19 changes: 17 additions & 2 deletions src/restLink.ts
Original file line number Diff line number Diff line change
Expand Up @@ -569,6 +569,9 @@ interface LinkChainContext {

/** List of headers to override, passing this will swap headersMergePolicy if necessary */
headersToOverride?: string[] | null;

/** An array of the responses from each fetched URL, useful for accessing headers in earlier links */
restResponses?: Response[];
}

/** Context passed via graphql() to our resolver */
Expand All @@ -590,6 +593,9 @@ interface RequestContext {
mainDefinition: OperationDefinitionNode | FragmentDefinitionNode;
fragmentDefinitions: FragmentDefinitionNode[];
typePatcher: RestLink.FunctionalTypePatcher;

/** An array of the responses from each fetched URL */
responses: Response[];
}

const resolver: Resolver = async (
Expand Down Expand Up @@ -724,7 +730,10 @@ const resolver: Resolver = async (
}
return res;
})
.then(res => res.json())
.then(res => {
context.responses.push(res);
return res.json();
})
.then(
result =>
fieldNameNormalizer == null
Expand Down Expand Up @@ -847,7 +856,7 @@ export class RestLink extends ApolloLink {
operation: Operation,
forward?: NextLink,
): Observable<FetchResult> | null {
const { query, variables, getContext } = operation;
const { query, variables, getContext, setContext } = operation;
const context: LinkChainContext | any = getContext() as any;
const isRestQuery = hasDirectives(['rest'], operation.query);
if (!isRestQuery) {
Expand Down Expand Up @@ -896,6 +905,7 @@ export class RestLink extends ApolloLink {
mainDefinition,
fragmentDefinitions,
typePatcher: this.typePatcher,
responses: [],
};
const resolverOptions = {};
return new Observable(observer => {
Expand All @@ -908,6 +918,11 @@ export class RestLink extends ApolloLink {
resolverOptions,
)
.then(data => {
setContext({
restResponses: (context.restResponses || []).concat(
requestContext.responses,
),
});
observer.next({ data });
observer.complete();
})
Expand Down

0 comments on commit fe3b0e9

Please sign in to comment.