Giter Site home page Giter Site logo

graphql / graphql-http Goto Github PK

View Code? Open in Web Editor NEW
300.0 9.0 18.0 9.7 MB

Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.

Home Page: https://graphql-http.com

License: MIT License

JavaScript 3.04% TypeScript 93.70% HTML 3.26%
apollo client express fastify graphql http observables relay server transport the-guild

graphql-http's Introduction


graphql-http

Simple, pluggable, zero-dependency, GraphQL over HTTP spec compliant server, client and audit suite.

Continuous integration graphql-http

Quickly check for compliance? Visit graphql-http.com!

Want a full-featured server? See the servers section!

Need subscriptions? Try graphql-ws or graphql-sse instead!


Getting started

Install

yarn add graphql-http

Create a GraphQL schema

import { GraphQLSchema, GraphQLObjectType, GraphQLString } from 'graphql';

/**
 * Construct a GraphQL schema and define the necessary resolvers.
 *
 * type Query {
 *   hello: String
 * }
 */
const schema = new GraphQLSchema({
  query: new GraphQLObjectType({
    name: 'Query',
    fields: {
      hello: {
        type: GraphQLString,
        resolve: () => 'world',
      },
    },
  }),
});

Start the server

With http
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import { schema } from './previous-step';

// Create the GraphQL over HTTP Node request handler
const handler = createHandler({ schema });

// Create a HTTP server using the listener on `/graphql`
const server = http.createServer((req, res) => {
  if (req.url.startsWith('/graphql')) {
    handler(req, res);
  } else {
    res.writeHead(404).end();
  }
});

server.listen(4000);
console.log('Listening to port 4000');
With http2

Browsers might complain about self-signed SSL/TLS certificates. Help can be found on StackOverflow.

$ openssl req -x509 -newkey rsa:2048 -nodes -sha256 -subj '/CN=localhost' \
  -keyout localhost-privkey.pem -out localhost-cert.pem
import fs from 'fs';
import http2 from 'http2';
import { createHandler } from 'graphql-http/lib/use/http2';
import { schema } from './previous-step';

// Create the GraphQL over HTTP Node request handler
const handler = createHandler({ schema });

// Create a HTTP/2 server using the handler on `/graphql`
const server = http2.createSecureServer(
  {
    key: fs.readFileSync('localhost-privkey.pem'),
    cert: fs.readFileSync('localhost-cert.pem'),
  },
  (req, res) => {
    if (req.url.startsWith('/graphql')) {
      handler(req, res);
    } else {
      res.writeHead(404).end();
    }
  },
);

server.listen(4000);
console.log('Listening to port 4000');
With express
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import { schema } from './previous-step';

// Create an express instance serving all methods on `/graphql`
// where the GraphQL over HTTP express request handler is
const app = express();
app.all('/graphql', createHandler({ schema }));

app.listen({ port: 4000 });
console.log('Listening to port 4000');
With fastify
import Fastify from 'fastify'; // yarn add fastify
import { createHandler } from 'graphql-http/lib/use/fastify';
import { schema } from './previous-step';

// Create a fastify instance serving all methods on `/graphql`
// where the GraphQL over HTTP fastify request handler is
const fastify = Fastify();
fastify.all('/graphql', createHandler({ schema }));

fastify.listen({ port: 4000 });
console.log('Listening to port 4000');
With Koa
import Koa from 'koa'; // yarn add koa
import mount from 'koa-mount'; // yarn add koa-mount
import { createHandler } from 'graphql-http/lib/use/koa';
import { schema } from './previous-step';

const app = new Koa();
app.use(mount('/graphql', createHandler({ schema })));

app.listen({ port: 4000 });
console.log('Listening to port 4000');
import uWS from 'uWebSockets.js'; // yarn add uWebSockets.js@uNetworking/uWebSockets.js#<version>
import { createHandler } from 'graphql-http/lib/use/uWebSockets';
import { schema } from './previous-step';

