Skip to content

Query API

The Slicknode GraphQL Server provides an extensive API to query the data in your data stores. You can query a list of nodes as well as single objects, filter and sort the result sets and paginate through large amounts of data.

Get Single Node

Every node can be retrieved by its id value or by any value of a field that has the @unique directive. Slicknode automatically adds a field to the root Query type.

Get Node by ID

To retrieve a single User node by its ID, you could write the following query:

Query:

query {
  getUserById(id: "1234xyz") {
    id
    email
  }
}

Result:

{
  "data": {
    "getUserById": {
      "id": "1234xyz",
      "email": "user@example.com"
    }
  }
}

Get Node by Unique value

When you add the @unique directive to a field in your schema, slicknode automatically adds a field on the root Query type that lets you get a node by its unique field value.

For example, to get a user by its email address, you could use the following query:

Query:

query {
  getUserByEmail(email: "user@example.com") {
    id
    email
  }
}

Result:

{
  "data": {
    "getUserByEmail": {
      "id": "1234xyz",
      "email": "user@example.com"
    }
  }
}

Naming convention

For modules that do not have a namespace, the field name on the root query type follows the naming convention:

get<TypeName>By<FieldName>.

For modules with a namespace, the naming convention is as follows:

<Namespace>_get<TypeNameWithoutNamespacePrefix>By<FieldName>

List nodes

When you add a Node type to your schema, Slicknode automatically adds a field to the root Query type of your GraphQL schema to retrieve a list of nodes, apply filters, pagination and to sort the results.

For example, to get a list of users, you could use the following query:

query {
  listUser {
    edges {
      node {
        id
        email
      }
    }
  }
}

Result:

{
  "data": {
    "listUser": {
      "edges": [
        {
          "node": {
            "id": "1234xyz",
            "email": "user@example.com"
          }
        },
        {
          "node": {
            "id": "2234xyz",
            "email": "user2@example.com"
          }
        }
      ]
    }
  }
}

This returns a list of users with their IDs and email addresses.

Filtering

Slicknode comes with a variety of filters for the data that is stored in your database. Whenever you add a node type to your schema, slicknode automatically generates the filter types for each node and adds them to your schema. This gives you a type safe query API that is best explored via the GraphiQL Playground.

The filters can be passed as an argument to the list field. For example, to load only the users that have the domain slicknode.com in their email address, you could execute the following query:

query {
  listUser(
    filter: {
      node: {
        email: {
          endsWith: "@slicknode.com"
          # ... mode filter conditions on the email field
        }
        # ... more filter conditions on other fields
      }
    }
  ) {
    edges {
      node {
        id
        email
      }
    }
  }
}

You can add as many filter conditions as you like. Multiple filter conditions are combined with the AND operator by default.

If you wanted to load all users that have email addresses for silcknode.com and have the first name John, you could run the following query:

query {
  listUser(
    filter: {
      node: { email: { endsWith: "@slicknode.com" }, firstName: { eq: "John" } }
    }
  ) {
    edges {
      node {
        id
        email
      }
    }
  }
}

The generated filter types are derived from the field types of your nodes.

Pagination

The Slicknode API provides two different methods of pagination. This allows you to load only a slice of a set of nodes and load more nodes in subsequent requests. You can use a Relay style cursor pagination or an offset based pagination.

The list fields for your nodes on the root GraphQL Query type accept a few arguments for pagination:

Name Type Description
first Int The number of nodes to return when using forward pagination
after String The cursor of the last item from the previous slice when using forward pagination.
last Int The number of nodes to return when using backwards pagination
before String The cursor of the last item from the previous slice when using backwards pagination
skip Int The number of nodes to skip in the result set for offset based pagination

Cursor Pagination

With a cursor based pagination you start either at the beginning or at the end of the dataset and then load a specified number of nodes. For example, to load the first 10 articles. The response also provides you with a cursor that you can use to load the next slice.

When to use?

  • you want to use infinite scrolling
  • you have large data sets
  • you need stable results and don't want to load duplicates when an item is added between requests

When not to use?

  • you need to be able to jump into the middle of a data set (you could achieve a similar result with filters though)

Forward Pagination

To load the first 10 users in your application you could use the following query:

query {
  listUser(first: 10) {
    pageInfo {
      hasNextPage
    }
    edges {
      cursor
      node {
        id
        email
      }
    }
  }
}

Result:

{
  "data": {
    "listUser": {
      "pageInfo": {
        "hasNextPage": false
      },
      "edges": [
        {
          "cursor": "1",
          "node": {
            "id": "VXNlcjox",
            "email": "john.doe@example.com"
          }
        },
        {
          "cursor": "2",
          "node": {
            "id": "VXNlcjoy",
            "email": "max.mustermann@example.com"
          }
        }
      ]
    }
  }
}

