GraphQL client and handler compliant with GraphQL over HTTP specification
- GraphQL over HTTP Spec compliant
application/graphql+jsonsupport- Lean interface, tiny using std and graphql public libraries
- Universal
A simple example of creating a GraphQL server and GraphQL client.
server:
import {
createHandler,
useGraphQLPlayground,
} from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
import { serve, Status } from "https://deno.land/std@$VERSION/http/mod.ts";
import { buildSchema } from "https://esm.sh/graphql@$VERSION";
const schema = buildSchema(`type Query {
hello: String!
}`);
let handler = createHandler(schema, {
rootValue: {
hello: "world",
},
});
handler = useGraphQLPlayground(handler);
serve((req) => {
const { pathname } = new URL(req.url);
if (pathname === "/graphql") {
return handler(req);
}
return new Response("Not Found", {
status: Status.NotFound,
});
});
// Listening on <BASE_URL>client:
import { gqlFetch } from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
const { data, errors, extensions } = await gqlFetch({
url: `<BASE_URL>/graphql`,
query: `query { hello }`,
});or you can access <BASE_URL>/graphql in your browser and use
graphql-playground.
This project is implemented in accordance with GraphQL over HTTP Spec.
We are actively implementing
IETF RFC 2119 SHOULD and
RECOMMENDED.
A GraphQL-over-HTTP request is formed of the following parameters:
| Name | Required | Description |
|---|---|---|
| query | ✅ | A Document containing GraphQL Operations and Fragments to execute. Must be a string. |
| operationName | - | The name of the Operation in the Document to execute. GET: If present, must be a string. |
| variables | - | Values for any Variables defined by the Operation. GET: If present, must be represented as a URL-encoded JSON string. |
The following responses may be returned.
| Status | Condition |
|---|---|
| 200 | If GraphQL is actually executed, even if it contains Field errors. |
| 400 | A required parameter does not exist. Illegal format of parameter. |
| 405 | When a mutation or subscription operation is requested on GET request. |
| 406 | The client Accept HTTP header does not contain at least one of the supported media types. |
| 415 | The client Content-type HTTP header does not contain at least one of the supported media types. |
| 500 | If the server encounters an unexpected error. |
Below are the specific conditions under which each may occur. Errors are classified into four major categories.
- HTTP Request errors
- (GraphQL) Request errors
- (GraphQL) Field errors
- Unknown errors
HTTP Request errors are errors in the http protocol. This refers to errors that can occur between HTTP and GraphQL, such as missing/incorrect values for required parameters, missing/incorrect values for required headers, etc.
Since these are client-side errors, the appropriate 4XX status is returned.
Request errors are defined at [GraphQL Spec] 7.Response - Request errors.
Note here that the status code may be different depending on the Content-Type.
If Content-Type is application/json, all results of GraphQL Requests including
GraphQL validation will be treated as 200 status.
See the [GraphQL over HTTP 6.4.1] application/json#Note for the reason for this.
If Content-Type is application/graphql+json, it is possible to respond with a
status code other than 200 depending on the result of the GraphQL Request.
If the GraphQL request is invalid (e.g. it is malformed, or does not pass validation) then the response with 400 status code.
Field errors are defined at [GraphQL Spec 7.Response] - Field errors.
Even if a Field error occurs, it will always be a 200 status code.
If an error other than the above occurs on the server side, a 500 status code
will be responded.
As you may have noticed, application/graphql+json represents a more accurate
semantics response.
If you want application/graphql+json content, you must put
application/graphql+json as a higher priority than application/json in the
Accept header.
Example: Accept: application/graphql+json,application/json.
Response status
| application/graphql+json | application/json | |
|---|---|---|
| HTTP Request error | 4XX(eg.406, 415) | 4XX |
| GraphQL request error | 400 | 200 |
| GraphQL field error | 200 | 200 |
| Unknown(Internal server) error | 5XX | 5XX |
Unfortunately, there is currently no specification for subscription and it is
not implemented.
You can refer to other projects' implementations using SSE or Websocket.
Create HTTP handler what handle GraphQL over HTTP request.
import { createHandler } from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
import { buildSchema } from "https://esm.sh/graphql@$VERSION";
const schema = buildSchema(`type Query {
hello: String!
}`);
const handler = createHandler(schema, {
rootValue: {
hello: "world",
},
});
const req = new Request("<ENDPOINT>");
const res = await handler(req);| N | Name | Required / Default | Description |
|---|---|---|---|
| 1 | schema | ✅ | GraphQLSchemaThe GraphQL type system to use when validating and executing a query. |
| 2 | options | - | handler options |
| source | - | Source | stringA GraphQL language formatted string representing the requested operation. |
|
| rootValue | - | unknownThe value provided as the first argument to resolver functions on the top level type (e.g. the query object type). |
|
| contextValue | - | unknownThe context value is provided as an argument to resolver functions after field arguments. It is used to pass shared information useful at any point during executing this query, for example the currently logged in user and connections to databases or other services. |
|
| variableValues | - | <{ readonly [variable: string: unknown; }> | null A mapping of variable name to runtime value to use for all variables defined in the requestString. |
|
| operationName | - | string | nullThe name of the operation to use if requestString contains multiple possible operations. Can be omitted if requestString contains only one operation. |
|
| fieldResolver | - | GraphQLFieldResolver<any, any> | nullA resolver function to use when one is not provided by the schema. If not provided, the default field resolver is used (which looks for a value or method on the source value with the field's name). |
|
| typeResolver | - | GraphQLTypeResolver<any, any> | nullA type resolver function to use when none is provided by the schema. If not provided, the default type resolver is used (which looks for a __typename field or alternatively calls the isTypeOf method). |
(req: Request) => Promise<Response>
AggregateError- When graphql schema validation is fail.
GraphQL client with HTTP.
import { gqlFetch } from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
const { data, errors, extensions } = await gqlFetch({
url: `<graphql-endpoint>`,
query: `query Greet(name: $name) {
hello(name: $name)
}`,
}, {
variables: {
name: "Bob",
},
operationName: "Greet",
method: "GET",
});T extends jsonObject-datafield type
| N | Name | Required / Default | Description |
|---|---|---|---|
| 1 | params | ✅ | Parameters |
| url | ✅ | string | URLGraphQL URL endpoint. |
|
| query | ✅ | stringGraphQL query |
|
| 2 | options | - | Options |
| variables | - | jsonObjectGraphQL variables. |
|
| operationName | - | stringGraphQL operation name. |
|
| method | "POST" |
"GET" | "POST" | ({} & string)HTTP Request method. According to the GraphQL over HTTP Spec, all GraphQL servers accept POST requests. |
|
| 3 | requestInit | - | RequestInitRequest init for customize HTTP request. |
type json =
| string
| number
| boolean
| null
| { [k: string]: json }
| json[];
type jsonObject = {
[k: string]: json;
};Promise<Result<T>>
ErrorTypeErrorSyntaxErrorDOMExceptionAggregateError
Use GraphQL Playground as handler.
import {
createHandler,
useGraphQLPlayground,
} from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
import { buildSchema } from "https://esm.sh/graphql@$VERSION";
const schema = buildSchema(`type Query {
hello: String!
}`);
let handler = createHandler(schema, {
rootValue: {
hello: "world",
},
});
handler = useGraphQLPlayground(handler);
const req = new Request("<ENDPOINT>");
const res = await handler(req);| N | Name | Required / Default | Description |
|---|---|---|---|
| 1 | handler | ✅ | (req: Request) => Promise<Response> | ResponseThe handler for individual HTTP requests. |
| 2 | options | - | RenderPageOptionsThe graphql-playground options. |
| endpoint | "/graphql" |
stringThe GraphQL endpoint url. |
|
| subscriptionEndpoint | - | stringThe GraphQL subscriptions endpoint url. |
|
| workspaceName | - | stringin case you provide a GraphQL Config, you can name your workspace here. |
|
| env | - | any |
|
| config | - | anyThe JSON of a GraphQL Config. |
|
| settings | - | ISettingsEditor settings in json format. |
|
| schema | - | IntrospectionResultThe result of an introspection query (an object of this form: {__schema: {...}}) The playground automatically fetches the schema from the endpoint. This is only needed when you want to override the schema. |
|
| tabs | - | Tab[]An array of tabs to inject. |
|
| codeTheme | - | EditorColoursCustomize your color theme. |
|
| version | - | string |
|
| cdnUrl | - | string |
|
| title | - | string |
|
| faviconUrl | - | string | null |
interface ISettings {
"editor.cursorShape": "line" | "block" | "underline";
"editor.fontFamily": string;
"editor.fontSize": number;
"editor.reuseHeaders": boolean;
"editor.theme": "dark" | "light";
"general.betaUpdates": boolean;
"prettier.printWidth": number;
"prettier.tabWidth": number;
"prettier.useTabs": boolean;
"request.credentials": "omit" | "include" | "same-origin";
"request.globalHeaders": { [key: string]: string };
"schema.polling.enable": boolean;
"schema.polling.endpointFilter": string;
"schema.polling.interval": number;
"schema.disableComments": boolean;
"tracing.hideTracingResponse": boolean;
"tracing.tracingSupported": boolean;
}
interface Tab {
endpoint: string;
query: string;
name?: string;
variables?: string;
responses?: string[];
headers?: {
[key: string]: string;
};
}
interface EditorColours {
property: string;
comment: string;
punctuation: string;
keyword: string;
def: string;
qualifier: string;
attribute: string;
number: string;
string: string;
builtin: string;
string2: string;
variable: string;
meta: string;
atom: string;
ws: string;
selection: string;
cursorColor: string;
editorBackground: string;
resultBackground: string;
leftDrawerBackground: string;
rightDrawerBackground: string;
}(req: Request) => Promise<Response> | Response
Create GraphQL Request object.
import { createRequest } from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
const [request, err] = createRequest({
url: "<graphql-endpoint>",
query: `query Greet(name: $name) {
hello(name: $name)
}`,
method: "GET",
});
if (!err) {
const res = await fetch(request);
}T extends jsonObject
| N | Name | Required / Default | Description |
|---|---|---|---|
| 1 | params | ✅ | Parameters |
| url | ✅ | string | URLGraphQL URL endpoint. |
|
| query | ✅ | stringGraphQL query |
|
| method | ✅ | "GET" | "POST" | ({} & string)HTTP Request method. According to the GraphQL over HTTP Spec, all GraphQL servers accept POST requests. |
|
| 2 | options | - | Options |
| variables | - | jsonObjectGraphQL variables. |
|
| operationName | - | stringGraphQL operation name. |
[data: Request, error: undefined] | [data: undefined, error: TypeError]
Resolve GraphQL over HTTP request, take out GraphQL parameters safety.
import {
resolveRequest,
} from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
const req = new Request("<graphql-endpoint>"); // any Request
const [data, err] = await resolveRequest(req);
if (data) {
const { query, variableValues, operationName, extensions } = data;
}| Name | Required | Description |
|---|---|---|
| req | ✅ | RequestRequest object |
Promise<RequestResult> | RequestResult
RequestResult:
| N | Name | Description |
|---|---|---|
| 1 | data | Bellow records | undefinedGraphQL parameters. |
| query | stringA Document containing GraphQL Operations and Fragments to execute. |
|
| variableValues | Record<string, json> | nullValues for any Variables defined by the Operation. |
|
| operationName | string | nullThe name of the Operation in the Document to execute. |
|
| extensions | Record<string, json> | nullReserved for implementors to extend the protocol however they see fit. |
|
| 2 | error | HttpError | undefinedThe base class that all derivative HTTP extend, providing a status and an expose property. |
No error is thrown and reject is never called.
Create a GraphQL over HTTP compliant Response object.
import {
createResponse,
} from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
import { buildSchema } from "https://esm.sh/graphql@$VERSION";
const schema = buildSchema(`query {
hello: String!
}`);
const res = createResponse({
schema,
source: `query { hello }`,
method: "POST",
}, {
rootValue: {
hello: "world",
},
});| N | Name | Required / Default | Description |
|---|---|---|---|
| 1 | params | ✅ | Parameters. |
| schema | ✅ | GraphQLSchema |
|
| method | ✅ | GET | POST |
|
| 2 | options | - | options. |
| operationName | - | string | null |
|
| variableValues | - | { readonly [variable: string]: unknown } | null |
|
| contextValue | - | unknown |
|
| rootValue | - | unknown |
|
| fieldResolver | - | GraphQLFieldResolver<any, any> | null |
|
| typeResolver | - | GraphQLTypeResolver<any, any> | null |
|
| mimeType | "application/graphql+json" |
"application/graphql+json"| application/json |
Response
Resolve GraphQL over HTTP response safety.
import {
resolveResponse,
} from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
const res = new Response(); // any Response
const { data, errors, extensions } = await resolveResponse(res);| Name | Required | Description |
|---|---|---|
| res | ✅ | ResponseResponse object |
Promise<Result<T>>
import { GraphQLError } from "https://esm.sh/graphql@$VERSION";
import { json } from "https://deno.land/x/pure_json@$VERSION/mod.ts";
type PickBy<T, K> = {
[k in keyof T as (K extends T[k] ? k : never)]: T[k];
};
type SerializedGraphQLError = PickBy<GraphQLError, json | undefined>;
type Result<
T extends Record<string, unknown> = Record<string, unknown>,
> = {
data?: T;
errors?: SerializedGraphQLError[];
extensions?: unknown;
};ErrorAggregateErrorSyntaxErrorTypeError
Compress GraphQL query.
import { gql } from "https://deno.land/x/graphql_http@$VERSION/mod.ts";
import { assertEquals } from "https://deno.land/std@$VERSION/testing/asserts.ts";
const query = gql`query Test {
hello
}`;
assertEquals(query, "query Test{hello}");| Name | Required | Description |
|---|---|---|
| query | ✅ | TemplateStringsArrayGraphql query. |
string
Copyright © 2022-present TomokiMiyauci.
Released under the MIT license