moongqlGraphQL · MoonBit
GraphQL for MoonBit

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.

schema.mbt
#gql.type
pub struct User {
  name : String
  posts : Array[Post]
}

#gql.type
pub struct Post {
  title : String
}

#gql.query
fn user(id : Int64) -> User?
one endpoint
Query User Post Post title

ask once
walk every edge

POST /graphql
{
  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

Three things the plate does

Interconnection, on demand, and pat­tern 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.

I · interconnection

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! }
II · on demand

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
III · pattern matched

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()
}
The whole surface

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.

Served with the endpoint

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.

GraphiQL served by a moongql endpoint, with the starter query and its response
GraphiQL
The moongql IDE: a document on the left, its response on the right, both on a gold-haired plate
MoonQL · the default IDE
Apollo Sandbox embedded in the moongql endpoint page, showing the schema explorer
Apollo Sandbox
Print, then read back

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.

Begin

Your structs are already the schema.