Skip to content

Queries

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:

NameGrefType
usergithub: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"
export async function
function example(): 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.

nodes
.
user: github.handles.User
user
.
github.handles.User.email: Scalar<string> & (() => Scalar<string>)

The user's email address

email
.
Scalar<string>.$get: () => Promise<string>

Gets the value of this field. This is equivalent to calling $query without arguments

$get
()
// For convenience, you can omit the $get() when querying a scalar
const
const blog: string
blog
= 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
.
github.handles.User.blog: Scalar<string> & (() => Scalar<string>)

The user's blog

blog
// Query a sub-node
const
const description: string
description
= 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
.
github.handles.User.repos: github.handles.RepositoryCollection & (() => github.handles.RepositoryCollection)

Collection of Github repositories owned by the user

repos
.
github.handles.RepositoryCollection.one: (args: {
name: string;
}) => github.handles.Repository

Retrieve a single repository by name

one
({
name: string
name
: "docs" }).
github.handles.Repository.description: Scalar<string> & (() => Scalar<string>)

The description of the repository

description
// Use graphQL to get multiple values
const {
const name: string | undefined
name
,
const email: string | undefined
email
,
const blog: string | undefined
blog
} = 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
.
Field<github.values.User>.$query(query: string): Promise<github.values.User>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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 email blog }")
// Use $query() and graphQL to get all the data you need in one call
const {
const name: string | undefined
name
,
const repos: github.values.RepositoryCollection | undefined
repos
} = 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
.
Field<github.values.User>.$query(query: string): Promise<github.values.User>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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:

GrefType
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"
export async function
function example(): 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.

nodes
.
user: github.handles.User
user
.
github.handles.User.repos: github.handles.RepositoryCollection & (() => github.handles.RepositoryCollection)

Collection of Github repositories owned by the user

repos
.
github.handles.RepositoryCollection.one: (args: {
name: string;
}) => github.handles.Repository

Retrieve a single repository by name

one
({
name: string
name
: "docs" })
const {
const description: string | undefined
description
,
const homepage: string | undefined
homepage
} = await
const docs: github.handles.Repository
docs
.
Field<github.values.Repository>.$query(query: string): Promise<github.values.Repository>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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"
export async function
function example(): 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.

nodes
.
user: github.handles.User
user
.
github.handles.User.repos: github.handles.RepositoryCollection & (() => github.handles.RepositoryCollection)

Collection of Github repositories owned by the user

repos
.
github.handles.RepositoryCollection.page: (args?: {
type?: string;
sort?: string;
direction?: string;
page?: number;
pageSize?: number;
}) => github.handles.RepositoryPage

Retrieve a page of repositories with optional filtering and pagination

page
()
while (
let page: github.handles.RepositoryPage
page
) {
const
const repos: github.values.Repository[]
repos
= await
let page: github.handles.RepositoryPage
page
.
github.handles.RepositoryPage.items: ListField<github.values.Repository> & (() => ListField<github.values.Repository>)

List of repositories in the page

items
.
ListField<github.values.Repository>.$query: (q: string) => Promise<github.values.Repository[]>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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")
let page: github.handles.RepositoryPage
page
= await
let page: github.handles.RepositoryPage
page
.
github.handles.RepositoryPage.next: () => Scalar<github.handles.RepositoryPage>

Reference to the next page of repositories

next
()
}
}
export async function
function exampleGQL(): Promise<void>
exampleGQL
() {
// Or, use graphQL syntax to query all sub-fields at once
let {
let page: github.values.RepositoryPage | undefined
page
} = 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
.
github.handles.User.repos: github.handles.RepositoryCollection & (() => github.handles.RepositoryCollection)

Collection of Github repositories owned by the user

repos
.
Field<github.values.RepositoryCollection>.$query(query: string): Promise<github.values.RepositoryCollection>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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
(`
page {
items { name }
next
}
`)
while (
let page: github.values.RepositoryPage | undefined
page
) {
const {
const items: github.values.Repository[] | undefined
items
,
const next: github.handles.RepositoryPage | undefined
next
} =
let page: github.values.RepositoryPage
page
let page: github.values.RepositoryPage | undefined
page
= await
const next: github.handles.RepositoryPage | undefined
next
?.
Field<github.values.RepositoryPage>.$query(query: string): Promise<github.values.RepositoryPage>

Queries the graph starting at node referenced by this g-ref.

@paramquery The query to perform. Must be valid GraphQL query

@returnsThe 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:

Terminal window
# Query a scalar
$ mctl query 'github:users.one(name:"membrane-io").bio'
# To query an object, pass a GraphQL query
$ 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

membrane/github:

  1. Root.users
  2. UserCollection.one
  3. User.name
  4. User.bio

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:

  1. Root.repos
  2. RepositoryCollection.page
  3. RepositoryPage.items
  4. For each item returned by RepositoryPage.items
    1. Repository.name
    2. 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.