Introduction to GraphQL
About GraphQL
GraphQL is a powerful query language, offering:
- Specification that facilitates queries with a self-descriptive language.
- Strongly-typed schema which defines all permissible queries and their relationships.
- Hierarchical queries enabling precise data retrieval, preventing over-fetching or under-fetching of data.
- Mutation functionality, that allows you to change data on the server and query data in a single request.
- Introspection, a capability that allows users to probe the schema to determine valid queries.
Quick tip:
- Queries are for fetching data.
- Mutations are for changing data.
Why GraphQL?
Levain prefers GraphQL for its compelling advantages:
- It provides integrators a comprehensive description of the API data.
- It ensures clients request only what they need.
- It simplifies the evolution of APIs.
- It bolsters the development process with potent tools.
In comparison to REST, GraphQL combines multiple API endpoints into one, addressing various app request requirements. This means that instead of making multiple requests to different endpoints, you can conveniently use a single GraphQL request to fetch all the data you need.
About the GraphQL Reference
The Reference section on the sidebar is generated from Levain's GraphQL schema.
All calls are validated against this schema:
- Allowed queries are listed under the
Queries
section. - Allowed mutations are listed under the
Mutations
section. - Schema-defined types are listed under the
Objects
,Input Objects
,Enums
, andScalars
sections.
Schema
A GraphQL schema is a collection of types and fields that describe the data that can be queried and mutated. This schema serves as a contract between the client and the server, ensuring that the client can only request what the server can deliver. The two primary operations in GraphQL are queries (for reading data) and mutations (for writing data), and both are validated against this schema. Furthermore, clients can inspect the server's capabilities through introspection, discovering available types and fields.
Field
query {
viewer {
firstName # This is an example of a field.
}
}
A field represents a specific piece of data that can be fetched or modified. These fields can reside on various types and might be either scalar (e.g., string, integer) or object types. Additionally, fields can accept arguments that further specify the data fetching or modification behavior.
What the official spec says about fields: As stated in the official GraphQL specification:
All GraphQL operations must specify their selections down to fields which return scalar values to ensure an unambiguously shaped response.
This guideline underscores the granularity and precision GraphQL offers. When querying for an object type, you must indicate which fields of that object you wish to fetch. Failing to do so, especially when querying fields that return objects, will yield an error. This design ensures clients retrieve precisely the data they need, no more and no less.
Argument
query {
# "first: 10" is an argument
networks(first: 10) {
edges {
node {
networkName
}
}
}
}
An argument is a value that can be passed to a field. Arguments are defined on fields and can be scalar or object types. Arguments can also be defined as arguments that can be passed to a query or mutation.
Object
An object is a type that contains fields. Objects can be queried and mutated. Objects can also be defined as arguments that can be passed to a query or mutation. Objects can be scalar or object types.
Input Object
Input objects are used to pass arguments to mutations. Input objects are defined as input types and can be scalar or object types. Input objects are similar to objects in that they can have fields, but they cannot have arguments on their fields.
Connection, Node & Edge
query {
# Represents a connection
networks(first: 10) {
# Represents an edge
edges {
# Represents a node
node {
networkName
}
}
}
}
A Connection is a type that contains a list of edges. Connections are used to paginate lists of objects. Connections are defined as object types and can be queried and mutated. Connections can also be defined as arguments that can be passed to a query or mutation.
An Edge is a type that contains a node and a cursor. Edges are used to paginate lists of objects. Edges are defined as object types and can be queried and mutated.
A Node is a generic term for an object. You can look up a node directly, or you can access related nodes via a connection. If you specify a node that does not return a scalar, you must include subfields until all fields return scalars. Nodes are defined as object types and can be queried and mutated, they are always at the leaf of a connection in edges.
Forming Calls with GraphQL
You must be authenticated to make calls to the GraphQL API. You can authenticate with GraphQL using an Access Token. Learn more about authentication.
The GraphQL endpoint
The GraphQL API utilizes a single, consistent endpoint for all its calls.
https://api.levain.tech/graphql
Making a request
While REST operations rely on varying HTTP verbs, GraphQL uses a unified approach.
Whether you're executing a query or a mutation, ensure your request has an HTTP verb set to POST with a JSON-encoded body.
This body should include a key named query
.
{
"query": "query { viewer { firstName } }"
}
Note: The string value of "query"
must escape newline characters or the schema will not parse it correctly.
For the POST
body, use outer double quotes and escaped inner double quotes.
curl
To query the GraphQL API using curl, make a POST
request:
curl 'https://api.levain.tech/graphql' \
-X POST \
-H 'Authorization: Bearer ...' \
-d '{"query":"query {\n viewer {\n firstName\n }\n}"}'
axios
To query the GraphQL API using JavaScript using axios, make a POST
request:
const axios = require('axios');
const response = await axios.request({
method: 'POST',
url: 'https://api.levain.tech/graphql',
headers: {
Authorization: 'Bearer ...',
},
data: JSON.stringify({
query: /* GraphQL */ `
query {
viewer {
firstName
}
}
`,
}),
});
console.log(JSON.stringify(response.data));
Forming Queries with GraphQL
Queries in GraphQL are used to fetch data from a GraphQL server. They specify both the data you're seeking and its desired structure upon return.
Queries are structured like this:
query {
JSON-OBJECT-TO-RETURN
}
Example Queries
Example 1: Query to "fetch the first 10 networks"
The following query returns the first 10 networks with their networkId
and networkName
:
query {
networks(first: 10) {
edges {
node {
networkId
networkName
}
}
}
}
Example 2: Query to "fetch specific details for a wallet"
For example, if we want to list the names of the tokens that a wallet holds, with all its keys, then our query could look like this:
query {
# Assuming the orgId is 000044448888
organization(orgId: "000044448888") {
orgId
name
# Assuming the walletId is 00-11
wallet(walletId: "00-11") {
walletId
balances {
name
}
walletKeys {
keyId
type
}
}
}
}
Forming Mutations with GraphQL
Mutations are formed with the same syntax as queries, but with 3 components:
- The mutation keyword and
MUTATION
to perform (e.gcreateWallet
) - The input object to pass to the mutation. You pass the input object as an argument to the mutation.
- The payload to return from the mutation. You specify the payload as a field on the mutation. This is the same as querying a field.
Mutations are structured like this:
mutation {
MUTATION-NAME(input: {MUTATION-INPUT-OBJECT!}) {
MUTATION-PAYLOAD
}
}
Working with variables and input objects
You can use variables to pass arguments to a mutation. Variables are defined in the query and passed to the mutation
as an argument. Variables are defined with the $
symbol and are typed.
Variables are defined in the query and passed to the mutation as an argument.
mutation CreateWallet($input: CreateWalletInput!) {
createWallet(input: $input) {
walletId
}
}
{
"query": "mutation CreateWallet",
"variables": {
"input": {
"orgId": "000044448888",
"organizationNetworkId": "00-11",
"type": "EvmContractSafe",
"name": "My Wallet",
"description": "My wallet description",
"mainKey": {
"keyId": "11-33"
},
"backupKey": {
"keyId": "11-22"
}
}
}
}
Using axios, you can pass variables to a mutation like this:
const axios = require('axios');
const response = await axios.request({
method: 'POST',
url: 'https://api.levain.tech/graphql',
headers: {
Authorization: 'Bearer ...',
},
data: JSON.stringify({
query: `
mutation CreateWallet($input: CreateWalletInput!) {
createWallet(input: $input) {
walletId
}
}
`,
variables: {
input: {
orgId: '000044448888',
organizationNetworkId: '00-11',
type: 'EvmContractSafe',
name: 'My Wallet',
description: 'My wallet description',
mainKey: {
keyId: '11-33',
},
backupKey: {
keyId: '11-22',
},
},
},
}),
});
Example Mutations
Example 1: Mutation to "Create a new Key"
mutation CreateKey {
createKey(input: { type: SCALAR, retrieveIfExists: true }) {
keyId
}
}
Example 2: Mutation to "Join an Organization"
mutation JoinOrg {
joinOrganization(input: { invitationCode: "long-code" }) {
user {
userId
}
}
}