graphql icon indicating copy to clipboard operation
graphql copied to clipboard

How to load graphql schema from a file ?

Open yogeshpandey opened this issue 8 years ago • 22 comments

Is there a way, where I can load schema from files or string. I am building versioned apis using graphql-go, therefore | need to load set of schema based on api version.

yogeshpandey avatar Jul 07 '17 12:07 yogeshpandey

I would also be curious to know if this was considered. I have been digging around but so far all I can think of is to parse the file using the language/parser package and then manually generate the necessary object definitions based on the result.

rydrman avatar Jul 27 '17 17:07 rydrman

It'd be really great to be able to do this, as of now you can achieve this in Java GraphQL implementation https://github.com/graphql-java/graphql-java/blob/36b0ec9214e9c40adeb6e8a52faeef0bacd71cf2/src/main/java/graphql/schema/idl/SchemaParser.java

ulm0 avatar Sep 08 '17 00:09 ulm0

I'm guessing it would be a port of this from the graphql-js package:

https://github.com/graphql/graphql-js/blob/master/src/utilities/buildASTSchema.js#L505

tiaanl avatar Oct 07 '17 02:10 tiaanl

The schema parser from SourceGraphs GraphQL might be a good candidate as well: https://github.com/neelance/graphql-go/blob/master/internal/schema/schema.go

hugowetterberg avatar Oct 08 '17 08:10 hugowetterberg

Is anyone doing anything for this? Is there a plan for such support? Thanks! Dan.

djmcgreal avatar Dec 18 '17 16:12 djmcgreal

Since reflection isn't used, and fields/objects are represented by concretete types, it seems that this should be completely doable from a parsed AST. ResolveFn could be added after the fact.

tcolgate avatar May 23 '18 15:05 tcolgate

Just create a generator :)

JulienBreux avatar Jul 27 '18 19:07 JulienBreux

Better yet, give a try to https://github.com/vektah/gqlgen

ulm0 avatar Jul 28 '18 16:07 ulm0

This can be simply done given the fact that GraphQL schemas are self-describing through introspection. Thus, graphql.Schema could implement json.Marshaller and json.Unmarshaller. The only API change to graphql.Schema would have to be some way to add Resolvers to type decls. at runtime or you could just walk the schema yourself starting from Schema.QueryType(). This solution extends @tcolgate's comment.

Saving a Schema

schema := // Create a schema
b, _ := schema.MarshallJSON()
f, _ := os.Open("my_api.gql")
defer f.Close()

f.Write(b) // json.NewEncoder(f).Encode(schema) could also be used too

Loading a Schema

schema := &graphql.Schema{} // Note: this would be inited as empty
b, _ := ioutil.ReadFile("my_api.gql")

schema.UnMarshallJSON(b) // json.NewDecoder(f).Decode(schema) could also be used too

// Now, all you'd have to do is add FieldResolverFns to your types
schema.AddResolvers(...)

Mock MarshallJSON impl.

func (s *Schema) MarshallJSON() ([]byte, error) {
     res := graphql.Do(s, `{
                   __schema { /* Add all fields here */ }
               }`)
     if len(r.Errors) > 0 {
           return nil, errors.New("report a meaningful error here")
     }
     return json.Marshall(res)
}

Zaba505 avatar Nov 08 '18 01:11 Zaba505

This issue is pretty urgent, considering that a text-representation (.gql) GQL schema is needed in order to build a statically typed frontend in a language other than Go (e.g. TypeScript).

Maintaining a gql schema in parallel to a Go schema undermines the purpose of using GraphQL in the first place, i.e. a single API specification. Keeping a Go schema in sync with a gql schema is going to be not fun.

