skip to main content

Formulating GraphQL calls

Let's look at how to authenticate to the API and how to formulate and execute queries and mutations.

On the The GraphQL schema page, we gave the definitions of query and mutation relative to SDL. Now let’s focus on the query language and see how to formulate and execute a GraphQL call in practice.

Generated endpoints #

For each application view (Application Schema) defined in the engine, a GraphQL endpoint is generated available at:

https://<CloudletURL>/auth/api/graphql/<ApplicationName>

Each endpoint references a separate GraphQL API. These endpoints remain constant throughout the life cycle of the generated application, possibly changing if you regenerate the application after replacing or modifying the original engine.

<CloudletURL> depends on the domain on which the Cloudlet is deployed, the username of the owner account, and the name of the Cloudlet. For example, given the Administration view on the Workforce Cloudlet of the JohnDoe account, the corresponding GraphQL endpoint is:

https://hs4.fhoster.com/JohnDoe/Workforce/auth/api/graphql/Administration

A list of all endpoints generated for the Cloudlet is available in the API & Public URLs panel, which can be reached from the Dashboard by clicking on the .

Communicating with GraphQL #

In REST, the type of HTTP request determines the type of operation to be performed. GraphQL calls, on the other hand, are always POST type requests regardless of the operation, as they always contain a body encoded in JSON.

The easiest way to query the API is via GraphiQL. You can also use cURL or any other HTTP library. With cURL, for example, the JSON payload must contain a query key, having as value the query written in the GraphQL language:

curl -i -X POST $MY_ENDPOINT \
 -H "Content-type: application/json" \
 -H "Authorization: Basic $MY_TOKEN" \
 -d '{"query": "'"$MY_QUERY"'"}'

Authentication #

All registered Cloudlet users can communicate with GraphQL. As with all Cloudlet resources under /auth, authentication in GraphQL is Basic and requires you to provide username and password. In the case of the Cloudlet user-administrator, these credentials match those of Cloudlet’s proprietary Livebase account.

At a low level, every HTTP request to the API must include the header Authorization: Basic <MyToken>; the token is the result of Base64 encoding of the string <Username>:<Password>; if you use cURL, you must always specify the header as shown above. Higher-level libraries, such as Apollo, must be configured appropriately by providing the authentication token.

Detail: generating the token

Graphql Login Prompt

If you are using the GraphiQL client, simply type your credentials into the dialog that pops up the first time you log in; the client will reuse these credentials to communicate with the server.

Formulating queries and mutations #

The two types of operation allowed are query and mutation (mutations). Compared to REST, a query is equivalent to a GET, while mutations are analogous to POST, PATCH or DELETE depending on the implementation. Both queries and mutations share a similar format, with some important differences.

These are the steps required to formulate a GraphQL call:

  1. you specify the operation type, using the keyword query or mutation depending on the operation to be performed. If you want to perform a query and there is no ambiguity in the code, you can omit the keyword altogether;

  2. you require a special root object (root), that is the entry point in the schema containing all available services of query (or mutation) type;

  3. on it, you specify the name of the service to be invoked (a service is a field of the root object, which returns a object containing the result of the service invocation);

  4. if the service requires them, you specify any arguments in round brackets next to the service name;

    • read services require an ID to retrieve individual objects or options to filter and retrieve paginated results;
    • write services and other mutations require an input object containing the data to be sent to the server.
  5. you specify, on the output-object, the fields you want to get from the server. If you want to get a field that is itself an object (with sub-fields), you must expand each node of this type until it reaches a leaf, i.e. a field of type scalar. This ensures that the response always has an unambiguous form. The order of the fields is not relevant.

