Code Monkey home page Code Monkey logo

graphql-http's Introduction

GraphQL

GraphQL Logo

The GraphQL specification is edited in the markdown files found in /spec the latest release of which is published at https://graphql.github.io/graphql-spec/.

The latest draft specification can be found at https://graphql.github.io/graphql-spec/draft/ which tracks the latest commit to the main branch in this repository.

Previous releases of the GraphQL specification can be found at permalinks that match their release tag. For example, https://graphql.github.io/graphql-spec/October2016/. If you are linking directly to the GraphQL specification, it's best to link to a tagged permalink for the particular referenced version.

Overview

This is a Working Draft of the Specification for GraphQL, a query language for APIs created by Facebook.

The target audience for this specification is not the client developer, but those who have, or are actively interested in, building their own GraphQL implementations and tools.

In order to be broadly adopted, GraphQL will have to target a wide variety of backend environments, frameworks, and languages, which will necessitate a collaborative effort across projects and organizations. This specification serves as a point of coordination for this effort.

Looking for help? Find resources from the community.

Getting Started

GraphQL consists of a type system, query language and execution semantics, static validation, and type introspection, each outlined below. To guide you through each of these components, we've written an example designed to illustrate the various pieces of GraphQL.

This example is not comprehensive, but it is designed to quickly introduce the core concepts of GraphQL, to provide some context before diving into the more detailed specification or the GraphQL.js reference implementation.

The premise of the example is that we want to use GraphQL to query for information about characters and locations in the original Star Wars trilogy.

Type System

At the heart of any GraphQL implementation is a description of what types of objects it can return, described in a GraphQL type system and returned in the GraphQL Schema.

For our Star Wars example, the starWarsSchema.ts file in GraphQL.js defines this type system.

The most basic type in the system will be Human, representing characters like Luke, Leia, and Han. All humans in our type system will have a name, so we define the Human type to have a field called "name". This returns a String, and we know that it is not null (since all Humans have a name), so we will define the "name" field to be a non-nullable String. Using a shorthand notation that we will use throughout the spec and documentation, we would describe the human type as:

type Human {
  name: String
}

This shorthand is convenient for describing the basic shape of a type system; the JavaScript implementation is more full-featured, and allows types and fields to be documented. It also sets up the mapping between the type system and the underlying data; for a test case in GraphQL.js, the underlying data is a set of JavaScript objects, but in most cases the backing data will be accessed through some service, and this type system layer will be responsible for mapping from types and fields to that service.

A common pattern in many APIs, and indeed in GraphQL is to give objects an ID that can be used to refetch the object. So let's add that to our Human type. We'll also add a string for their home planet.

type Human {
  id: String
  name: String
  homePlanet: String
}

Since we're talking about the Star Wars trilogy, it would be useful to describe the episodes in which each character appears. To do so, we'll first define an enum, which lists the three episodes in the trilogy:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

Now we want to add a field to Human describing what episodes they were in. This will return a list of Episodes:

type Human {
  id: String
  name: String
  appearsIn: [Episode]
  homePlanet: String
}

Now, let's introduce another type, Droid:

type Droid {
  id: String
  name: String
  appearsIn: [Episode]
  primaryFunction: String
}

Now we have two types! Let's add a way of going between them: humans and droids both have friends. But humans can be friends with both humans and droids. How do we refer to either a human or a droid?

If we look, we note that there's common functionality between humans and droids; they both have IDs, names, and episodes in which they appear. So we'll add an interface, Character, and make both Human and Droid implement it. Once we have that, we can add the friends field, that returns a list of Characters.

Our type system so far is:

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

interface Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
}

type Human implements Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
  homePlanet: String
}

type Droid implements Character {
  id: String
  name: String
  friends: [Character]
  appearsIn: [Episode]
  primaryFunction: String
}

One question we might ask, though, is whether any of those fields can return null. By default, null is a permitted value for any type in GraphQL, since fetching data to fulfill a GraphQL query often requires talking to different services that may or may not be available. However, if the type system can guarantee that a type is never null, then we can mark it as Non Null in the type system. We indicate that in our shorthand by adding an "!" after the type. We can update our type system to note that the id is never null.

Note that while in our current implementation, we can guarantee that more fields are non-null (since our current implementation has hard-coded data), we didn't mark them as non-null. One can imagine we would eventually replace our hardcoded data with a backend service, which might not be perfectly reliable; by leaving these fields as nullable, we allow ourselves the flexibility to eventually return null to indicate a backend error, while also telling the client that the error occurred.

enum Episode {
  NEWHOPE
  EMPIRE
  JEDI
}

interface Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
}

type Human implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  homePlanet: String
}

type Droid implements Character {
  id: String!
  name: String
  friends: [Character]
  appearsIn: [Episode]
  primaryFunction: String
}

We're missing one last piece: an entry point into the type system.

When we define a schema, we define an object type that is the basis for all query operations. The name of this type is Query by convention, and it describes our public, top-level API. Our Query type for this example will look like this:

type Query {
  hero(episode: Episode): Character
  human(id: String!): Human
  droid(id: String!): Droid
}

