Making HTTP Requests
How to make HTTP requests with Apollo Connectors
Methods
Within the http
argument of a @connect
directive, you can specify the URL and query parameters for one of the following HTTP methods: GET
, POST
, PUT
, PATCH
, or DELETE
.
1type Query {
2 products: [Product]
3 @connect(
4 http: {
5 GET: "https://myapi.dev/products"
6 }
7 selection: "id"
8 )
9}
10
11type Mutation {
12 createProduct(input: CreateProductInput): CreateProductPayload
13 @connect(
14 http: {
15 POST: "https://myapi.dev/products"
16 body: "$args.input { name }"
17 }
18 selection: "id"
19 )
20
21 updateProduct(input: UpdateProductInput): UpdateProductPayload
22 @connect(
23 http: {
24 PUT: "https://myapi.dev/products/{$args.input.id}"
25 body: "$args.input { name }"
26 }
27 selection: "id"
28 )
29
30 patchProduct(input: PatchProductInput): PatchProductPayload
31 @connect(
32 http: {
33 PATCH: "https://myapi.dev/products/{$args.input.id}"
34 body: "$args.input { name }"
35 }
36 selection: "id"
37 )
38
39 deleteProduct(input: DeleteProductInput): DeleteProductPayload
40 @connect(
41 http: {
42 DELETE: "https://myapi.dev/products/{$args.input.id}"
43 }
44 selection: "id"
45 )
46}
URLs and query parameters
URLs can be dynamic and use values from field arguments, sibling fields, and router configuration. The dynamic parts appear between curly braces ({}
). For example, you can use field arguments as path segments:
1type Query {
2 product(storeId: ID!, productId: ID!): Product
3 @connect(
4 http: {
5 GET: "https://myapi.dev/store/{$args.storeId}/products/{$args.productId}"
6 }
7 selection: "id"
8 )
9}
Path segment values are URL-encoded. If the value contains a /
, it is encoded as %2F
.
You can add query parameters to the URL by appending them with a ?
and separating them with &
. You can use field arguments to create query parameters:
1type Query {
2 products(limit: Int = 10, offset: Int): Product
3 @connect(
4 http: {
5 GET: "https://myapi.dev/products?limit={$args.limit}&offset={$args.offset}"
6 }
7 selection: "id"
8 )
9}
When a value is missing or null
, the result is an empty string. In the previous example, if offset
in {$args.offset}
is not provided, the URI will end with &offset=
. Parameter names still appear in the URI if the parameter value is missing or `null``.
Disambiguating arguments and fields
You can reference both field arguments and sibling fields in the URL, even if they have the same name, via the $args
and $this
variables.
1type Foo {
2 bar: ID
3 baz(bar: String): String
4 @connect(
5 http: { GET: "/foo/{$this.bar}?bar={$args.bar}" }
6 # ^ field ^ argument
7 )
8}
For more information about variables, see the Variables section in the Mapping documentation.
Using baseURL
in @source
When your connector has a related @source
and the connector's URL doesn't start with http(s)
, the connector URL is appended to the baseURL
of the @source
. Query parameters in the baseURL
are also included.
The connector URL below resolves to https://myapi.dev/v1/products?client=router&first=10
.
1extend schema
2 @source(
3 name: "myapi"
4 http: { baseURL: "https://myapi.dev/v1?client=router" }
5 )
6
7type Query {
8 products(first: Int = 10): [Product]
9 @connect(
10 source: "myapi"
11 http: { GET: "/products?first={$args.first}" }
12 selection: "id"
13 )
14}
Headers
You can add headers to your HTTP requests using the headers
argument in the http
configuration. The headers
argument is a list of objects.
You can define values for headers using strings as well as interpolate values from router configuration using the $config
variable.
1type Query {
2 products: [Product]
3 @connect(
4 http: {
5 GET: "https://myapi.dev/products"
6 headers: [
7 { name: "x-api-version", value: "2024-01-01" }
8 { name: "x-api-key", value: "{$config.api_key}" }
9 ]
10 }
11 selection: "id"
12 )
13}
You can also propagate headers from the incoming client request using the from
argument:
1type Query {
2 products: [Product]
3 @connect(
4 http: {
5 GET: "https://myapi.dev/products"
6 headers: [{ name: "Authorization", from: "Authorization" }]
7 }
8 selection: "id"
9 )
10}
Using headers
in @source
When your connector has a related @source
, the connector inherits its headers. When the name
argument is the same, the headers in the @connect
directive take precedence.
The connector below uses the x-api-key
header from the related @source
and overrides the x-api-version
header with a different value.
1extend schema
2 @source(
3 name: "myapi"
4 http: {
5 baseURL: "https://myapi.dev/v1"
6 headers: [
7 { name: "x-api-version", value: "2024-01-01" }
8 { name: "x-api-key", value: "{$config.api_key}" }
9 ]
10 }
11 )
12
13type Query {
14 products: [Product]
15 @connect(
16 source: "myapi"
17 http: {
18 GET: "/products"
19 headers: [{ name: "x-api-version", value: "2023-01-01" }]
20 }
21 selection: "id"
22 )
23}
Content Type
Connectors expect a JSON response body. If your API endpoint doesn't default to a JSON content type, you may need to specify an Accept: application/json
header using the mechanisms described above.
Some APIs may have a different mechanism for specifying the response content type, such as a query parameter or file extension. Ensure that your connector is configured appropriately to request a JSON response.
Note that connectors do not require a Content-Type
response header and interpret any response body as JSON by default.
Guidelines for using APIs with connectors
Connectors make it easy to build a GraphQL API using your existing HTTP/JSON APIs. They're flexible and work with most APIs, especially if they conform to the following guidelines.
JSON-over-HTTP basics
Your endpoints accept requests using these HTTP verbs:
GET
,POST
,PUT
,PATCH
,DELETE
.Your endpoints use path segments and query string parameters for inputs, such as
/users/123
or/users?limit=10
.For
POST
,PUT
, andPATCH
requests with request bodies, your endpoints accept JSON.You endpoints respond with JSON values—typically objects, but any JSON value is allowed.
Your endpoints always return a status code between 200 and 299 for successful requests.
Your endpoints provide a known set of properties. GraphQL doesn't have a
Map
type, so you must define the mapping between JSON properties and GraphQL fields in advance. (You can map an arbitrary map to a scalar field using a custom scalar type.)
Graph-like conventions
You represent your entities across various endpoints using the same identifiers. For example, a
User
is consistently identified by123
across all endpoints.You provide endpoints to fetch an entity by its primary key. For example,
/users/123
returns the user with ID123
.Your endpoints use simple values for foreign keys. For example, a
User
object has acompanyId
field containing the ID of the company it belongs to. (Using a full URL such as{"company": "http://myapi.com/company/234"}
is difficult to work with).When appropriate, your endpoints embed related entities in the response. For example, a
User
object might include acompany
field that contains aCompany
object.
Security
Your endpoints perform their own authentication and authorization checks as necessary. You can add layers of additional security using GraphOS Router's security features.
Your endpoints accept authentication information in request headers. The headers can come directly from the client or be injected by the router.
Your endpoints do not use query parameters for sensitive information. The router will emit full URLs in logs and traces.
When your APIs aren't a good fit for connectors
Apollo Connectors are designed to work with a wide variety of APIs, but there are some cases where they are not a good fit. The most common reason is that you need some business logic to transform or aggregate data from endpoints in order for them to compose cleanly into a unified GraphQL schema.
Fortunately, connectors and Apollo Federation work great together. You can combine GraphQL subgraphs with connectors to add business logic to your subgraph alongside declarative data fetching.
For example, if you have an API that returns a list but doesn't support filtering, you can use a resolver to fetch and filter the results and let the connector handle the details.