Skip to content

Execute GraphQL queries without http server #936

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
kristoferma opened this issue May 27, 2020 · 8 comments
Open

Execute GraphQL queries without http server #936

kristoferma opened this issue May 27, 2020 · 8 comments
Labels
scope/schema Related to the schema component type/feat Add a new capability or enhance an existing one

Comments

@kristoferma
Copy link

Perceived Problem

Next.js discourages accessing API Routes in server-side code like getStaticProps and getServerSideProps. The encourage users to Write server-side code directly.

Ideas / Proposed Solution(s)

Nexus framework could allow developers to execute graphQL queries directly, without going through the http server.

Some discussion happened here: #782
Working preliminary solution: #921

@kristoferma kristoferma added the type/feat Add a new capability or enhance an existing one label May 27, 2020
@jasonkuhrt
Copy link
Member

Thanks @kristoferma

How would you handle resolvers that depend on data from the request object?

Two options I could see (not saying I like these):

  1. Caller just doesn't send operations against those resolvers
  2. Have caller supply simulated request object data

@jasonkuhrt jasonkuhrt added the scope/schema Related to the schema component label May 28, 2020
@kristoferma
Copy link
Author

Thank you @jasonkuhrt for helping me with this issue.

So in the case of Next.js you can access the request object in getServerSideProps and I can imagine it being useful to pass that on to nexus.

So my first thought is to have an optional request object parameter with a default value of the equivalent of an empty request object.

@jasonkuhrt
Copy link
Member

jasonkuhrt commented May 28, 2020

To recap, from graphql-nexus/nexus#782 (comment), the primary motivation for this issue as I understand it is:

but it adds an extra step that could be bypassed by executing the graphql query from the GetServerSideProps

One thing I don't fully understand is, what's the fundamental limitation of using server.handlers.graphql, if we're in a case where the request object is already available and will be passed? Is is just the nuisance of needing a res object?

@kristoferma
Copy link
Author

The limitation that I ran into is that the res object manages the GraphQL response. I want to capture the GraphQL response put it into the Relay Store and pass the Relay Store as props to my Next.js app.

Should I make an example of my use case? I feel like we might not be on the same page here.

@kristoferma
Copy link
Author

This is the most basic example of getServerSideProps.

function Page({ data }) {
  // Render data...
}

export async function getServerSideProps() {
  // Fetch data from external API
  const res = await fetch(`https://.../data`)
  const data = await res.json()

  // Pass data to the page via props
  return { props: { data } }
}

export default Page

I can do this with nexus

function Page({ data }) {
  // Render data...
}

export async function getServerSideProps() {
  const query = graphql`...`;
  const variables = { ... };
  // Fetch data from Next.js API Routes
  const response = await fetch("https://example.com/api/graphql", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
    },
    body: JSON.stringify({
      query,
      variables,
    }),
  });

  // Get the response as JSON
  const json = await response.json();

  // Pass data to the page via props
  return { props: { data: json } };
}

export default Page

But I want to do this

function Page({ data }) {
  // Render data...
}

export async function getServerSideProps() {
  const query = graphql`...`;
  const variables = { ... };

  // Fetch directly from nexus
  const response = await app.schema.exec(query, variables)

  // Pass data to the page via props
  return { props: { data: JSON.stringify(response) } };
}

export default Page

By doing this I get rid of the extra network call from the server side rendering to the graphql endpoint.

@jasonkuhrt
Copy link
Member

Thanks @kristoferma, we're on the same page about that. What I'm saying is that there is also is third way:

export async function getServerSideProps() {
  const query = graphql`...`;
  const variables = { ... };

  // Fetch directly from nexus
  const req = ... // get this from nextjs
  const res = ... // mock this... /shrug
  const response = await app.server.handlers.graphql(req, res)

  // Pass data to the page via props
  return { props: { data: res.body } };
}

I'm not saying that this is good. But it seems to me that it is already possible to avoid the network call.

To be clear, I like the idea of direct schema execution access.

Between these two directions:

  1. Introduce the concept of schema execution sans request object
  2. Allow direct schema execution with req object passed

I don't like (2) at all because the abstraction appears odd, its half decoupled from server (no res) but half not (wants req).

I like (1) because it feels distinct, clearly an abstraction level lower than the server handlers.

I'm thinking right now of an API like app.schema.exec(query, variables?, context?). The context would rely on typegen. If any context contributions are present in the app (sources can be: plugins, addToContext) then user would need to re-supply it themselves.

But, I could see that being really hard/painful in an app with complex context contributors setup.

In such cases, we could help, if the user has access to the req object, which in complex scenarios they ought to, so that we can enable them to build the context themselves like Nexus does internally.

const context = await app.schema.makeContext(req) // assumes user has access to req
app.schema.exec(query, vars, context)

I think this is reasonable:

  1. In simple cases just create the context object yourself, maybe based on the req object but really it is up to you
  2. In complex cases, leverage the context maker, requiring though the req object

Just a sketch of thoughts... still need to think more about it.

One thing I'm weary about, generally, is expanding the API surface that 90% of users see with things that only 10% of users use. Not against, just requires extract care to have the correct API nesting, naming, etc.

@jasonkuhrt
Copy link
Member

Mentioned this before in another thread. This is future-looking, how the API could grow in the future. The key would be some conditional typing that only requires the context data actually used by the selection set.

app.schema.exec('query { foo { bar } }') // OK, foo.bar doesn't use any context
app.schema.exec('query { foo { qux } }') // Error, qux needs some context data
//							   ~~~
app.schema.exec('query { foo { qux } }', {}, { blah: false }) // fix

Getting this information with static analysis would be hard. Getting it dynamically would require running resolvers during reflection which is almost certainly not workable. But would have to think more about either approach more.

@devYonz
Copy link

devYonz commented Jul 18, 2022

Can this also be applied to using graphQL as a local store, I have been searching for solutions for an offline application that uses a graph on top of local storage to act like an API for the application. I am looking to build an offline-only application that uses graphQL.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
scope/schema Related to the schema component type/feat Add a new capability or enhance an existing one
Projects
None yet
Development

No branches or pull requests

3 participants