uWS
  .App()
  .any('/graphql', createHandler({ schema }))
  .listen(4000, () => {
    console.log('Listening to port 4000');
  });
With Deno
import { serve } from 'https://deno.land/[email protected]/http/server.ts';
import { createHandler } from 'https://esm.sh/graphql-http/lib/use/fetch';
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
const handler = createHandler({ schema });

// Start serving on `/graphql` using the handler
await serve(
  (req: Request) => {
    const [path, _search] = req.url.split('?');
    if (path.endsWith('/graphql')) {
      return handler(req);
    } else {
      return new Response(null, { status: 404 });
    }
  },
  {
    port: 4000, // Listening to port 4000
  },
);
With Bun
import { createHandler } from 'graphql-http/lib/use/fetch'; // bun install graphql-http
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
const handler = createHandler({ schema });

// Start serving on `/graphql` using the handler
export default {
  port: 4000, // Listening to port 4000
  fetch(req) {
    const [path, _search] = req.url.split('?');
    if (path.endsWith('/graphql')) {
      return handler(req);
    } else {
      return new Response(null, { status: 404 });
    }
  },
};
import { createHandler } from 'graphql-http/lib/use/@netlify/functions'; // yarn add @netlify/functions
import { schema } from './previous-step';

// Create the GraphQL over HTTP native fetch handler
export const handler = createHandler({ schema });

Use the client

import { createClient } from 'graphql-http';

const client = createClient({
  url: 'http://localhost:4000/graphql',
});

(async () => {
  let cancel = () => {
    /* abort the request if it is in-flight */
  };

  const result = await new Promise((resolve, reject) => {
    let result;
    cancel = client.subscribe(
      {
        query: '{ hello }',
      },
      {
        next: (data) => (result = data),
        error: reject,
        complete: () => resolve(result),
      },
    );
  });

  expect(result).toEqual({ hello: 'world' });
})();

Serve GraphiQL

Thanks to ruru, serving GraphiQL is as easy as running:

npx ruru -SP -p 4001 -e http://localhost:4000/graphql

Open http://localhost:4001 in the browser to use it.

Recipes

🔗 Client usage with Promise
import { ExecutionResult } from 'graphql';
import { createClient, RequestParams } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://hey.there:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

function execute<Data, Extensions>(
  params: RequestParams,
): [request: Promise<ExecutionResult<Data, Extensions>>, cancel: () => void] {
  let cancel!: () => void;
  const request = new Promise<ExecutionResult<Data, Extensions>>(
    (resolve, reject) => {
      let result: ExecutionResult<Data, Extensions>;
      cancel = client.subscribe<Data, Extensions>(params, {
        next: (data) => (result = data),
        error: reject,
        complete: () => resolve(result),
      });
    },
  );
  return [request, cancel];
}

(async () => {
  const [request, cancel] = execute({
    query: '{ hello }',
  });

  // just an example, not a real function
  onUserLeavePage(() => {
    cancel();
  });

  const result = await request;

  expect(result).toBe({ data: { hello: 'world' } });
})();
🔗 Client usage with Observable
import { Observable } from 'relay-runtime';
// or
import { Observable } from '@apollo/client/core';
// or
import { Observable } from 'rxjs';
// or
import Observable from 'zen-observable';
// or any other lib which implements Observables as per the ECMAScript proposal: https://github.com/tc39/proposal-observable
import { createClient } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://graphql.loves:4000/observables',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

const observable = new Observable((observer) =>
  client.subscribe({ query: '{ hello }' }, observer),
);

const subscription = observable.subscribe({
  next: (result) => {
    expect(result).toBe({ data: { hello: 'world' } });
  },
});

// unsubscribe will cancel the request if it is pending
subscription.unsubscribe();
🔗 Client usage with Relay
import { GraphQLError } from 'graphql';
import {
  Network,
  Observable,
  RequestParameters,
  Variables,
} from 'relay-runtime';
import { createClient } from 'graphql-http';
import { getSession } from './my-auth';

