Queries are the way to read data from a node. Now that you know how
schemas shape a program’s graph,
let’s talk about how queries work.
To illustrate this section, we’ll assume that you have membrane/github
running on your workspace. We’ll also assume that you have created a blank
program with the following node as a connection named user:
Name
Gref
Type
user
github:users.one(name:'membrane-io')
You can click on the User type in the table above to explore the entire schema of membrane/github. We’ll be querying data from the above Github User. Feel free to change it to your own GitHub username.
Querying a node
Now that your program has access to a node named user, you can use $query
and $get to read values from it.
Here are some examples.
import {
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
nodes } from"membrane"
exportasyncfunction
functionexample():Promise<void>
example() {
// Using $get() to query a string
const
const email:string
email = await
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
Queries the graph starting at node referenced by this g-ref.
@param ― query The query to perform. Must be valid GraphQL query
@returns ― The result of the query. An object with the same shape as the query
@example
This example queries the status and body of the membrane.io website:
const { status, body } = await nodes.http.get({ url: "https://example.com" }).$query("{ status body }")
$query(`
name
repos {
page {
items { name, description }
}
}`)
}
Note that you can only query
scalar nodes. When querying objects
nodes, you must specify which fields to include by using $query and passing a
GraphQL query. Here’s an example that won’t work:
import {
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
nodes } from"membrane"
// This won't work. Which fields do we want from the user node?
const
const user: github.handles.User
user = await
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
nodes.
user: github.handles.User
user
The above expression doesn’t work because to query an object node you must
specify which fields you want to get from it using $query to provide a GraphQL
query.
Finally, for convenience, you can omit the outer { and } when using
$query. So instead of "{ name bio }" you can just write "name bio".
Querying a collection
Fields that return collections of data follow conventions for pagination. Let’s take a look at that repos field we queried above for a GitHub user:
Gref
Type
github:users.one(name:'membrane-io').repos
Click the RepositoryCollection type in the table above to inspect its schema. You’ll see that the collection contains fields for one<Repository> repo and a page<RepositoryPage> of repos. Take a look at those types as well.
As a refresher on querying a node, we can query fields on a single repo:
import {
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
nodes } from"membrane"
exportasyncfunction
functionexample():Promise<void>
example() {
const
const docs: github.handles.Repository
docs =
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
Queries the graph starting at node referenced by this g-ref.
@param ― query The query to perform. Must be valid GraphQL query
@returns ― The result of the query. An object with the same shape as the query
@example
This example queries the status and body of the membrane.io website:
const { status, body } = await nodes.http.get({ url: "https://example.com" }).$query("{ status body }")
$query(
"{ description homepage }",
)
}
But how about querying a collection of repos and paginating through it? For that, we access the page field on our repository collection, which itself contains sub-fields for items<List<Repository>> and next<Gref<RepositoryPage>>.
import {
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
nodes } from"membrane"
exportasyncfunction
functionexample():Promise<void>
example() {
// Get a reference to a RepositoryPage and paginate
let
let page: github.handles.RepositoryPage
page =
const nodes: {
readonly user: github.User;
readonly clock:Clock;
readonly process:Process;
}
Contains the graph references (grefs) that this program has been given access to.
Queries the graph starting at node referenced by this g-ref.
@param ― query The query to perform. Must be valid GraphQL query
@returns ― The result of the query. An object with the same shape as the query
@example
This example queries the status and body of the membrane.io website:
const { status, body } = await nodes.http.get({ url: "https://example.com" }).$query("{ status body }")
$query(`{ items { name } next }`)
}
}
Pagination UI in the IDE relies on these conventions for a page with items and next. You’ve likely already come across it in the explorer sidebar or dashboard.
For example, here in the sidebar we’re flipping through pages of emilk’s repos to find egui (the Rust frontend library that we use at Membrane):
Filtering
You may have noticed that the page field accepts parameters for filtering. In the case of GitHub repos, you can optionally pass filters for type, sort, direction, page, and pageSize.
Filtering options for a collection depend on the API itself. Membrane API drivers implement pagination in a standard way as described above, but filtering options will vary from driver to driver.
In the graph explorer sidebar (seen in the video above), you can select filters in the bottom right corner, below the paginated list.
Querying from the CLI
You can also make queries from the command line by using
mctl query. For example:
$ mctl query 'github:users.one(name:"membrane-io")''{ name bio }'
Resolving queries
Query execution
This section explains how Membrane queries work by calling the functions
exported from a program. You generally don’t need to think about this, but it’s
helpful to understand which functions are called when.
When a query is received for a node, Membrane first combines the gref and the
query (if any) into a larger GraphQL query.
If we’re querying { name bio } on the above gref, the actual query that will
be executed looks something like this:
{
users {
one(name: "membrane-io") {
# The lines above are derived from the gref
# The lines below come verbatim from the original query
name
bio
}
}
}
Then, to resolve the values, Membrane will invoke these functions in order,
which are all exported from
The result of each resolver is passed as the obj argument to all child
resolvers.
Executing List fields
Resolvers of type List<T> must return an array of values. Membrane will then
invoke the resolvers for each item in the array.
For example. If we wanted to get the name and description of all repos, we
would typically use the following query:
{
repos {
# The first page of repos
page {
items {
name
description
}
}
# A gref to the next page
next
}
}
Membrane would invoke the resolvers in the following order:
Root.repos
RepositoryCollection.page
RepositoryPage.items
For each item returned by RepositoryPage.items
Repository.name
Repository.description
Gref resolvers
Nodes in the graph are identified by their gref. Why? Because APIs typically use
integers IDs, names or UUIDs to identify resources. However, a bare ID is not
enough to interact with its corresponding resource. When using an API directly
you’d also need to know which endpoints to hit and how to pass the ID.
By using grefs to identity graph nodes, we are able to “encode” all the
information needed to interact with something.
All object types have an implicit field aptly named gref which can be
queried to get a reference to the node. The gref field, when queried, answers
the question “what is the gref that points to this node?”.
For example, we could query user.gref and would get back
github:users.one(name:'membrane-io'). That’s not very useful because we
already knew that one. However, when querying a list of nodes, including gref
in the query can be useful to know the identify of each returned item. We can
then use the returned grefs to interact with each item individually.
Membrane’s IDE uses grefs to identify each node. This is what enables you to
click on a list item and interact with the corresponding node in a generic way
(independent of how each API identifies its resources).
In most cases, you don’t need to write a resolver for this field, but it’s
important for when you’re querying a list of nodes and you want to keep track of
the identity of each item.