In this example, there are three top-level operations that can be done on our schema:

  • hero returns the Character who is the hero of the Star Wars trilogy; it takes an optional argument that allows us to fetch the hero of a specific episode instead.
  • human accepts a non-null string as a query argument, a human's ID, and returns the human with that ID.
  • droid does the same for droids.

These fields demonstrate another feature of the type system, the ability for a field to specify arguments that configure their behavior.

When we package the whole type system together, defining the Query type above as our entry point for queries, this creates a GraphQL Schema.

This example just scratched the surface of the type system. The specification goes into more detail about this topic in the "Type System" section, and the type directory in GraphQL.js contains code implementing a specification-compliant GraphQL type system.

Query Syntax

GraphQL queries declaratively describe what data the issuer wishes to fetch from whoever is fulfilling the GraphQL query.

For our Star Wars example, the starWarsQueryTests.js file in the GraphQL.js repository contains a number of queries and responses. That file is a test file that uses the schema discussed above and a set of sample data, located in starWarsData.js. This test file can be run to exercise the reference implementation.

An example query on the above schema would be:

query HeroNameQuery {
  hero {
    name
  }
}

The initial line, query HeroNameQuery, defines a query with the operation name HeroNameQuery that starts with the schema's root query type; in this case, Query. As defined above, Query has a hero field that returns a Character, so we'll query for that. Character then has a name field that returns a String, so we query for that, completing our query. The result of this query would then be:

{
  "hero": {
    "name": "R2-D2"
  }
}

Specifying the query keyword and an operation name is only required when a GraphQL document defines multiple operations. We therefore could have written the previous query with the query shorthand:

{
  hero {
    name
  }
}

Assuming that the backing data for the GraphQL server identified R2-D2 as the hero. The response continues to vary based on the request; if we asked for R2-D2's ID and friends with this query:

query HeroNameAndFriendsQuery {
  hero {
    id
    name
    friends {
      id
      name
    }
  }
}

then we'll get back a response like this:

{
  "hero": {
    "id": "2001",
    "name": "R2-D2",
    "friends": [
      {
        "id": "1000",
        "name": "Luke Skywalker"
      },
      {
        "id": "1002",
        "name": "Han Solo"
      },
      {
        "id": "1003",
        "name": "Leia Organa"
      }
    ]
  }
}

One of the key aspects of GraphQL is its ability to nest queries. In the above query, we asked for R2-D2's friends, but we can ask for more information about each of those objects. So let's construct a query that asks for R2-D2's friends, gets their name and episode appearances, then asks for each of their friends.

query NestedQuery {
  hero {
    name
    friends {
      name
      appearsIn
      friends {
        name
      }
    }
  }
}

which will give us the nested response

{
  "hero": {
    "name": "R2-D2",
    "friends": [
      {
        "name": "Luke Skywalker",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Han Solo" },
          { "name": "Leia Organa" },
          { "name": "C-3PO" },
          { "name": "R2-D2" }
        ]
      },
      {
        "name": "Han Solo",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Luke Skywalker" },
          { "name": "Leia Organa" },
          { "name": "R2-D2" }
        ]
      },
      {
        "name": "Leia Organa",
        "appearsIn": ["NEWHOPE", "EMPIRE", "JEDI"],
        "friends": [
          { "name": "Luke Skywalker" },
          { "name": "Han Solo" },
          { "name": "C-3PO" },
          { "name": "R2-D2" }
        ]
      }
    ]
  }
}

The Query type above defined a way to fetch a human given their ID. We can use it by hard-coding the ID in the query:

query FetchLukeQuery {
  human(id: "1000") {
    name
  }
}

to get

{
  "human": {
    "name": "Luke Skywalker"
  }
}

Alternately, we could have defined the query to have a query parameter:

query FetchSomeIDQuery($someId: String!) {
  human(id: $someId) {
    name
  }
}

This query is now parameterized by $someId; to run it, we must provide that ID. If we ran it with $someId set to "1000", we would get Luke; set to "1002", we would get Han. If we passed an invalid ID here, we would get null back for the human, indicating that no such object exists.

Notice that the key in the response is the name of the field, by default. It is sometimes useful to change this key, for clarity or to avoid key collisions when fetching the same field with different arguments.

We can do that with field aliases, as demonstrated in this query:

query FetchLukeAliased {
  luke: human(id: "1000") {
    name
  }
}

We aliased the result of the human field to the key luke. Now the response is:

{
  "luke": {
    "name": "Luke Skywalker"
  }
}

Notice the key is "luke" and not "human", as it was in our previous example where we did not use the alias.

This is particularly useful if we want to use the same field twice with different arguments, as in the following query:

query FetchLukeAndLeiaAliased {
  luke: human(id: "1000") {
    name
  }
  leia: human(id: "1003") {
    name
  }
}

We aliased the result of the first human field to the key luke, and the second to leia. So the result will be:

{
  "luke": {
    "name": "Luke Skywalker"
  },
  "leia": {
    "name": "Leia Organa"
  }
}

Now imagine we wanted to ask for Luke and Leia's home planets. We could do so with this query:

query DuplicateFields {
  luke: human(id: "1000") {
    name
    homePlanet
  }
  leia: human(id: "1003") {
    name
    homePlanet
  }
}