const client = createClient({
  url: 'http://i.love:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});

function fetch(operation: RequestParameters, variables: Variables) {
  return Observable.create((sink) => {
    if (!operation.text) {
      return sink.error(new Error('Operation text cannot be empty'));
    }
    return client.subscribe(
      {
        operationName: operation.name,
        query: operation.text,
        variables,
      },
      sink,
    );
  });
}

export const network = Network.create(fetch);
🔗 Client usage with Apollo
import {
  ApolloLink,
  Operation,
  FetchResult,
  Observable,
} from '@apollo/client/core';
import { print, GraphQLError } from 'graphql';
import { createClient, ClientOptions, Client } from 'graphql-http';
import { getSession } from './my-auth';

class HTTPLink extends ApolloLink {
  private client: Client;

  constructor(options: ClientOptions) {
    super();
    this.client = createClient(options);
  }

  public request(operation: Operation): Observable<FetchResult> {
    return new Observable((sink) => {
      return this.client.subscribe<FetchResult>(
        { ...operation, query: print(operation.query) },
        {
          next: sink.next.bind(sink),
          complete: sink.complete.bind(sink),
          error: sink.error.bind(sink),
        },
      );
    });
  }
}

const link = new HTTPLink({
  url: 'http://where.is:4000/graphql',
  headers: async () => {
    const session = await getSession();
    if (session) {
      return {
        Authorization: `Bearer ${session.token}`,
      };
    }
  },
});
🔗 Client usage with request retries
import { createClient, NetworkError } from 'graphql-http';

const client = createClient({
  url: 'http://unstable.service:4000/graphql',
  shouldRetry: async (err: NetworkError, retries: number) => {
    if (retries > 3) {
      // max 3 retries and then report service down
      return false;
    }

    // try again when service unavailable, could be temporary
    if (err.response?.status === 503) {
      // wait one second (you can alternatively time the promise resolution to your preference)
      await new Promise((resolve) => setTimeout(resolve, 1000));
      return true;
    }

    // otherwise report error immediately
    return false;
  },
});
🔗 Client usage in browser
<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>GraphQL over HTTP</title>
    <script
      type="text/javascript"
      src="https://unpkg.com/graphql-http/umd/graphql-http.min.js"
    ></script>
  </head>
  <body>
    <script type="text/javascript">
      const client = graphqlHttp.createClient({
        url: 'http://umdfor.the:4000/win/graphql',
      });

      // consider other recipes for usage inspiration
    </script>
  </body>
</html>
🔗 Client usage in Node
const fetch = require('node-fetch'); // yarn add node-fetch
const { AbortController } = require('node-abort-controller'); // (node < v15) yarn add node-abort-controller
const { createClient } = require('graphql-http');

const client = createClient({
  url: 'http://no.browser:4000/graphql',
  fetchFn: fetch,
  abortControllerImpl: AbortController, // node < v15
});

// consider other recipes for usage inspiration
🔗 Client usage in Deno
import { createClient } from 'https://esm.sh/graphql-http';

const client = createClient({
  url: 'http://deno.earth:4000/graphql',
});

// consider other recipes for usage inspiration
🔗 Client usage in Bun
import { createClient } from 'graphql-http'; // bun install graphql-http

const client = createClient({
  url: 'http://bun.bread:4000/graphql',
});

// consider other recipes for usage inspiration
🔗 Server handler migration from express-graphql
import express from 'express';
import { schema } from './my-graphql-schema';

-import { graphqlHTTP } from 'express-graphql';
+import { createHandler } from 'graphql-http/lib/use/express';

const app = express();

app.use(
  '/graphql',
-  graphqlHTTP({ schema }),
+  createHandler({ schema }),
);

app.listen(4000);
🔗 Server handler usage with authentication

Authenticate the user within graphql-http during GraphQL execution context assembly. This is a approach is less safe compared to early authentication (see early authentication in Node) because some GraphQL preparations or operations are executed even if the user is not unauthorized.

import { createHandler } from 'graphql-http';
import {
  schema,
  getUserFromCookies,
  getUserFromAuthorizationHeader,
} from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req) => {
    // process token, authenticate user and attach it to your graphql context
    const userId = await getUserFromCookies(req.headers.cookie);
    // or
    const userId = await getUserFromAuthorizationHeader(
      req.headers.authorization,
    );

    // respond with 401 if the user was not authenticated
    if (!userId) {
      return [null, { status: 401, statusText: 'Unauthorized' }];
    }

    // otherwise attach the user to the graphql context
    return { userId };
  },
});
🔗 Server handler usage with custom context value
import { createHandler } from 'graphql-http';
import { schema, getDynamicContext } from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req, args) => {
    return getDynamicContext(req, args);
  },
  // or static context by supplying the value directly
});
🔗 Server handler usage with custom execution arguments
import { parse } from 'graphql';
import { createHandler } from 'graphql-http';
import { getSchemaForRequest, myValidationRules } from './my-graphql';

