Skip to content

Commit

Permalink
feat: Support per-request headers (#33) (#228)
Browse files Browse the repository at this point in the history
closes #33

There is a new third parameter on the `request` an `rawRequest` methods for passing headers particular to that request.

For example:

```js
import { GraphQLClient } from 'graphql-request'

const client = new GraphQLClient(endpoint)

const query = gql`
  query getMovie($title: String!) {
    Movie(title: $title) {
      releaseDate
      actors {
        name
      }
    }
  }
`
const variables = {
  title: 'Inception',
}

const requestHeaders = {
  authorization: 'Bearer MY_TOKEN'
}

// Overrides the clients headers with the passed values
const data = await client.request(query, variables, requestHeaders)
```
  • Loading branch information
moeHaydar authored Nov 16, 2020
1 parent c75a29a commit 952e972
Show file tree
Hide file tree
Showing 4 changed files with 133 additions and 8 deletions.
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ Minimal GraphQL client supporting Node and browsers for scripts or simple apps
- [Examples](#examples)
- [Authentication via HTTP header](#authentication-via-http-header)
- [Incrementally setting headers](#incrementally-setting-headers)
- [Passing Headers in each request](#passing-headers-in-each-request)
- [Passing more options to `fetch`](#passing-more-options-to-fetch)
- [Using GraphQL Document variables](#using-graphql-document-variables)
- [GraphQL Mutations](#graphql-mutations)
Expand Down Expand Up @@ -153,6 +154,39 @@ client.setHeaders({
})
```

#### passing-headers-in-each-request

It is possible to pass custom headers for each request. `request()` and `rawRequest()` accept a header object as the third parameter


```js
import { GraphQLClient } from 'graphql-request'

const client = new GraphQLClient(endpoint)

const query = gql`
query getMovie($title: String!) {
Movie(title: $title) {
releaseDate
actors {
name
}
}
}
`

const variables = {
title: 'Inception',
}

const requestHeaders = {
authorization: 'Bearer MY_TOKEN'
}

// Overrides the clients headers with the passed values
const data = await client.request(query, variables, requestHeaders)
```

### Passing more options to `fetch`

```js
Expand Down
33 changes: 33 additions & 0 deletions examples/passing-custom-header-per-request.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { GraphQLClient } from '../src'
;(async function () {
const endpoint = 'https://api.graph.cool/simple/v1/cixos23120m0n0173veiiwrjr'

const client = new GraphQLClient(endpoint, {
headers: {
authorization: 'Bearer MY_TOKEN',
},
})

const query = /* GraphQL */ `
{
Movie(title: "Inception") {
releaseDate
actors {
name
}
}
}
`

const requestHeaders = {
authorization: 'Bearer MY_TOKEN_2',
'x-custom': 'foo'
}

interface TData {
Movie: { releaseDate: string; actors: Array<{ name: string }> }
}

const data = await client.request<TData>(query, {}, requestHeaders)
console.log(JSON.stringify(data, undefined, 2))
})().catch((error) => console.error(error))
15 changes: 9 additions & 6 deletions src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,17 +43,19 @@ export class GraphQLClient {

async rawRequest<T = any, V = Variables>(
query: string,
variables?: V
variables?: V,
requestHeaders?: Dom.RequestInit['headers']
): Promise<{ data?: T; extensions?: any; headers: Dom.Headers; status: number; errors?: GraphQLError[] }> {
let { headers, fetch: localFetch = crossFetch, ...others } = this.options
const body = createRequestBody(query, variables)
headers = resolveHeaders(headers)


const response = await localFetch(this.url, {
method: 'POST',
headers: {
...(typeof body === 'string' ? { 'Content-Type': 'application/json' } : {}),
...headers,
...resolveHeaders(headers),
...resolveHeaders(requestHeaders)
},
body,
...others,
Expand All @@ -76,17 +78,18 @@ export class GraphQLClient {
/**
* Send a GraphQL document to the server.
*/
async request<T = any, V = Variables>(document: RequestDocument, variables?: V): Promise<T> {
async request<T = any, V = Variables>(document: RequestDocument, variables?: V,
requestHeaders?: Dom.RequestInit['headers']): Promise<T> {
let { headers, fetch: localFetch = crossFetch, ...others } = this.options
headers = resolveHeaders(headers)
const resolvedDoc = resolveRequestDocument(document)
const body = createRequestBody(resolvedDoc, variables)

const response = await localFetch(this.url, {
method: 'POST',
headers: {
...(typeof body === 'string' ? { 'Content-Type': 'application/json' } : {}),
...headers,
...resolveHeaders(headers),
...resolveHeaders(requestHeaders)
},
body,
...others,
Expand Down
59 changes: 57 additions & 2 deletions tests/headers.test.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import * as CrossFetch from 'cross-fetch'
import * as Dom from '../src/types.dom'
import { GraphQLClient } from '../src'
import { setupTestServer } from './__helpers'

const ctx = setupTestServer()
// Headers not defined globally in Node
const H = typeof Headers === 'undefined' ? CrossFetch.Headers : Headers

describe('using class', () => {
test('.setHeader() sets a header that get sent to server', async () => {
Expand All @@ -16,8 +19,6 @@ describe('using class', () => {
describe('.setHeaders() sets headers that get sent to the server', () => {
test('with headers instance', async () => {
const client = new GraphQLClient(ctx.url)
// Headers not defined globally in Node
const H = typeof Headers === 'undefined' ? CrossFetch.Headers : Headers
client.setHeaders(new H({ 'x-foo': 'bar' }))
const mock = ctx.res()
await client.request(`{ me { id } }`)
Expand All @@ -38,4 +39,58 @@ describe('using class', () => {
expect(mock.requests[0].headers['x-foo']).toEqual('bar')
})
})

describe('custom header in the request', () => {
describe.each([
[new H({ 'x-request-foo': 'request-bar' })],
[{ 'x-request-foo': 'request-bar' }],
[[['x-request-foo', 'request-bar']]]
])('request unique header with request', (headerCase: Dom.RequestInit['headers']) => {

test('with request method', async () => {
const client = new GraphQLClient(ctx.url)

client.setHeaders(new H({ 'x-foo': 'bar' }))
const mock = ctx.res()
await client.request(`{ me { id } }`, {}, headerCase)

expect(mock.requests[0].headers['x-foo']).toEqual('bar')
expect(mock.requests[0].headers['x-request-foo']).toEqual('request-bar')
})

test('with rawRequest method', async () => {
const client = new GraphQLClient(ctx.url)

client.setHeaders(new H({ 'x-foo': 'bar' }))
const mock = ctx.res()
await client.rawRequest(`{ me { id } }`, {}, headerCase)

expect(mock.requests[0].headers['x-foo']).toEqual('bar')
expect(mock.requests[0].headers['x-request-foo']).toEqual('request-bar')
})
})

describe.each([
[new H({ 'x-foo': 'request-bar' })],
[{ 'x-foo': 'request-bar' }],
[[['x-foo', 'request-bar']]]
])('request header overriding the client header', (headerCase: Dom.RequestInit['headers']) => {
test('with request method', async () => {
const client = new GraphQLClient(ctx.url)
client.setHeader('x-foo', 'bar')
const mock = ctx.res()
await client.request(`{ me { id } }`, {}, headerCase);
expect(mock.requests[0].headers['x-foo']).toEqual('request-bar')
});

test('with rawRequest method', async () => {
const client = new GraphQLClient(ctx.url)
client.setHeader('x-foo', 'bar')
const mock = ctx.res()
await client.rawRequest(`{ me { id } }`, {}, headerCase);
expect(mock.requests[0].headers['x-foo']).toEqual('request-bar')
});

})
})
})

0 comments on commit 952e972

Please sign in to comment.