but we can already see that this could get unwieldy, since we have to add new fields to both parts of the query. Instead, we can extract out the common fields into a fragment, and include the fragment in the query, like this:

query UseFragment {
  luke: human(id: "1000") {
    ...HumanFragment
  }
  leia: human(id: "1003") {
    ...HumanFragment
  }
}

fragment HumanFragment on Human {
  name
  homePlanet
}

Both of those queries give this result:

{
  "luke": {
    "name": "Luke Skywalker",
    "homePlanet": "Tatooine"
  },
  "leia": {
    "name": "Leia Organa",
    "homePlanet": "Alderaan"
  }
}

The UseFragment and DuplicateFields queries will both get the same result, but UseFragment is less verbose; if we wanted to add more fields, we could add it to the common fragment rather than copying it into multiple places.

We defined the type system above, so we know the type of each object in the output; the query can ask for that type using the special field __typename, defined on every object.

query CheckTypeOfR2 {
  hero {
    __typename
    name
  }
}

Since R2-D2 is a droid, this will return

{
  "hero": {
    "__typename": "Droid",
    "name": "R2-D2"
  }
}

This was particularly useful because hero was defined to return a Character, which is an interface; we might want to know what concrete type was actually returned. If we instead asked for the hero of Episode V:

query CheckTypeOfLuke {
  hero(episode: EMPIRE) {
    __typename
    name
  }
}

We would find that it was Luke, who is a Human:

{
  "hero": {
    "__typename": "Human",
    "name": "Luke Skywalker"
  }
}

As with the type system, this example just scratched the surface of the query language. The specification goes into more detail about this topic in the "Language" section, and the language directory in GraphQL.js contains code implementing a specification-compliant GraphQL query language parser and lexer.

Validation

By using the type system, it can be predetermined whether a GraphQL query is valid or not. This allows servers and clients to effectively inform developers when an invalid query has been created, without having to rely on runtime checks.

For our Star Wars example, the file starWarsValidationTests.js contains a number of demonstrations of invalid operations, and is a test file that can be run to exercise the reference implementation's validator.

To start, let's take a complex valid query. This is the NestedQuery example from the above section, but with the duplicated fields factored out into a fragment:

query NestedQueryWithFragment {
  hero {
    ...NameAndAppearances
    friends {
      ...NameAndAppearances
      friends {
        ...NameAndAppearances
      }
    }
  }
}

fragment NameAndAppearances on Character {
  name
  appearsIn
}

And this query is valid. Let's take a look at some invalid queries!

When we query for fields, we have to query for a field that exists on the given type. So as hero returns a Character, we have to query for a field on Character. That type does not have a favoriteSpaceship field, so this query:

# INVALID: favoriteSpaceship does not exist on Character
query HeroSpaceshipQuery {
  hero {
    favoriteSpaceship
  }
}

is invalid.

Whenever we query for a field and it returns something other than a scalar or an enum, we need to specify what data we want to get back from the field. Hero returns a Character, and we've been requesting fields like name and appearsIn on it; if we omit that, the query will not be valid:

# INVALID: hero is not a scalar, so fields are needed
query HeroNoFieldsQuery {
  hero
}

Similarly, if a field is a scalar, it doesn't make sense to query for additional fields on it, and doing so will make the query invalid:

# INVALID: name is a scalar, so fields are not permitted
query HeroFieldsOnScalarQuery {
  hero {
    name {
      firstCharacterOfName
    }
  }
}

Earlier, it was noted that a query can only query for fields on the type in question; when we query for hero which returns a Character, we can only query for fields that exist on Character. What happens if we want to query for R2-D2s primary function, though?

# INVALID: primaryFunction does not exist on Character
query DroidFieldOnCharacter {
  hero {
    name
    primaryFunction
  }
}

That query is invalid, because primaryFunction is not a field on Character. We want some way of indicating that we wish to fetch primaryFunction if the Character is a Droid, and to ignore that field otherwise. We can use the fragments we introduced earlier to do this. By setting up a fragment defined on Droid and including it, we ensure that we only query for primaryFunction where it is defined.

query DroidFieldInFragment {
  hero {
    name
    ...DroidFields
  }
}

fragment DroidFields on Droid {
  primaryFunction
}

This query is valid, but it's a bit verbose; named fragments were valuable above when we used them multiple times, but we're only using this one once. Instead of using a named fragment, we can use an inline fragment; this still allows us to indicate the type we are querying on, but without naming a separate fragment:

query DroidFieldInInlineFragment {
  hero {
    name
    ... on Droid {
      primaryFunction
    }
  }
}

This has just scratched the surface of the validation system; there are a number of validation rules in place to ensure that a GraphQL query is semantically meaningful. The specification goes into more detail about this topic in the "Validation" section, and the validation directory in GraphQL.js contains code implementing a specification-compliant GraphQL validator.

Introspection

It's often useful to ask a GraphQL schema for information about what queries it supports. GraphQL allows us to do so using the introspection system!

For our Star Wars example, the file starWarsIntrospectionTests.js contains a number of queries demonstrating the introspection system, and is a test file that can be run to exercise the reference implementation's introspection system.