(It's frustrating since there's at least four GraphQL libraries for Go, yet none of them hit the sweet spot. graph-gophers supports reading gql files, but doesn't have a great interface and lacks features, e.g. mutations; gqlgen looks perhaps the most promising, but no support for Go modules yet; thunder looks unfinished.)

atombender avatar Jan 22 '19 21:01 atombender

@atombender The standard way of getting the schema for the frontend is via introspections, so this really isn't a problem as there is lots of tooling for generating graphql or JSON schemas from a given endpoint.

frankdugan3 avatar Jan 22 '19 21:01 frankdugan3

Sounds like that implies a running server, which means you have to boot up a particular version of a server to "scrape" a live schema that you then generate a client for. That sounds like a very awkward workflow that would make consistent versioning difficult. I'd rather work off a canonical gql file, which is what the tooling I'm familiar with does.

atombender avatar Jan 22 '19 21:01 atombender

@atombender 1) The general consensus is that GraphQL schemas should be version-less, 2) Wouldn't a typical frontend workflow have access to an API server, whether GraphQL or otherwise?

frankdugan3 avatar Jan 22 '19 21:01 frankdugan3

I'm not saying it's impossible, it's just an awkward workflow. It should be possible to write client code without immediate access to a running server (which may be behind access tokens or other barriers requiring client-side configuration).

If a server has a schema.gql or whatever, it makes sense to just generate a client from that, not fire up the server. In particular, copying the gql file into the frontend allows you to track what schema that frontend was generated from, as well as having a reference for documentation purposes. Personally, I shape my workflow around Git revisions, not around running servers. With Git, I can say, with confidence, that schema.gql corresponds to my server's revision 1234567, and so my well-tested client works against that revision at a minimum.

By versioning, I don't mean a version scheme, but matching the frontend schema with the right server schema. To my knowledge, GraphQL does have any provisions for versioning in the same way as gRPC does; "versioning" becomes a matter of managing conflicts — optional fields becoming required, field types changing, and so on — and is left as an exercise for the developer.

atombender avatar Jan 22 '19 21:01 atombender

Hello all, honestly skimmed most of this convo and saw some comments I may be able to shed some hope on. In particular, @atombender wanting to take a text gql schema and generate in multiple languages. I started a project https://github.com/gqlc, a little while ago, with the goal of creating a Protocol Buffer compiler but for GraphQL. Currently, the project is still on the runway but I hope to have it finished some time in early February.

Zaba505 avatar Jan 25 '19 19:01 Zaba505

As an addendum to my comment above: You can of course write your own introspection query and just run graphql.Do() with query IntrospectionQuery { __schema { ... etc. } } to dump the textual representation without having a running server.

That said, starting with a textual schema first, as opposed to a schema data model in Go, is much more flexible. It would let you generate the necessary Go types (structs etc.), including JSON marshaling code/tags, from a single source textual schema. Today, you have to define the GraphQL schema and your structs separately, which is of course redundant. To avoid this redundancy, you have to write your GraphQL schema (as Go code, via graphql.NewSchema() etc.) first, then run an introspection query, then run one of the several Go code generators out there.

With a single textual source, you'd just write .gql files, then run go generate to generate your Go types, then write some glue to do something like graphql.ParseFile("schema.gql"), and then finally plug in the resolver functions.

atombender avatar Jan 29 '19 19:01 atombender

@atombender I used to think the SDL was The Way™ to go for defining the server, but here are several issues for my use cases off the top of my head:

  1. In one of the apps I'm developing, the (SDL) schema is over 37,000 lines (it's an ERP), and it's only about 1/8 complete. Naturally, this should be split into multiple files (for human readability and editor performance), which means tooling is already required (some, but not all tools can read multiple SDL files) to combine the SDL files anyway, so avoiding a compilation step is no longer a given perk of SDL, at least not for large schemas.
  2. Though some progress has been made with schema directives, there is occasionally information the code (Go or otherwise) needs that can't be conveyed via SDL (for example, the Join Monster library for Node needs markup to associate DB tables and columns with GraphQL types -- there is a workaround, but to me it's confusing and redundant to define those relationships separate from the schema).
  3. Sometimes I want to generate parts of the schema from other code (for example, creating Enums from a unit conversion library).
  4. I've found it far easier for backend development to NOT have that generation step. Since frontend is almost always running through webpack these days, I find it trivial to add in a script to refetch the schema from the server and generate typings on build or file change. A factor in my idea of convenience is that I do full-stack development. YMMV.

