One schema,
and the answer takes the shape you asked for.
moongql reads the types you already wrote, prints them as a schema, and answers any query by walking that graph — returning exactly the fields selected, nothing more, nothing twice.
#gql.type pub struct User { name : String posts : Array[Post] } #gql.type pub struct Post { title : String } #gql.query fn user(id : Int64) -> User?
ask once
walk every edge
{ user(id: 1) { name posts { title } } }
{ "data": { "user": { "name": "Ada", "posts": [ {"title": "On graphs"} ] } } }
hover a field: it lights the type, the wire and the line
Interconnection, on demand, and pattern matched.
A GraphQL server is judged on three things: whether the graph is really one graph, whether a request can ask for less, and whether the code that answers it is pleasant to write in the language you already use.
Every type is a node
A field that returns User is an edge to User. One request
crosses them all; no client-side stitching, no second round trip.
type User { posts: [Post!]! } type Post { author: User! }
Ask for less, get less
The selection set is the contract. Drop title and the resolver for it
never runs, the column is never read, the byte is never sent.
{ user(id: 1) { name } }
// three fields asked, three returned
MoonBit, not a DSL
Arguments arrive as Json, so reading one is a match — exhaustive,
and checked at compile time instead of at three in the morning.
match info.arg("id") { String(id) => find(id) _ => Json::null() }
Everything a GraphQL service is expected to answer.
Code-first schema
Build object, input, enum, interface and union types in code; print them to SDL and parse SDL back. Print-then-parse yields the same schema, descriptions and all.
Lexer, parser, validator
A hand-written lexer and recursive-descent parser turn a document into an AST; the validator flags bad ones with the line and column of the field at fault.
Executor
Variables, aliases, fragments, @skip/@include, non-null
propagation, error paths and extensions — execution as specified.
Introspection & SDL
__schema, __type and __typename answered from
the schema; SDL printed and read in both directions.
Subscriptions & batching
Source streams over graphql-transport-ws, and a DataLoader
that collapses the N+1 into one batch per pass.
HTTP, WS and subgraphs
GraphQL over HTTP with content negotiation and batching, the WebSocket protocol, and Apollo Federation v1/v2 for the parts that need their own service.
The IDE arrives at the same address.
A GET without a query returns a
page, not a 400. The library serves its own IDE by default — the same plate as this
site, with the document on the left and the answer on the right. GraphiQL, Apollo
Sandbox and Altair are one argument away, and so is serving nothing at all.
The schema travels as text, both ways.
schema.graphql
schema { query: Query } type Query { "The user with this id." user(id: ID!): User } type User { id: ID! name: String! "Use name instead." legacyName: String! @deprecated(reason: "use name") }
Schema::of_sdl
let s = Schema::of_sdl(text) s.to_sdl() // byte for byte the same document: // descriptions, defaults, deprecations // and custom directives all survive
A definition that stops
mid-way reports where it stopped — 2:1 expected a type system definition
— rather than being silently dropped.