GraphQL in Atrahasis

A GraphQL server already describes itself. Atrahasis reads that schema the moment you paste an endpoint, builds the type tree, and writes the query and variables for you. Here is what happens at each step.

A GraphQL API has one endpoint and one rule: you describe the data you want, and the server returns that exact shape. No endpoint per screen, no payload full of fields you will throw away, no second round trip for the thing you needed all along.

GraphQL in Ninety Seconds

The contract between client and server is a schema: a typed description of everything the API can return and every operation it can perform.

type User {
  id: ID!
  name: String!
  email: String
  posts: [Post!]!
}

The punctuation is the interesting part. A bare String may be null, String! never is, and the notation composes from the inside out. [Post!]! means a list that always exists, may be empty, and never contains a null. That is a promise the server makes and clients can rely on.

A schema has up to three entry points: Query reads, Mutation writes, Subscription streams over a long lived connection.

On the wire, a GraphQL call is almost always a POST to a single URL with a JSON body holding up to three keys:

{
  "query": "query GetUser($id: ID!) { user(id: $id) { id name } }",
  "variables": { "id": "user-123" },
  "operationName": "GetUser"
}

Values belong in variables, not interpolated into the document, because a variable is data while a pasted value is code, and because $id: ID! gets type checked against the schema before a single resolver runs.

Two things surprise people coming from REST. Errors live in the body, not the status line. A server that processed your request correctly answers 200 OK even when every field failed, and the failures arrive in an errors array carrying a message, the locations in your document, and the path to the field that broke. Partial success is normal. A response can hold data and errors at the same time, one field resolved and another rejected for permissions. A client that reads data and ignores errors will treat that rejection as a null value.

The last piece is the one that matters most for tooling: a GraphQL server can describe itself. Query the __schema field and you get every type, field, argument, and deprecation note it knows about. Introspection is why schema explorers, code generators, and autocomplete exist at all.

Most Clients Ignore All of That

Open a GraphQL request in a typical API client and you get a text box, a variables pane, and a send button. The schema is sitting on the server describing itself, and the tool asks you to go read someone else's documentation to find out what fields exist.

Atrahasis API Client starts from the schema instead. You paste an endpoint, and before you type anything the type tree is on screen: every query, every mutation, every argument, every nullability marker. You click the fields you want and the query writes itself, variables included.

The schema arrives before you ask

Type or paste a URL and nothing happens for half a second. Atrahasis waits 500 milliseconds after your last keystroke, then sends a standard introspection query. No half typed URL gets hit, and there is no button to press first.

Introspection uses your auth. The headers built for it are the same ones built for a real query: the same bearer token, basic credentials, API key, custom headers, with the same environment variable resolution. Plenty of APIs will not describe themselves to an anonymous caller, so fill in the Auth tab and press Fetch to retry without touching the URL.

The parsed schema is cached in memory and keyed by the endpoint it came from, so expanding nodes later reads that cache instead of hitting the network again. Switching tabs resets it, which means one tab's schema can never leak into another tab pointed at a different API.

The tree is lazy on purpose

The explorer shows Query, Mutation, and Subscription roots, whichever the schema declares. Fields under them are built shallow. Atrahasis knows whether a field has children, because it resolves the base type behind the wrappers and checks whether that type declares fields, but it does not build those children until you expand the node.

This is what keeps large schemas usable. A production schema carries hundreds of types with cycles running through all of them, User to Post to User again. Building that eagerly means either an enormous object graph or an infinite loop. Building it on demand means the first paint is immediate no matter how big the schema is. Every field is labeled with its real type, rendered the way you would write it: [User!]!, String, Int.

Clicking fields writes the query

Tick a checkbox and the query below updates immediately. Select a field that returns an object type and Atrahasis selects all of its scalar children automatically, then expands the node so you can see what it did. Select users and you get id, name, email without ticking each one.

What it deliberately does not do is follow nested objects. Selecting users will not also pull in users.posts and then posts.author down a cycle that never ends. Nested objects stay closed until you open them, so the generated query is runnable straight away and never accidentally enormous. Deselecting cleans up after itself: untick a parent and every descendant selection under it disappears.

Arguments become variables

Select a field that takes arguments and input rows appear beneath it, with required ones marked. Fill in limit: 10 on the users field and the generated document is not users(limit: 10). It is this:

query($users_limit: Int) {
  users(limit: $users_limit) {
    id
    name
  }
}

with the Variables tab holding { "users_limit": 10 }.

The operation stays anonymous, the variable definitions are what get added to it. The variable itself is named from the field and the argument together, so two fields both taking a limit never collide. The declared type comes straight from the schema with its wrappers intact, so an ID! argument is declared ID!. Values are parsed as JSON where they can be, so 10 arrives as a number and true as a boolean.

Required arguments are included even before you fill them in. That is intentional. The document stays structurally valid and the server tells you what is missing, instead of the query quietly dropping a required field and failing validation for a confusing reason.

When an argument's type is an input object, the row expands to show that object's own fields, nested as deep as the input types go. Fill in the leaves you care about and Atrahasis assembles them into an inline object literal, quoting strings and leaving numbers and booleans bare. A filter three levels deep becomes a few text inputs instead of hand written punctuation.

Search covers the whole schema

Lazy trees need search smarter than filtering what is on screen. Type in the search box and Atrahasis searches the cached schema, not the rendered tree, so fields inside types you have never expanded are found anyway. Matches come back with the parents needed to reach them, those parents expand and load on the spot, and the tree filters down to the branches that contain a hit.

Hand written queries, same tab

The explorer is one way in, not the only one. The editor below it is fully editable with GraphQL syntax highlighting, and what you write there is what gets sent. The usual workflow is to build the skeleton by clicking, then switch to the editor for what a field picker cannot express: fragments, aliases, @include and @skip, or a hand tuned mutation.

The Variables tab sits beside it, prefilled from your argument inputs and editable afterward. One behavior to know: the block must be valid JSON. If it does not parse, Atrahasis omits it rather than sending a broken payload, so a query that suddenly complains about missing variables is usually a stray comma one pane over.

The same engine as every other request

Underneath, a GraphQL call in Atrahasis is a POST built in the Rust core and sent through the same HTTP engine that powers every ordinary request in the app. Content-Type and Accept default to application/json and can be overridden from the Headers tab. An empty operation name is dropped rather than sent as an empty string, which some servers reject.

Sharing that engine is why the Detail tab is populated for GraphQL exactly as it is for REST: DNS lookup, TCP connect, TLS handshake with protocol and cipher, the server certificate, time to first byte, total duration, and byte counts both ways. When a GraphQL query is slow, that breakdown tells you whether you are waiting on the network or on the resolvers.

Environment variables resolve everywhere text is accepted, including the URL, header values, and every auth field. The same tab points at dev, staging, or production by switching environments, and secrets stay in your environment rather than in the tab.

What the tab does not do yet

Worth stating plainly, because finding out later is worse. Subscriptions are not streamed: the tab recognizes a subscription and labels it correctly, but executes it as a single POST. There is no WebSocket transport for GraphQL subscriptions yet, so streaming work belongs in the dedicated WebSocket and SSE tabs today. The builder writes selections, not documents: fragments, aliases, and directives are hand written in the editor. API key auth goes in the header for GraphQL requests, not the query string.

The Point

Introspection means the server already knows every answer you would otherwise look up in documentation. A client that reads it properly can hand you a working, correctly typed, variable driven query for an API you have never seen, seconds after you paste the URL. That is the whole design goal of the GraphQL tab: you should not have to look up what the server is willing to tell you.