const handler = createHandler({
  onSubscribe: async (req, params) => {
    const schema = await getSchemaForRequest(req);

    const args = {
      schema,
      operationName: params.operationName,
      document: parse(params.query),
      variableValues: params.variables,
    };

    return args;
  },
});
🔗 Server handler usage in Node with early authentication (recommended)

Authenticate the user early, before reaching graphql-http. This is the recommended approach because no GraphQL preparations or operations are executed if the user is not authorized.

import { createHandler } from 'graphql-http';
import {
  schema,
  getUserFromCookies,
  getUserFromAuthorizationHeader,
} from './my-graphql';

const handler = createHandler({
  schema,
  context: async (req) => {
    // user is authenticated early (see below), simply attach it to the graphql context
    return { userId: req.raw.userId };
  },
});

const server = http.createServer(async (req, res) => {
  if (!req.url.startsWith('/graphql')) {
    return res.writeHead(404).end();
  }

  try {
    // process token, authenticate user and attach it to the request
    req.userId = await getUserFromCookies(req.headers.cookie);
    // or
    req.userId = await getUserFromAuthorizationHeader(
      req.headers.authorization,
    );

    // respond with 401 if the user was not authenticated
    if (!req.userId) {
      return res.writeHead(401, 'Unauthorized').end();
    }

    const [body, init] = await handler({
      url: req.url,
      method: req.method,
      headers: req.headers,
      body: () =>
        new Promise((resolve) => {
          let body = '';
          req.on('data', (chunk) => (body += chunk));
          req.on('end', () => resolve(body));
        }),
      raw: req,
    });
    res.writeHead(init.status, init.statusText, init.headers).end(body);
  } catch (err) {
    res.writeHead(500).end(err.message);
  }
});

server.listen(4000);
console.log('Listening to port 4000');
🔗 Server handler usage with graphql-upload and http
import http from 'http';
import { createHandler } from 'graphql-http/lib/use/http';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';

const handler = createHandler({
  schema,
  async parseRequestParams(req) {
    const params = await processRequest(req.raw, req.context.res);
    if (Array.isArray(params)) {
      throw new Error('Batching is not supported');
    }
    return {
      ...params,
      // variables must be an object as per the GraphQL over HTTP spec
      variables: Object(params.variables),
    };
  },
});

const server = http.createServer((req, res) => {
  if (req.url.startsWith('/graphql')) {
    handler(req, res);
  } else {
    res.writeHead(404).end();
  }
});

server.listen(4000);
console.log('Listening to port 4000');
🔗 Server handler usage with graphql-upload and express
import express from 'express'; // yarn add express
import { createHandler } from 'graphql-http/lib/use/express';
import processRequest from 'graphql-upload/processRequest.mjs'; // yarn add graphql-upload
import { schema } from './my-graphql';