We designed the type system, so we know what types are available, but if we didn't, we can ask GraphQL, by querying the __schema field, always available on the root type of a Query. Let's do so now, and ask what types are available.

query IntrospectionTypeQuery {
  __schema {
    types {
      name
    }
  }
}

and we get back:

{
  "__schema": {
    "types": [
      {
        "name": "Query"
      },
      {
        "name": "Character"
      },
      {
        "name": "Human"
      },
      {
        "name": "String"
      },
      {
        "name": "Episode"
      },
      {
        "name": "Droid"
      },
      {
        "name": "__Schema"
      },
      {
        "name": "__Type"
      },
      {
        "name": "__TypeKind"
      },
      {
        "name": "Boolean"
      },
      {
        "name": "__Field"
      },
      {
        "name": "__InputValue"
      },
      {
        "name": "__EnumValue"
      },
      {
        "name": "__Directive"
      }
    ]
  }
}

Wow, that's a lot of types! What are they? Let's group them:

  • Query, Character, Human, Episode, Droid - These are the ones that we defined in our type system.
  • String, Boolean - These are built-in scalars that the type system provided.
  • __Schema, __Type, __TypeKind, __Field, __InputValue, __EnumValue, __Directive - These all are preceded with a double underscore, indicating that they are part of the introspection system.

Now, let's try and figure out a good place to start exploring what queries are available. When we designed our type system, we specified what type all queries would start at; let's ask the introspection system about that!

query IntrospectionQueryTypeQuery {
  __schema {
    queryType {
      name
    }
  }
}

and we get back:

{
  "__schema": {
    "queryType": {
      "name": "Query"
    }
  }
}

And that matches what we said in the type system section, that the Query type is where we will start! Note that the naming here was just by convention; we could have named our Query type anything else, and it still would have been returned here if we had specified it as the starting type for queries. Naming it Query, though, is a useful convention.

It is often useful to examine one specific type. Let's take a look at the Droid type:

query IntrospectionDroidTypeQuery {
  __type(name: "Droid") {
    name
  }
}

and we get back:

{
  "__type": {
    "name": "Droid"
  }
}

What if we want to know more about Droid, though? For example, is it an interface or an object?

query IntrospectionDroidKindQuery {
  __type(name: "Droid") {
    name
    kind
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "kind": "OBJECT"
  }
}

kind returns a __TypeKind enum, one of whose values is OBJECT. If we asked about Character instead:

query IntrospectionCharacterKindQuery {
  __type(name: "Character") {
    name
    kind
  }
}

and we get back:

{
  "__type": {
    "name": "Character",
    "kind": "INTERFACE"
  }
}

We'd find that it is an interface.

It's useful for an object to know what fields are available, so let's ask the introspection system about Droid:

query IntrospectionDroidFieldsQuery {
  __type(name: "Droid") {
    name
    fields {
      name
      type {
        name
        kind
      }
    }
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "fields": [
      {
        "name": "id",
        "type": {
          "name": null,
          "kind": "NON_NULL"
        }
      },
      {
        "name": "name",
        "type": {
          "name": "String",
          "kind": "SCALAR"
        }
      },
      {
        "name": "friends",
        "type": {
          "name": null,
          "kind": "LIST"
        }
      },
      {
        "name": "appearsIn",
        "type": {
          "name": null,
          "kind": "LIST"
        }
      },
      {
        "name": "primaryFunction",
        "type": {
          "name": "String",
          "kind": "SCALAR"
        }
      }
    ]
  }
}

Those are our fields that we defined on Droid!

id looks a bit weird there, it has no name for the type. That's because it's a "wrapper" type of kind NON_NULL. If we queried for ofType on that field's type, we would find the String type there, telling us that this is a non-null String.

Similarly, both friends and appearsIn have no name, since they are the LIST wrapper type. We can query for ofType on those types, which will tell us what these are lists of.

query IntrospectionDroidWrappedFieldsQuery {
  __type(name: "Droid") {
    name
    fields {
      name
      type {
        name
        kind
        ofType {
          name
          kind
        }
      }
    }
  }
}

and we get back:

{
  "__type": {
    "name": "Droid",
    "fields": [
      {
        "name": "id",
        "type": {
          "name": null,
          "kind": "NON_NULL",
          "ofType": {
            "name": "String",
            "kind": "SCALAR"
          }
        }
      },
      {
        "name": "name",
        "type": {
          "name": "String",
          "kind": "SCALAR",
          "ofType": null
        }
      },
      {
        "name": "friends",
        "type": {
          "name": null,
          "kind": "LIST",
          "ofType": {
            "name": "Character",
            "kind": "INTERFACE"
          }
        }
      },
      {
        "name": "appearsIn",
        "type": {
          "name": null,
          "kind": "LIST",
          "ofType": {
            "name": "Episode",
            "kind": "ENUM"
          }
        }
      },
      {
        "name": "primaryFunction",
        "type": {
          "name": "String",
          "kind": "SCALAR",
          "ofType": null
        }
      }
    ]
  }
}

Let's end with a feature of the introspection system particularly useful for tooling; let's ask the system for documentation!