In its simplest form, a query is structured like this (rows prefixed with # are not interpreted):

query {
 ServiceName {
  # set of fields to select
 }
}

All intermediate nodes should be expanded to require only scalar (leaves):

query {
QueryServiceName {
scalarField1
scalarField2
nestedField {
scalarField3
}
}
}

A mutation always requires at least one argument specifying what data you want to send to the server:

mutation {
 MutationServiceName(mutationArgument: "Hello world!") {
  # set of fields to select
 }
}

Services of type mutation also return output objects, such as queries, on which one or more scalars are to be selected:

mutation {
MutationServiceName(mutationArgument: "Hello world!") {
scalarField1
scalarField2
nestedField {
scalarField3
}
}
}

The input can also be a complex object:

mutation {
MutationServiceName(data: {first_name: "John", last_name: "Doe", age: 42}) {
scalarField1
scalarField2
nestedField {
scalarField3
}
}
}

Working with variables #

Variables can make GraphQL calls more dynamic and powerful and help reduce the complexity of mutations when passing complex input objects.

Here is an example query with a single variable:

query($number_of_employees: Int!) {
  Employee___getPage(
    options: {next: $number_of_employees}
  ) {
    items {
      full_name
    }
  }
}
{
  "number_of_employees": 30
}

Variables must be defined outside the query in a valid JSON object, including an arbitrary number of variables to be used in the call. To use the variables so declared in the GraphQL operation, you must:

  1. Pass the variables as arguments to the operation:

    query($number_of_employees: Int!) {
    

    The argument is a key-value pair, where the key is the name prefixed by $, and the value is the type of the variable (e.g. Int). You can add a ! to mark the argument as mandatory. The type should always be included to comply with GraphQL’s static typing so that the server can validate the variables passed against the schema.

  2. Use variables in operation:

    Employee___getPage(options: {next: $number_of_employees}) {
    

    In this example, we replace the variable with the number of employees to retrieve.

When sending a call with variables, it is the client’s job to include the data structure containing them in the HTTP POST, along with the GraphQL query. With cURL, for example, the variables should be included in the JSON payload at the variables key:

curl -i -X POST $MY_ENDPOINT \
 -H "Content-type: application/json" \
 -H "Authorization: Basic $MY_TOKEN" \
 -d '{"query": "query($number_of_employees: ID!) { Employee___get(_id:$number_of_employees) { full_name } }", "variables": {"id": "10101"}}'

This process makes the query argument dynamic. We can simply change the value in the variables object and keep the rest of the query unchanged, encouraging reuse.

More information about variables is available here: Variables.

Query example #

Let’s look at a typical query, where we retrieve information from an Employee object using the single read service get.

{
  Employee___get(_id: "12345") {
    full_name
    age
    team {
      name
    }
    assignments {
      totalCount
      items {
        start_date
        end_date
        project_ {
          director
        }
      }
    }
  }
}

Let’s go through the query line by line:

  1. we use the Query.Employee___get service, which allows us to get an Employee object. This service requires the _id argument, a scalar of type ID;
  2. of the returned employee, we ask for his full name (full_name) and his age (age), two scalars of type string and number respectively;
  3. we also ask for information about two satellite classes: the fields team and assignments are of type Team and Project_assignment respectively, so we expand these objects and select scalars;
  4. for team (role to one), we ask for the name of the team the employee is part of (name);
  5. for assignments (many roles), we ask for the number of assignments of the employee (totalCount) and use the items field of the Project_assignmentPage object to select which fields to show for each assignment;
  6. we choose the scalars start_date and end_date, of type date, i.e., start and end date of the assignment, and show the project manager associated with the assignment;
  7. finally, we show the name of the project director associated with the assignment by operating a selection on the object of type Project associated with the assignment on the field project_.

Example of mutation #

Mutations often require information that can only be retrieved by first running a query. In this example, we use a query to retrieve the ID of an existing Employee object and a mutation to update its data, using the getPage and update services.

query FindEmployeeID {
  Employee___getPage(options: {filter: {team_name___eq: "Cool Coders"}}) {
    items {
      _id
      full_name
      address {
        _id
        city
      }
    }
  }
}

mutation ChangeEmployeeCity {
  Employee___update(
    data: {
      _id: "10101"
      supervisor: {
        set: "10102"
      }
      address: {
        update: {
          _id: "222000"
          city: "Milan"
        }
      }
    }
  ) {
    _id
    full_name
    address {
      city
    }
    supervisor {
      _id
      full_name
    }
  }
}

Let’s examine the example: the task is to update the city of residence of an existing employee who has just moved and assign a supervisor who is part of his team. Of course, we know the first and last names of both employees.

  1. consulting the documentation generated for our schema, we find the Mutation.Employee___update service, which needs the input EmployeeUpdate, a type that has the field _id mandatory (marked with the symbol !);
  2. we need to retrieve the ID of the employee, but we also need the ID of his supervisor because we notice that the supervisor field of EmployeeUpdate is an input of type EmployeeRoleRef (update structure for 1-way role Team on class Employee), within turn the field set, of type ID, to indicate the employee to be associated;
  3. we use the page-reading Query.Employee___getPage service to retrieve the two employees, both objects of class Employee;
  4. since we know that both employees are part of the same team, we can restrict the results returned by the paginated get. We include the options argument with the EmployeePageOptions input; here we specify the filter field, choosing as value team_name___eq and writing the team name, which we know is "Cool Coders";
  5. from the returned object of type EmployeePage, we use the items field to select which fields to show for each employee. We choose to show full_name, and in addition to _id; we also need the ID of the object part Address.

Running the query, we get the following response:

{
  "data": {
    "Employee___getPage": {
      "items": [
        {
          "_id": "10101",
          "full_name": "Frank Jaeger",
          "address": {
            "_id": "222000",
            "city": "Rome".
          }
        },
        {
          }, "_id": "10101",
          }, { "full_name": "Levi Smith",
          "address": {
            "_id": "222754",
            "city": "Milan".
          }
        }
        // ... other employees of the same team
      ]
    }
  }
}

Having known the IDs, let’s now move on to the mutation:

  1. as mentioned before, we want to use the Mutation.Employee___update service: we populate the data argument with an object of type EmployeeUpdate, inserting on the mandatory field _id the ID of our employee, i.e. "Frank Jaeger";
  2. we add the field supervisor as mentioned above, specifying the field set to which we match the ID of the supervisor, namely "Levi Smith";
  3. to change the city, we have to modify the object Address (part a 1), which corresponds in EmployeeUpdate to the field address of type AddressRoleObject (update structure for part a 1 to Address on class Employee), within turn the field update, of type AddressUpdate;
  4. also AddressUpdate requires the ID of the object; we provide it together with the field city to update the city from "Rome" to "Milan";
  5. finally, we specify which fields we want to obtain from the server in output at the end of the update operation. The type returned by Employee___update is still Employee and we ask to show the new city and the supervisor’s name, which we expect to be "Milan" and "Levi Smith" respectively.

Running the mutation, we get this response:

{
  "data": {
    "Employee___update": {
      "_id": "10101",
      "full_name": "Frank Jaeger",
      "address": {
        "city": "Milan"
      },
      "supervisor": {
        "_id": "10102",
        "full_name": "Levi Smith"
      }
    }
  }
}

In this example we worked with a selection of schema fields: we could have asked for a lot more information from those available for the Employee and Address types, but thanks to GraphQL, we were able to focus only on those of interest in this context. We also made only two calls to the server, saving time and bandwidth.

One last note: the syntax can become cumbersome when passing multiple fields in an input object. In these cases, it can be helpful to move the fields into a variable. Here’s how we could have rewritten the original mutation using a variable:

mutation ChangeEmployeeCity($myVar: EmployeeUpdate!) {
  Employee___update(data: $myVar) {
    _id
    full_name
    address {
      city
    }
    supervisor {
      _id
      full_name
    }
  }
}
{
  "myVar": {
    "_id": "10101",
    "supervisor": {
      "set": "10102"
    },
    "address": {
      "update": {
        }, "_id": "222000",
        "city": "Milan".
      }
    }
  }
}

Further reading #

In addition to variables, GraphQL offers many other useful constructs for making calls supported by our implementation but are beyond the scope of this guide. If you want to learn more, you can find more information in the official documentation (graphql.org):