const app = express();
app.all(
  '/graphql',
  createHandler({
    schema,
    async parseRequestParams(req) {
      const params = await processRequest(req.raw, req.context.res);
      if (Array.isArray(params)) {
        throw new Error('Batching is not supported');
      }
      return {
        ...params,
        // variables must be an object as per the GraphQL over HTTP spec
        variables: Object(params.variables),
      };
    },
  }),
);

app.listen({ port: 4000 });
console.log('Listening to port 4000');
🔗 Audit for servers usage in Jest environment
import { fetch } from '@whatwg-node/fetch';
import { serverAudits } from 'graphql-http';

for (const audit of serverAudits({
  url: 'http://localhost:4000/graphql',
  fetchFn: fetch,
})) {
  test(audit.name, async () => {
    const result = await audit.fn();
    if (result.status === 'error') {
      throw result.reason;
    }
    if (result.status === 'warn') {
      console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements)
    }
    // result.status === 'ok'
  });
}
🔗 Audit for servers usage in Deno environment
import { serverAudits } from 'npm:graphql-http';

for (const audit of serverAudits({
  url: 'http://localhost:4000/graphql',
  fetchFn: fetch,
})) {
  Deno.test(audit.name, async () => {
    const result = await audit.fn();
    if (result.status === 'error') {
      throw result.reason;
    }
    if (result.status === 'warn') {
      console.warn(result.reason); // or throw if you want full compliance (warnings are not requirements)
    }
    // Avoid leaking resources
    if ('body' in result && result.body instanceof ReadableStream) {
      await result.body.cancel();
    }
  });
}

Put the above contents in a file and run it with deno test --allow-net.

This is the official GraphQL over HTTP spec reference implementation and as such follows the specification strictly without any additional features (like playgrounds or GUIs, file uploads, @stream/@defer directives and subscriptions).

Having said this, graphql-http is mostly aimed for library authors and simple server setups, where the requirements are exact to what the aforementioned spec offers.

If you want a feature-full server with bleeding edge technologies, you're recommended to use one of the following servers.

Their compliance with the GraphQL over HTTP spec is checked automatically and updated regularly.

Name Audit
apollo-server ✅ Compliant
deno ✅ Compliant
graph-client ✅ Compliant
graphql-helix ✅ Compliant
graphql-yoga ✅ Compliant
hotchocolate ✅ Compliant
lighthouse ✅ Compliant
pioneer ✅ Compliant
postgraphile ✅ Compliant

Check the docs folder out for TypeDoc generated documentation.

Inspect audits of other implementations in the implementations folder. Adding your implementation is very welcome, see how!

Want to help?

File a bug, contribute with code, or improve documentation? Read more in CONTRIBUTING.md.

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

2013xile avatar acao avatar d-exclaimation avatar enisdenjo avatar github-actions[bot] avatar glasser avatar h3ssan avatar semantic-release-bot avatar spawnia avatar trevor-scheer 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

graphql-http's Issues

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...

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

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})
})`

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\""
        }
    ]
}

`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.

Add Graphiql

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

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
  }
`)

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 }.

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.

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)?

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"
  }
}

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 })
})

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."

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.)

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.

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.

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.

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

`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';

Change schema at runtime ?

Hi would like to request or if it existing please add to documentation

Please add the option to change schema at runtime ,

in python and flask with ariadne this is possible ,

should have different schema per URL , yes i know i can define the schema and the URL before the express server runs but the thing is , everytime i have to add new schema i need to define another URL with its own createHandler option ,

i just want to add a folder directory then inside off every folder created there is api.js that defines the rootValue methods , and its own schema , the goal is to everytime we have another API we will add new folder and an api.js file and it will be accessible via url http://mydomain.com/partner/code , this code is dynamic so i only need to use this express js routing app.use('/partner/:code') to make my code accessible via request.params and this code will be equal to the partner folder where api and schemas reside

thank you

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?)

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

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.

`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?

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.