Besides the actual data of the node (id, email) we are also loading some pagination information. hasNextPage indicates whether there is more data stored in the database than was returned in the current slice, and the cursor of the edge can be used to load more nodes or to refresh the loaded data.

To load the next slice of data, we use the cursor of the last item from the previous response and pass it as an argument to the field:

Query:

query {
  listUser(first: 10, after: "cursorfromlastitemofpreviousresponse") {
    pageInfo {
      hasNextPage
    }
    edges {
      cursor
      node {
        id
        email
      }
    }
  }
}

This returns the next slice of data and can be repeated until all the data is loaded.

Backward Pagination

You can also start the pagination from the end of your data set. It is very similar to the forward pagination, but instead of the arguments first and after, we are using the equivalent arguments last and before.

To load the last 10 users in your database, use the following query:

query {
  listUser(last: 10) {
    pageInfo {
      hasPreviousPage
    }
    edges {
      cursor
      node {
        id
        email
      }
    }
  }
}

To load the next slice of data, use the first cursor of the returned response and pass it as an argument to the list field:

Query:

query {
  listUser(last: 10, before: "firstcursorofpreviousresponse") {
    pageInfo {
      hasPreviousPage
    }
    edges {
      cursor
      node {
        id
        email
      }
    }
  }
}

Offset Based Pagination

With offset based pagination, you can request a number of nodes from a set of data and optionally skip any number of nodes. This allows you to directly jump into the middle of a dataset.

When to use?

  • You need to jump into a middle of a dataset
  • You need to implement page based pagination

When not to use?

  • You have changing result sets and need stable results (someone adds or removes nodes between requests)
  • You need to jump far into a dataset and need top performance (use cursor based pagination with filters instead)

To load 10 nodes, provide the number of nodes to load in the first argument and use the skip argument to indicate how many nodes should be skipped from the result set.

The following query would load the first 10 nodes:

query {
  listUser(first: 10, skip: 0) {
    pageInfo {
      hasNextPage
    }
    edges {
      node {
        id
        email
      }
    }
  }
}

The following query would load the nodes 11-20:

query {
  listUser(first: 10, skip: 10) {
    pageInfo {
      hasNextPage
    }
    edges {
      node {
        id
        email
      }
    }
  }
}

Total Count

To get the total number of nodes that are stored on the server, you can use the field totalCount that is available on every connection type:

{
  listUser {
    totalCount
  }
}

This will return the total number of users in the system. There are a few things to keep in mind when querying the total count:

  • Permissions: The permission filters are automatically applied to the aggregation query, that means that the server only counts the nodes that are accessible by the current user.
  • Filters: The filters that are passed to the parent field are also applied to the aggregation query, which lets you count only a subset of the nodes.
  • Pagination: When a cursor is passed to the input arguments after or before, the aggregation query is adjusted to only return the total number of nodes after or before the given cursor. This lets you count the number of nodes on each side of the loaded data slice without loading the actual nodes.

For example for a functionality like (24 new comments - Click to load)

If you want to get the totalCount of nodes and a slice of nodes in the same request, you can use aliases:

query PaginatedUsers($after: String) {
  listUser {
    # This returns all users without pagination limitation
    totalCount
  }
  # We only add the cursor on this field here to get a slice of nodes
  paginatedUsers: listUser(after: $after, first: 10) {
    edges {
      node {
        id
        # ... other fields
      }
    }
  }
}

Note that the input arguments first and last are ignored to determine the total count. You can derive that number from the number of returned nodes if needed.

Sorting

By default, the returned nodes of a list field are returned in the order they were added to the data store (sorted by id). If you want to change the sorting order of the result, you can pass the argument order to the list field. By default, the results are sorted in ascending order.

Sorting Fields

To get a list of users sorted by their lastName, you could run the following query:

query {
  listUser(order: { fields: ["lastName"] }) {
    edges {
      node {
        id
        lastName
      }
    }
  }
}

You can also sort the results by multiple fields. For example, if you have multiple users with the same last name and you want to sort the results by the lastName first and firstName second, you can add that field to the sorting configuration:

query {
  listUser(order: { fields: ["lastName", "firstName"] }) {
    edges {
      node {
        id
        lastName
      }
    }
  }
}

You can add any number of fields to the sorting order. Keep in mind though that this might consume a significant amount of database resources, depending on the size and complexity of your data, available indexes etc.

Sorting Direction

By default, all nodes are sorted in ascending order. If you want to change the default behavior, you can provide the sorting direction as an argument:

query {
  listUser(order: { fields: ["lastName"], direction: DESC }) {
    edges {
      node {
        id
        lastName
      }
    }
  }
}