query IntrospectionDroidDescriptionQuery {
  __type(name: "Droid") {
    name
    description
  }
}

yields

{
  "__type": {
    "name": "Droid",
    "description": "A mechanical creature in the Star Wars universe."
  }
}

So we can access the documentation about the type system using introspection, and create documentation browsers, or rich IDE experiences.

This has just scratched the surface of the introspection system; we can query for enum values, what interfaces a type implements, and more. We can even introspect on the introspection system itself. The specification goes into more detail about this topic in the "Introspection" section, and the introspection file in GraphQL.js contains code implementing a specification-compliant GraphQL query introspection system.

Additional Content

This README walked through the GraphQL.js reference implementation's type system, query execution, validation, and introspection systems. There's more in both GraphQL.js and specification, including a description and implementation for executing queries, how to format a response, explaining how a type system maps to an underlying implementation, and how to format a GraphQL response, as well as the grammar for GraphQL.

Contributing to this repo

This repository is managed by EasyCLA. Project participants must sign the free (GraphQL Specification Membership agreement before making a contribution. You only need to do this one time, and it can be signed by individual contributors or their employers.

To initiate the signature process please open a PR against this repo. The EasyCLA bot will block the merge if we still need a membership agreement from you.

You can find detailed information here. If you have issues, please email [email protected].

If your company benefits from GraphQL and you would like to provide essential financial support for the systems and people that power our community, please also consider membership in the GraphQL Foundation.

graphql-http's People

Contributors

acao avatar d-exclaimation avatar enisdenjo avatar github-actions[bot] avatar glasser avatar semantic-release-bot avatar spawnia avatar trevor-scheer avatar xilesun avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-http's Issues

Graphiql:true not working - {"errors":[{"message":"Missing query"}]}

Created new fresh project followed steps from provided documentation and I set graphiql:true and hit localhost:3001/graphql from browser so it's just showing following error instead graphqlUI.

{"errors":[{"message":"Missing query"}]}

`const express = require('express');
const app = express();
const port = 3001;
const { createHandler } = require('graphql-http/lib/use/express');

const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require('graphql');

const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: 'Query',
fields: {
hello: {
type: GraphQLString,
resolve: () => 'world',
},
},
}),
});

app.get('/', (req, res) => {
res.send('Hello World!')
})

// Create a express instance serving all methods on /graphql
app.all('/graphql', createHandler({ schema,graphiql:true }));

app.listen(port, () => {
console.log(Example app listening on port ${port})
})`

Server compliance audit through a website

Create an online website which accepts an URL and performs a GQL over HTTP audit for quick checks without having to install anything.

Would work for local servers too because you can simply provide a localhost address too.

`graphql` seems to have an issue with vite-node

I apologize if this bug is invalid. I tried searching the internet for similar issues but could not find one.

I believe this has something to do with how libraries are bundled in vite-node.

Here is a reproduction

https://stackblitz.com/edit/stackblitz-starters-se6shc

node src/server.js works but running yarn dev fails

If this is a vite-node or graphql issue, let me know.

Many things can be wrong or it all may just be an incompatibility issue. It would be nice if someone could explain what is going on and let others know the solution if they are encountering the same issue.

Deprecate old versions of `graphql-http`

The npm package of graphql-http had a few releases before. Deprecate all of them before releasing v1.

npm deprecate graphql-http@"< 1.0.0" "This package has a new owner and has completely changed as of v1! Please go to https://github.com/enisdenjo/graphql-http for more info."

Audits shouldn't require you to know the URL in order to generate the list of tests

I would like to integrate the audits in my Jest test suite as shown in the README.

I don’t want to have a fixed port for my URL: I want to be able to listen on port 0 and get the URL back and pass that to the tests.

But I don’t want to actually start and listen on a server at the “top level” of a file, because I don't want it to run unless the particular tests are selected (eg, if it.only or the like disables this part of the test file, I don't want to start a server). So listening should go in beforeAll or beforeEach or something.

But you need to give Jest the list of tests before this code runs, and the API here only gives you the list of audit tests once you already have an URL.

It would work better if the url was an argument to fn instead, or if you can pass a function returning an URL or something.

Branching audits

For example, a server MAY support GET requests - if it does, it MUST do this and that.

Additionally something like "watershed readiness" checks; for example, express-graphql should pass the basic tests but won't pass the watershed test because how could it (it's depricated)?

`Module '"graphql-http"' has no exported member '...'` happens with ESM TypeScript

Screenshot
Screenshot 2023-03-31 at 15 12 15

% yarn run -s ts-node-esm test.ts
warning package.json: No license field
/private/tmp/a/node_modules/ts-node/src/index.ts:859
    return new TSError(diagnosticText, diagnosticCodes, diagnostics);
           ^
TSError: ⨯ Unable to compile TypeScript:
test.ts:1:10 - error TS2305: Module '"graphql-http"' has no exported member 'createHandler'.

