Skip to content

Lab 10 - Discovering GraphQL with GraphiQL

Lab Overview

In this lab we will explore using GraphQL and GraphiQL to query the Nautobot database.

Table of Contents

Lab 10 - Discovering GraphQL with GraphiQL

Task 1 - GraphiQL Interface

Nautobot supports a Read-Only GraphQL interface that can be used to query most information present in the database. The GraphQL interface is available at the endpoint graphql/ for a human to explore and GraphQL can be queried as an API via the endpoint api/graphql/.

Step 1-1 - Login via the web browser to Nautobot

If you have not already, use a web browser to navigate to your instance of Nautobot at https://{{ your pod }}. We will be using the Nautobot GUI for the remainder of this lab so there won't be a need for a terminal. Log into Nautobot by using the login option in the upper right of the Nautobot homepage. You can log in as the superuser you created in a previous lab or the superuser account included with your lab environment, the credentials for this account will be provided by the instructor.

Step 1-2 - Open GraphiQL

From the Nautobot homepage, click on the GraphQL link at the bottom right of the page, or navigate to https://{{ your pod }}/graphql/

image01-1

Once loaded you will see a pane on the left for queries and a pane on the right for results. There are some comments that contain basic usage information, after reading them they are safe to delete.

image01-2

Step 1-3 - Review interface features

There are several buttons near the top of the page, Run (looks like a play button), Prettify, Merge, Copy, History, Queries, and on the far right Docs.

  • Run Query: Run it!
  • Prettify: clean up the query, make it look nice.
  • Merge Query: merge fragments into a query, we will do this later
  • Copy: copy all text in the main query pane to the clipboard
  • History: open the history pane to reveal past queries made in this session
  • Queries: save queries or load previously saved queries
  • Docs: open the documentation explorer. Autocomplete is good, but everything can be found in here!

image01-3

The documentation explorer contains the entire GraphQL Schema definition for Nautobot. In addition to a full list of all the objects and attributes that can be queried, you'll find syntax for specific types of searches such as "starts with" or "case-insensitive exact". We will explore some complex queries later, but for now lets move on to a simple query to get started.

Task 2 - Query Basics

Step 2-1 - Query Devices

Let's start by simply retrieving all the Devices.

Run this query:

query {
  devices {
    name
  }
}

Unlike ReST, GraphQL queries match the desired response. Sure enough the response looks like the query, and we were given only what we asked for, the name of every Device. The response is actually much longer than shown here. For the purposes of this lab when the output has been truncated it will be denoted with ...

Response:

{
  "data": {
    "devices": [
      {
        "name": "ams01-dist-01"
      },
      {
        "name": "ams01-edge-01"
      },
      {
        "name": "ams01-edge-02"
      },
      ...

What if we want more than just the name? No problem, run this query:

query {
  devices {
    name
    last_updated
  }
}

Response:

{
  "data": {
    "devices": [
      {
        "name": "ams01-dist-01",
        "last_updated": "2023-09-01T21:42:48.185480+00:00"
      },
      {
        "name": "ams01-edge-01",
        "last_updated": "2023-09-05T15:44:32.120029+00:00"
      },
      {
        "name": "ams01-edge-02",
        "last_updated": "2023-09-01T21:38:36.498439+00:00"
      },
      {
        "name": "ams01-leaf-01",
        "last_updated": "2023-09-01T21:38:36.504873+00:00"
      },
      ...

It's easy! Let's do another one. What if I need to traverse a relationship to a related object and retrieve an attribute from that object? No problem, run this query:

query {
  devices {
    name
    interfaces {
      name
      ip_addresses {
        address
      }
    }
  }
}

Uh oh. You probably noticed that query took awhile to complete. A note of caution when using GraphQL, it will retrieve exactly what you want, but complex queries that traverse many tables can quickly get out of hand and take too long to process. If you are running queries for large amounts of data, ReST will provide better performance.

Response:

{
  "data": {
    "devices": [
      {
        "name": "ams01-dist-01",
        "interfaces": []
      },
      {
        "name": "ams01-edge-01",
        "interfaces": [
          {
            "name": "Ethernet1/1",
            "ip_addresses": [
              {
                "address": "10.11.192.0/32"
              }
            ]
          },
          {
            "name": "Ethernet2/1",
            "ip_addresses": [
              {
                "address": "10.11.192.2/32"
              }
            ]
          },
          {
            "name": "Ethernet3/1",
            "ip_addresses": [
              {
                "address": "10.11.192.4/32"
              }
            ]
          },
          ...

Run the same query again, but this time provide the name of a specific Device:

query {
  devices(name: "hou01-dist-01") {
    name
    interfaces {
      name
      ip_addresses {
        address
      }
    }
  }
}

This time the response was very fast, this type of query is where GraphQL shines.

Response:

{
  "data": {
    "devices": [
      {
        "name": "hou01-edge-01",
        "interfaces": [
          {
            "name": "Ethernet1/1",
            "ip_addresses": [
              {
                "address": "10.45.0.0/24"
              }
            ]
          },
          {
            "name": "Ethernet2/1",
            "ip_addresses": [
              {
                "address": "10.45.0.1/24"
              }
            ]
          },
          {
            "name": "Ethernet3/1",
            "ip_addresses": [
              {
                "address": "10.45.0.2/24"
              }
            ]
          },
          ...

Let's dig deeper and run an even more complex Device query, run this query (replace the name of the device with yours if necessary):

query {
  devices(name: "hou01-edge-01") {
    name
    status {
      name
      color
    }
        device_type {
      manufacturer {
        name
      }
    }
    interfaces {
      name
      ip_addresses {
        address
      }
    }
  }
}
The response was still fast, great!

Response:

{
  "data": {
    "devices": [
      {
        "name": "hou01-edge-01",
        "status": {
          "name": "Active",
          "color": "4caf50"
        },
        "device_type": {
          "manufacturer": {
            "name": "Arista"
          }
        },
        "interfaces": [
          {
            "name": "Ethernet1/1",
            "ip_addresses": [
              {
                "address": "10.45.0.0/24"
              }
            ]
          },
          {
            "name": "Ethernet2/1",
            "ip_addresses": [
              {
                "address": "10.45.0.1/24"
              }
            ]
          }
          ...

Instead of filtering on a specific name, try searching all names for a string. Run this query to search all Devices that contain "leaf" (case-insensitive) in the name:

query {
  devices(name__ic: "leaf") {
    name
  }
}
Response:

{
  "data": {
    "devices": [
      {
        "name": "ams01-leaf-01"
      },
      {
        "name": "ams01-leaf-02"
      },
      {
        "name": "ams01-leaf-03"
      },
      {
        "name": "ams01-leaf-04"
      },
      {
        "name": "ams01-leaf-05"
        ...

Task 3 - Intermediate Queries

Step 3-1 - Create a fragment

A fragment is an easy way to create a reusable piece of a query that can be included in other queries. Let's break out a piece of the previous query and turn it into a fragment. Run this query:

fragment IPInfo on InterfaceType {
  name
  status {
    name
  }
  ip_addresses {
    address
  }
}

query {
  devices(name: "hou01-edge-01") {
    name
    status {
      name
      color
    }
        device_type {
      manufacturer {
        name
      }
    }
    interfaces {
      ...IPInfo
    }
  }
}

Fragments can be named whatever you like, in this case it is named IPInfo.

Response:

{
  "data": {
    "devices": [
      {
        "name": "hou01-edge-01",
        "status": {
          "name": "Active",
          "color": "4caf50"
        },
        "device_type": {
          "manufacturer": {
            "name": "Arista"
          }
        },
        "interfaces": [
          {
            "name": "Ethernet1/1",
            "status": {
              "name": "Active"
            },
            "ip_addresses": [
              {
                "address": "10.45.0.0/24"
              }
            ]
          },
          {
            "name": "Ethernet2/1",
            "status": {
              "name": "Active"
            },
            "ip_addresses": [
              {
                "address": "10.45.0.1/24"
              }
            ]
          },
          ...

Click the Merge button at the top of the GraphiQL interface and the fragment will merge into the query.

{
  devices(name: "hou01-edge-01") {
    name
    status {
      name
      color
    }
    device_type {
      manufacturer {
        name
      }
    }
    interfaces {
      name
      status {
        name
      }
      ip_addresses {
        address
      }
    }
  }
}

Step 3-2 - Use an alias

If you want to query two different objects with different argument values that share the same field name, an alias is required. Let's try and get the status of two different devices and see what happens. Run this query:

query {
  devices (name: "hou01-edge-01") {
    status {
      name
    }
  }
  devices (name: "hou01-dist-01") {
    status {
      name
    }
  }
}

Well, that didn't work.

Response:

{
  "errors": [
    {
      "message": "Fields \"devices\" conflict because they have differing arguments. Use different aliases on the fields to fetch both if this was intentional.",
      "locations": [
        {
          "line": 2,
          "column": 3
        },
        {
          "line": 7,
          "column": 3
        }
      ]
    }
  ]
}
To fix this problem, simply name the parts of the query that clash, for this example I used the names of the Devices. Run this query:

query {
  hou01edge01:devices(name: "hou01-edge-01") {
    status {
      name
    }
  }   
  hou01dist01:devices(name: "hou01-dist-01") {
    status {
      name
    }
  }
  hou01leaf01:devices(name: "hou01-leaf-01") {
    status {
      name
    }
  }
}

Problem solved! Aliases simply rename part of the response.

Response:

{
  "data": {
    "hou01edge01": [
      {
        "status": {
          "name": "Active"
        }
      }
    ],
    "hou01dist01": [
      {
        "status": {
          "name": "Active"
        }
      }
    ]
  }
}

Task 4 - Save, load and execute queries

Step 4-1 - Save a query

To save a query for later use, click on the Queries drop down menu, next to the other GraphiQL buttons, and select Save Current Query As...

image04-1

For this example, the previous query has been expanded to include all 3 HOU01 Devices. Save the query with the following name (or substitute for your stadium):

  • Name: Houston Stadium Device Status
  • Slug: houston-stadium-device-status

image04-2

After saving the form, you will be directed to the detail page for the GraphQL Query, it has been saved as an object in Nautobot! Click on the blue Execute button on the right hand side of the page and the results will be displayed in the Response box.

image04-3

Step 4-2 - Load a saved query in GraphiQL

Return to GraphiQL by clicking on the link at the bottom right of the Nautobot homepage.

Click on the Queries drop down menu, next to the other GraphiQL buttons and the new query is now available. Click on it to load the query.

image04-4