The sorting direction is a system enum type with the values ASC for ascending order and DESC for descending order and is part of all Slicknode GraphQL schemas.

Preview / Published

The Slicknode GraphQL API has two modes to load content for all nodes that implement the Content interface.

  • Published Mode (default): This mode returns all content from the published storage. Unpublished content is not returned from the API.
  • Preview Mode: In this mode, the API returns all content from the preview storage including unpublished content.

Usually you would use the preview mode to preview changes and unpublished content and use the published node to deliver the content to end users.

By default, the API returns content in the published mode. There are several ways to set the mode of the API.

HTTP-Header

You can enable set preview mode by adding the HTTP header X-Slicknode-Preview in requests to the GraphQL API:

const endpoint = 'https://<your-slicknode-endpoint>';
fetch(endpoint, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-Slicknode-Preview': '1',
  },
  body: JSON.stringify({
    query: '{ Blog_getPostBySlug(slug: "my-article") {title} }',
  }),
})
  .then((r) => r.json())
  .then((data) => console.log('data returned:', data));

Input Arguments

You can set the preview and published mode for individual parts of your GraphQL query by using the preview input argument. The selected mode will then be used to return the data of that node and the entire selection set of all its children.

For example:

query GetPost($slug: String!) {
  # This will return the blog post in preview mode
  unpublishedPost: Blog_getPostBySlug(slug: $slug, preview: true) {
    title
    # ...

    # The category will be loaded from preview storage,
    # since `preview` was set to true in a parent field
    category {
      name
      # This will also be returned in preview node, the preview setting cascades
      articles {
        edges {
          node {
            title
          }
        }
      }
    }
  }

  # Returns the published version of the post
  publishedPost: Blog_getPostBySlug(slug: $slug, preview: false) {
    title
    # You can also override the preview mode in a child selection set.
    # `category` will now be loaded in preview mode
    category(preview: true) {
      name
      # Articles will now be loaded in preview mode,
      # since the `preview` setting of the closest parent is `true`
      articles {
        edges {
          node {
            title
          }
        }
      }
    }
  }
}

Setting the preview mode via HTTP-Header and via input arguments can be used in combination. In that case, the preview setting of the HTTP header is applied to the query and the setting of the input argument overrides the setting for the particular part of the GraphQL query.

Localization

Slicknode has comprehensive locatiztion capabilities that allow you to localize content for your audiences. The localiztion features are available for all types that implement the Content interface.

Slicknode has several ways to set the locale and load localized content via the GraphQL API.

HTTP-Header

You can set the locale for your content by adding the HTTP header X-Slicknode-Locale in requests to the GraphQL API, for example:

const endpoint = 'https://<your-slicknode-endpoint>';
fetch(endpoint, {
  method: 'POST',
  headers: {
    'Content-Type': 'application/json',
    Accept: 'application/json',
    'X-Slicknode-Locale': 'en-US',
  },
  body: JSON.stringify({
    query: '{ Blog_getPostBySlug(slug: "my-article") {title} }',
  }),
})
  .then((r) => r.json())
  .then((data) => console.log('data returned:', data));

This will load all content nodes with the locale en-US.

Input Arguments

You can set the locale for individual parts of your GraphQL query by using the locale input argument. The specified locale will then be used to return the data of that node and the entire selection set of all its children.

For example:

query GetPost($slug: String!) {
  # This will return the blog post for the `de-DE` locale
  postDE: Blog_getPostBySlug(slug: $slug, locale: "de-DE") {
    title
    # ...

    # The category will be loaded with the `de-DE` locale,
    # since the `locale` was set to `de-DE` in a parent field
    category {
      name
      # This will also be returned with the `de-DE`, the locale setting cascades
      articles {
        edges {
          node {
            title
          }
        }
      }
    }
  }

  # Returns the english version of the post
  postEN: Blog_getPostBySlug(slug: $slug, locale: "en-US") {
    title
    # You can also override the locale in a child selection set.
    # `category` will now be loaded for the locale `de-DE`
    category(locale: "de-DE") {
      name
      # Articles will now be loaded for the locale `de-DE`,
      # since the `locale` setting of the closest parent is `de-DE`
      articles {
        edges {
          node {
            title
          }
        }
      }
    }
  }
}

You can configure the available locales by adding and removing nodes of type Locale to your project via the Slicknode console or the API.

Loading Localiztions

You can load all localizations of a content node via the _localizations field that is automatically added to all types that implement the Content interface.

query GetTranslations($slug: String!) {
  Blog_getPostBySlug(slug: $slug) {
    # Returns the title in the default locale (or set via HTTP-Header)
    title
    locale {
      code
    }
    # List of all localiztions for the particular content node
    _localizations {
      edges {
        node {
          title
          locale {
            code
          }
        }
      }
    }
  }
}