1 import { createHandler } from 'graphql-http';
           ~~~~~~~~~~~~~

    at createTSError (/private/tmp/a/node_modules/ts-node/src/index.ts:859:12)
    at reportTSError (/private/tmp/a/node_modules/ts-node/src/index.ts:863:19)
    at getOutput (/private/tmp/a/node_modules/ts-node/src/index.ts:1077:36)
    at Object.compile (/private/tmp/a/node_modules/ts-node/src/index.ts:1433:41)
    at transformSource (/private/tmp/a/node_modules/ts-node/src/esm.ts:400:37)
    at /private/tmp/a/node_modules/ts-node/src/esm.ts:278:53
    at async addShortCircuitFlag (/private/tmp/a/node_modules/ts-node/src/esm.ts:409:15)
    at async nextLoad (node:internal/modules/esm/loader:163:22)
    at async ESMLoader.load (node:internal/modules/esm/loader:605:20)
    at async ESMLoader.moduleProvider (node:internal/modules/esm/loader:457:11) {
  diagnosticCodes: [ 2305 ]
}

Expected Behaviour
I expected it to work importing graphql-http module with ESM TypeScript.

Actual Behaviour
but instead it did fail with Module '"graphql-http"' has no exported member '...' error.

Debug Information
test.ts

import { createHandler } from 'graphql-http';

package.json

{
  "type": "module",
  "dependencies": {
    "graphql": "^16.6.0",
    "graphql-http": "^1.17.0"
  },
  "devDependencies": {
    "ts-node": "^10.9.1",
    "typescript": "^5.0.3"
  }
}

tsconfig.json

{
  "compilerOptions": {
    "module": "esnext",
    "moduleResolution": "nodenext",
    "strict": true
  }
}

Further Information

Looking at the type definition of graphql-http in node_modules/graphql-http/lib/index.d.mts, there is no .js extension.

export * from './common';
export * from './handler';
export * from './client';
export * from './audits';

It may works correctly by changing the following code:

export * from './common.js';
export * from './handler.js';
export * from './client.js';
export * from './audits/index.js';

Unable to migrate from express-graphql: loads forever

Screenshot
image
image

Expected Behaviour
Should return

{
    "data": {
        "hello": "world"
    }
}

Actual Behaviour
Check screenshot, loads forever.

Debug Information
I used 2 code snippets: one with express-graphql, the other graphql-http

const express = require("express")
const {createHandler} = require('graphql-http');
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require("graphql");

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: () => 'world',
      },
    },
  }),
});

const app = express()
  app.use(
  "/graphql",
  createHandler({
    schema
  })
)
app.listen(4000)
console.log("Running a GraphQL API server at http://localhost:4000/graphql")
const express = require("express")
const { graphqlHTTP } = require("express-graphql")
const { GraphQLSchema, GraphQLObjectType, GraphQLString } = require("graphql");
const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: () => 'world',
      },
    },
  }),
});

const app = express()
app.use(
  "/graphql",
  graphqlHTTP({
    schema: schema
  })
)
app.listen(4000)
console.log("Running a GraphQL API server at http://localhost:4000/graphql")

Further Information
I'm with node v16.16.0
package.json

{
  "name": "demo",
  "version": "1.0.0",
  "description": "demo",
  "main": "index.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "license": "ISC",
  "dependencies": {
    "express": "^4.18.2",
    "express-graphql": "^0.12.0",
    "graphql-http": "^1.18.0"
  }
}

Support for graphiql

I was looking forward to starting to learn graphql and migrate from rest for my a personal project. Every other tutorial in the realm of NodeJS and Express recommend to use express-graph but it is deprecated and this module is recommend. However, I noticed one feature seems to be missing, graphiql. This isn't a dealbreaker but it would be nice to have this feature in graphql-http.

Add Graphiql

express-graphql had a Graphiql. And that module is now deprecated. I want that feature in this module.

Export parseRequestParams similar to express-graphql

Story

As a _user

I want parseRequestParams function to be exported

So that same implementation can be used outside the package when interacting with graphql-http.
This was the behaviour in express-graphql implementation.

Acceptance criteria

  • _user I can access parseRequestParams from graphql-http package.

v1.5.0 and 1.6.0 not working in module environment

Screenshot
image

Expected Behaviour
I expected it to works :)

Actual Behaviour
but instead it did this.

Debug Information
Just install latest version and used it with vite.

Further Information
Obviously, something is wrong with bunling - in lib folder there is no audits.mjs, so it should be audits/index.mjs in reexport

Allow to configure the rootValue

Story

As a user migrating from express-graphql, I want to pass a rootValue resolver. With express-graphql, I was able to do the following:

app.get('/graphql', graphqlHTTP((req, res) => ({
  schema,
  rootValue: resolvers
})))

Ideally, I would like to set rootValue as an option of createHandler

app.use(
  '/graphql',
  createHandler({ schema,  rootValue }),
)

Acceptance criteria

  • user is able to configure a rootValue without overriding the whole ExecutionContext with onSubscribe
  • user can find documentation on how to configure a rootValue

Note

Maybe using a rootValue is not recommended anymore? Maybe there's an alternative? For reference, here's how I declare my schema/resolvers:

module.exports = {
  /**
   * Create an article.
   */
  articles: async (args, { req }) => {
    // ...
  }
  /**
   * Delete an article.
   */
  deleteArticle: async (args, { req }) => {
    // ...
  },
}
const gql = require('graphql-tag')
const { buildASTSchema } = require('graphql')