As mentioned above, gqlgen looks very promising for an SDL-first approach. I think combined with Gnorm (for users of Postgres), a lot of boilerplate could be avoided.

An interesting note: Prisma has been all about SDL-first code generation, yet Schickling (one of the founders) predicted:

GraphQL prediction for 2019: Resolver-first server frameworks will begin to replace the currently popular schema-first approach Main reasons: ◆ No code redundancy (i.e. single definition of resolvers and types) ◆ Better code reuse & type-safety ◆ Superior DX Twitter source

frankdugan3 avatar Jan 29 '19 20:01 frankdugan3

Sure. Except for 4, those are really all evidence of immature tooling.

It's worth noting that gRPC has the exact same challenges, and they're generally solved; gRPC schemas are modular and have the equivalent fo GraphQL directives. gRPC/Protobuf has been "schema first" from day one, and it's worked out really well. I don't think I've seen anyone generate Protobuf schemas in code.

It's also worth noting that the assumption that a backend is SQL isn't universal. If you're not using an SQL database, the boilerplate problem is further magnified. In our case, the data is already in a document database backend that speaks JSON, and so with graphql-go/graphql, we'll effectively end up with three sets of declarations: The GraphQL schema, the Go types, and the document database schema.

I don't know what the Prisma guy is basing his prediction on, but Prisma isn't Go, so that might be a factor. The point isn't the SDL, by the way; it's that something should be the master schema, so that you can avoid duplicating the information. I shouldn't need to write Go structs for some type if I've already defined it as a schema, or vice versa.

atombender avatar Jan 29 '19 23:01 atombender

@atombender Not sure how 3 reflects poor tooling. Perhaps I wasn't clear about what I was doing: I generate GraphQL enums from an external library (JS in this case, as it is part of the frontend), so I don't have to manually sync GraphQL types to types available in the lib.

I do agree that generation to avoid duplication is the way forward, I'm just not convinced that GraphQL SDL is capable of handling all the edge cases needed to be the single source of truth, at least not in complex applications. I could be wrong, of course.

frankdugan3 avatar Jan 30 '19 00:01 frankdugan3

Well, point 3 can be solved by adequate tooling. For example, if your GraphQL tooling supports multiple files, then your enums can be generated from its "source of truth" into a GraphQL schema. I don't know your full use case, though, so it's not clear if that would be the best solution.

If a GraphQL schema isn't adequate in describing the full schema, then our tooling isn't good enough. Things like directives that are needed to provide things like type information to the tooling can always be described outside the schema itself, as configuration in YAML or whatever. For example, gqlgen lets you override the names and types of fields in order to generate better native Go types.

The problem with this repo is that as long as you need native Go types for things, it's a bit of a dead end — you can't realistically generate those Go types from its data model, since the data model conflates both typing (the "what") and resolvers (the "how"). The latter needs the Go types, so you get a cyclic dependency there. Possibly you can define the schema without the resolvers and plug in the resolvers after?

My current app has about 20 different types. They have to exist both as GraphQL schema types, as Go types with json tags, and as TypeScript types on the front end. Maintaining three sets of declarations is going to be a nightmare. Unfortunately, it looks like gqlgen has its own share of challenges (such as a lack of support for thunks, as I understand it).

atombender avatar Jan 30 '19 00:01 atombender

I don't know if this is still relevant to the thread but I've started working on implementing makeExecutableSchema from graphql-tools in go. https://github.com/bhoriuchi/graphql-go-tools

bhoriuchi avatar Jun 13 '19 07:06 bhoriuchi

Such a long discussion here. DuckDuckGo pointed me out to the issue with graphql-go/graphql schema from file request. Anyone can share the status of the topic? Thanks!

khssnv avatar Mar 04 '21 17:03 khssnv