module.exports = buildASTSchema(gql`
  type Article {
    _id: ID!
    title: String
    content: String
    createdAt: String
    updatedAt: String
  }

  type RootQuery {
    articles (user: ID): [Article!]!
  }

  type RootMutation {
    deleteArticle(article: ID!): Article!
  }

  schema {
    query: RootQuery
    mutation: RootMutation
  }
`)

Responses from createHandler in express are not returned

I tried to implement the code sample for express in order to migrate away from express-graphql. The express-graphql version works and returns a response, but the http-graphql version does not respond.

Code:

import * as express from 'express';
import { graphqlHTTP } from 'express-graphql';
import { GraphQLObjectType, GraphQLSchema, GraphQLString } from 'graphql';
import { createHandler } from 'graphql-http/lib/use/express';

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: "Query", 
    fields: {hello: {type: GraphQLString, resolve: () => "hello"}}
  })
});
const app = express();

// express-graphql implementation
app.use('/graphql', graphqlHTTP({ schema: schema }));

// graphql-http express implementation
app.all('/graphql2', createHandler({ schema }));
app.listen(4000);

The I use curl:

curl http://localhost:4000/graphql --data-urlencode "query=query Q { hello }"
{"data":{"hello":"hello"}}

curl http://localhost:4000/graphql2 --data-urlencode "query=query Q { hello }"

Expected Behaviour
A response of {"data":{"hello":"hello"}} for both /graphql and /graphql2.

Actual Behaviour
Only /graphql (the express-graphql implementation) returns any response.

Version: 1.17.1
Node version: 16.14.0

Add to the context values the response of the route, because it is usefull for set cookies for example.

Story

As a user or client or server

I want some feature

I want to add to the context values the response of the route, because it is usefull for set cookies for example.

So that some value

I want to add to the context values the response of the route, because it is usefull for set cookies for example.

Acceptance criteria

Something like this:

createHandler({
  schema,
  context: (req, res, params) => ({ req, res, params })
})

Add extensions function to createHandler

Story

As a server, I want to add an extensions callback function similar to what's available in the express-graphql package options
so that I can add additional key-value metadata to each response. This is useful for capturing runtime and metrics for a given request.

Acceptance criteria

  • The graphql-http createHandler function should have an optional extensions function for adding additional metadata to the GraphQL response as a key-value object. The result will be added to the "extensions" field in the resulting JSON. This is often a useful place to add development time metadata such as the runtime of a query or the amount of resources consumed. This may be an async function. The function is given one object as an argument: { document, variables, operationName, result, context }.

`SHOULD not contain the data entry on JSON parsing failure when accepting application/graphql-response+json` also fails on non-JSON responses

The audit SHOULD not contain the data entry on JSON parsing failure when accepting application/graphql-response+json does this:

        assert(
          'Data entry',
          (await assertBodyAsExecutionResult(res)).data,
        ).toBe(undefined);

This will throw if the body is not actually JSON, with the misleading error about "SHOULD not contain the data entry".

First, I'm not actually sure if the spec actually has a SHOULD that JSON parse errors should be returned as JSON at all (as opposed to having opinions about its structure if it is JSON). Apollo Server now expects you to use your web framework's JSON parsing capabilities (eg, body-parser with Express) rather than handling it "in-house" and it is primarily structured as a non-error-handling middleware, so it's challenging for us to respond to invalid JSON with any particular error format. Maybe that's a self-imposed limitation but we're probably not going to change it. But I don't really see anything that says you SHOULD have a JSON response here. There's

When a server receives a well-formed GraphQL-over-HTTP request, it must return a well‐formed GraphQL response.

but in this case there is no well-formed request, so that doesn't apply.

Second, even if this is a legitimate thing to (optionally) test for, I think it should probably be explicitly tested for separately from this test case, and this particular test shouldn't fail claiming there's a data entry where there really isn't one. ie if the response is not JSON at all then I think this test should pass?

Error: The GraphQL schema is not provided (Express)

Screenshot

Expected Behaviour

I followed the recipe from https://github.com/graphql/graphql-http#recipes for migration from express-graphql. When I run the server I would expect it to work as with express-graphql (no errors).

Actual Behaviour

When starting the server I get:

0|www    | Internal error occurred during request handling. Please check your implementation. Error: The GraphQL schema is not provided
0|www    |     at handler (/Users/e/Desktop/projects/my/node_modules/graphql-http/lib/handler.js:202:23)
0|www    |     at processTicksAndRejections (node:internal/process/task_queues:96:5)
0|www    |     at async requestListener (/Users/e/Desktop/projects/my/node_modules/graphql-http/lib/use/express.js:28:34)

Debug Information
Help us debug the bug?

Further Information

Using:

  • graphql-http 1.7.0
  • @graphql-tools/schema 9.0.4 to create my schema (is this not supported?)

Should have audits for accepting `null`s

The spec has a paragraph that states:

Note: Specifying null in JSON (or equivalent values in other formats) as values for optional request parameters is equivalent to not specifying them at all.

The audit suite should test this explicitly.

(I was inspired by the audit suite to be stricter about banning things like variables: 0 and got carried away and also banned variables: null. So the suite should prevent this over-fitting.)

Compatibility with graphql-upload

Hi,

First, thanks for all the work you are doing for these libraries!

Its not much of an issue as it is a question following my transition from express-graphql to graphql-http. Express-graphql used to work with graphql-upload but now it seems to me that the handler is blocking my POST request with an image because the Content-Type is not "application/json".

Is this limitation is expected?

Thanks,

Expected Behaviour
I expect this library to accept multipart/form-data Content-Type.

Actual Behaviour
Right now, the library only accepts "application/json" as Content-Type for POST requests. Otherwise, it throws a 415 Unsupported Media Type

Allow Deno to clean up leaked test resources

Screenshot

SHOULD use 4xx or 5xx status codes on document validation failure when accepting application/graphql-response+json => ./test.ts:9:8
error: AssertionError: Test case is leaking 1 resource:

 - A fetch response body (rid 380) was created during the test, but not consumed during the test. Consume or close the response body `ReadableStream`, e.g `await resp.text()` or `await resp.body.cancel()`.

    at assert (ext:deno_web/00_infra.js:353:11)
    at resourceSanitizer (ext:cli/40_testing.js:417:5)
    at async Object.exitSanitizer [as fn] (ext:cli/40_testing.js:435:7)
    at async runTest (ext:cli/40_testing.js:840:5)
    at async runTests (ext:cli/40_testing.js:1098:20)

Expected Behaviour

It should be possible to use the server audits in Deno.test() in a way that does not leak resources.

Actual Behaviour

As discussed in #63 (comment), the tests leak resources.
I am not sure why, but the fix I added to the PR now does not work (fully) in my project.

Debug Information

The following run shows what happens without any cleanup: https://github.com/nuwave/lighthouse/actions/runs/4488866245/jobs/7893989167.

With a cleanup step, some portion of the tests is fixed (I think the failing ones), but others still leak: https://github.com/nuwave/lighthouse/actions/runs/4488817464/jobs/7893880714.

Further Information

Here is the link to the PR where I try to run the audit in my project: nuwave/lighthouse#2359.

UTF-8 body corruption

Expected Behaviour
One expects the default/fallback behaviour to correctly assemble UTF-8 multibyte characters in the request body.

Actual Behaviour
The default/fallback behaviour incorrectly assembles requests that exceed the input stream chunk size. Multibyte characters that straddle chunks are incorrectly parsed, resulting in replacement characters or arbitrary single byte characters.

Example: https://github.com/graphql/graphql-http/blob/main/src/use/http.ts#L144-L146

Debug Information
Send an HTTP request with a variable assigned to a large number of fx "å" that exceeds the request body stream high-water mark. The value passed to the schema will be intermittently corrupted.

Further Information
It appears as though simply calling req.setEncoding("utf8"); is enough to address the issue.

Response containing errors parsing variables is nested twice

Screenshot
Screenshot 2023-01-18 at 13 28 46

Expected Behaviour
I expected the error response to be structured like this:

{
    "errors": [
        {
            "locations": [
                {
                    "column": 8,
                    "line": 1
                }
            ],
            "message": "Variable \"$input\" got invalid value \"foo\"; Int cannot represent non-integer value: \"foo\""
        }
    ]
}

Actual Behaviour
but instead it was nested twice like so:

{
    "errors": [
        {
            "errors": [
                {
                    "locations": [
                        {
                            "column": 8,
                            "line": 1
                        }
                    ],
                    "message": "Variable \"$input\" got invalid value \"foo\"; Int cannot represent non-integer value: \"foo\""
                }
            ]
        }
    ]
}

Debug Information
Minimal example to reproduce:

Code:

const { GraphQLSchema, GraphQLObjectType, GraphQLInt } = require("graphql");

const express = require("express");
const { createHandler } = require("graphql-http/lib/use/express");

const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: "Query",
    fields: {
      hello: {
        type: GraphQLInt,
        args: {
          input: {
            type: GraphQLInt,
          },
        },
      },
    },
  }),
});

const app = express();
app.all("/graphql", createHandler({ schema }));

app.listen({ port: 4000 });
console.log("Listening to port 4000");

Send a query such as:

query ($input: Int) {
  hello(input: $input)
}

with variables:

{ "input": "foo" }

At first glance after briefly debugging it appears as though the error is here in the makeResponse function.

Further Information
The nesting format shown above under the "Expected Behaviour" section is the response that is returned by other GraphQL server frameworks such as express-graphql and graphql-yoga. Other errors returned by graphql-http are also not nested twice, such as when sending the following query against the example above.

Query

{
  hello(input: "foo")
}

Response:

{
    "errors": [
        {
            "locations": [
                {
                    "column": 16,
                    "line": 1
                }
            ],
            "message": "Int cannot represent non-integer value: \"foo\""
        }
    ]
}

Add graphiql in createHandler method

Story

As a user or client or server

I want some feature

So that some value

Acceptance criteria

  • user or client or server type is able to...
  • user or client or server type can add...
  • user or client or server type can remove...

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.