skip to main content

The GraphQL schema

GraphQL represents a significant architectural and conceptual change from the REST API and brings with it some unfamiliar terms.

On this page, we provide an excerpt from the official GraphQL documentation (Schemas and Types), enriched with details about our implementation and mapping of Livebase classes to GraphQL. In the Glossary section at the bottom, we have collected a cheat sheet with the GraphQL vocabulary we will use in this documentation.

Structure of the schema #

The schema is at the core of the GraphQL server implementation and describes the functionality available to clients connecting to the API. When the server receives a request, it is validated and executed according to the schema. The schema defines the **type system_, which is the set of data that can be validated, queried, and executed on the API.

Basically, we can divide the type system into two categories:

  • types that describe the data model handled by the API (objects, scalar and enum);
  • types that identify the permitted operations on the data model (services and input objects).

As stated in the introduction, the syntax chosen to write a schema takes the name Schema Definition Language (SDL), is similar to query language and, like the latter, is independent of the server implementation.

Objects and fields #

At the base of the data model, we find objects (GraphQL Object Type, or simply object type); in SDL, an object is identified by the keyword type and simply represents a set of fields that you can retrieve from the server in an aggregate structure.

type Employee {
  _id: ID
  full_name: String
  age: Int
  team: Team
}

type Team {
  _id: ID
  name: String
}

A name characterizes each field of an object and a type; a field may itself contain an object (e.g., the team field of Employee is of type Team), or resolve into a simple piece of information (e.g., full_name and age of Employee). We will discuss primitive data types later.

The following modifiers can be applied to the field type:

  • !: the field is non-nullable. The server promises always to return a value when requested;
  • [<TypeName>]: the field returns a list of values of that type.

Finally, a field can be characterized by zero or more arguments, collected in round brackets as in the example:

type Foo {
  campoSemplice: String
  campoNonNull: String!
  campoConArgomenti(arg1: String!, arg2: Int = 42): String
}

An argument can also be marked with ! to make it mandatory; in that case, the call is invalid if you request the field without specifying a value for that argument. Conversely, if the argument is not mandatory, it will take the default value as defined in the schema (e.g. 42 for arg2).

Services and Input Objects #

Allowed operations are collected in two special types: Query and Mutation. Both define the input point of each GraphQL call. By requesting one or more fields of Query, the client can navigate the object graph to read the information present; similarly, the fields of Mutation allow to send data to the server, modify the graph, and read the result of the write.

schema {
  query: Query
  mutation: Mutation
}

type Query {
  # una query
  Employee___get(_id: ID!): Employee
}

type Mutation {
  # una mutation
  Employee___create(data: EmployeeCreate!): Employee
}

As the example suggests, all services are parametric and have at least one mandatory argument in our implementation. What we didn’t mention by introducing arguments in the previous paragraph is that it is possible to pass objects as arguments (e.g., the Employee___create service requires the data argument of type EmployeeCreate). An input object of this type is called an input type, and is identified in the schema by the keyword input:

input EmployeeCreate {
  full_name: String
  age: Int
  team: Team
}

Service naming conventions #

In practice, Query and Mutation are normal object type, and their fields have as their type the data structure returned by the server upon execution of the operation. In the example above, both Employee___get and Employee___create return an Employee object, but the former already exists on the server and is requested to be read, while the latter is read contextually to its creation.

But what then are Employee___get and Employee___create? By convention, we use the term service in this documentation to refer to the fields of Query and Mutation:

  • with read service or query type service, we refer to the name of a field in Query;
  • with write service or mutation service, we refer to the name of a field of Mutation;

Primitive data types #

Primitive data types are named scalar; they represent the “leaves” of a query, always resolve concrete data (such as strings or numbers), and have no subfields.

System Scalar #

GraphQL handles the following types of scalar:

  • String: alphanumeric sequences of characters expressed in UTF-8 encoding;
  • Int: 32-bit signed integers;
  • Float: double-precision signed floating-point numbers;
  • Boolean: logical values true and false;
  • ID: an identifier used to identify resources and objects uniquely. It is intended to be human-readable.

All homonymous data types handled by Livebase are mapped in GraphQL to the corresponding scalar type (e.g., an attribute of type string in the model is a String in GraphQL). The platform attribute __id is mapped to the ID type, while the Float type is not used.

Livebase scalar #

Our implementation also adds the following scalar to map the remaining data types:

  • Real: analogous to the real type;
  • Date: analogous to the date type;
  • Time: analog of the time type;
  • Datetime: analog of the datetime type;
  • Text`: analogous of text;
  • Serial: analog of serial;
  • Year: analog of year;

Tutti i tipi elencati sono serializzati come stringhe, a eccezione di Year che è serializzato come numero intero (come Int).

Dates and numbers formats #

The representation format of fields of type Date, Time, Datetime or Real depends on the Cloudlet user’s custom display settings and the Cloudlet’s own default settings.

In read services that return Date, Time, or Datetime, the optional format argument, of type String, is available on fields of this type, allowing the user to specify a format (among those supported by the Cloudlet) other than the one set for the user.

type Employee {
  _id: ID
  date_of_birth(format: String = "default"): Date
  # altri campi
}
type Query {
  Employee___get(_id: ID!): Employee
}
query {
Employee___get(id: "10101") {
date_of_birth(format: "MMM-d-yyyy")
}
}

Enumerated #

We will also find another type of object in the generated schema are enumerates (enum). An enum is a special type of scalar, limited to a given set of allowed values.

Model - schema mapping #

For each application view (Application Schema) defined in the model, a separate GraphQL API is generated. The schema of each API respects the constraints, manageability, filters and permissions defined in the model for that specific application view.

Each class <ClassName> of the model (among those enabled for that application view and reachable), is mapped to the schema in the type <ClassName> structure, to access which several services are generated, whose names follow the format <ClassName>___<ServiceType>>. As it is true for all generated application interfaces, in GraphQL, each service works on the graph of objects reachable from that class; for this reason, services are generated only for main classes and not for part classes, since the management of these objects is subordinated to the whole classes.

In addition to <ClassName>, additional object type and input type are generated to support services on that class; the names of these structures follow the format type <ClassName><TypeName> or input <ClassName><InputName> (e.g., type EmployeePage or input EmployeeCreate).

Attributes on <ClassName> are mapped as follows:

  • the name of the attribute matches the name defined on the model, while the type follows the mapping shown in Primitive data types;
  • by default, the platform attribute __id is made visible for all classes, with name _id of type ID (in GraphQL, the double underscore __ is a prefix reserved for metadata);
  • in input structures, required attributes in the model are marked with !;

An optional field exclusive to the GraphQL schema is also enabled on both ClassName and its supporting structures: _clientId: ID. In write services, clients may fill this field with arbitrary values (e.g. hash or timestamp).

The outgoing roles on <ClassName> are mapped differently depending on the type of service; see Read services and Write services for more information.

Response Structure #

GraphQL emphasizes consistency and predictability of results, so server responses always have a predictable structure. Let’s now look at the common format of all GraphQL responses (the query format is described here: Formulating Query and Mutation).

The JSON object returned by the server can take two forms:

  • If the request was executed with success, there will be a data field, containing, for each service invoked by the query, a key with the name of the service and as value, the object returned by the service.

    {
      "data": {
        "className___ServiceType": { ... }
      }
    }
    
  • If the server encountered errors in resolving the request, there will be a errors field, containing a list of the errors encountered.

    {
      "errors": [ ... ],
      "data": {
        "ClassName___ServiceType": null
      }
    }
    

According to the specification, if no errors are returned, the errors field is absent, while if no data is returned for a service, the data field will still contain the service name, with value null.

The elements of the errors field have, in turn, a defined structure. Each error contains the following fields:

  • message: a string with the description of the error. It is the only field that is always present;
  • locations: array of locations, each characterized by the fields line and column, where an error occurred;
  • path: array of paths, represented by strings (in case of fields) and integers (in case of indexes);
  • extensions: additional information provided by the server (see Issue).

The error types fall into two categories: errors in the request and errors occurring on the server. In the first case, the client has sent a request that is syntactically invalid or violates type checking, i.e. GraphQL schema validation. In the second case, the client sent a correct request, but errors occurred on the server during its resolution.

Detail: a request that violates type checking.

Server-side errors (Issues). #

When it receives a valid call, the server checks that all constraints defined on the model for the elements involved (in the context of the application view on which the schema is defined) are met. For example, in a write, the server checks if constraints on attributes are respected and if the user has a profile that allows him to write to that attribute.

In detail, the server analyzes the call in three steps, checking for each step a given category of constraints:

  1. Grant: constraints related to the rights of the user profile on the model elements available in the schema, defined at the Profile Schema and Permission Schema level. Grants are controlled for both reads and writes, as a model element may be on in an application but off for a profile accessing it.
  2. Veto Action: constraints on Class Warnings (defined at the Application Schema level) set to be evaluated following a user action on the web client. GraphQL “simulates” an action when invoking a writing service.
  3. Data Validation: constraints related to input validation, checked only in writes. They include constraints related to Class Warnings evaluated due to the persist on the database and the same Database Schema level constraints on attribute domains, role cardinality, and unique constraints.

If an error occurs in any of the listed steps, the server stops retrieving data and returns an error. The structure to represent the error is the Issue object, placed in the extensions field:

type Issue {
  userMessage: String
  issueLevel: IssueLevel
  issueReferenceType: IssueReferenceType
  issueType: IssueType
  entityName: String
  entityID: ID
  attributeNames: [String!]
  roleNames: [String!]
  applicationName: String!
  profileName: String!
  traceId: String
}

Let’s examine it field by field:

  • userMessage is a message designed for display in the client to notify the user of the problem;
  • issueLevel, issueReferenceType, and issueType describe hierarchically the type of problem that occurred. The related structures are of type enum. More information in detail;
  • entityName and entityID refer to the Entity (intended as a model class), or the particular instance on which the problem was encountered;
  • attributeNames and roleNames enrich the context, indicating which attributes/roles of the entity are involved. They are valued only if issueReferenceType is ENTITY_ATTRIBUTE or ENTITY_ROLE;
  • applicationName and profileName indicate on which application view and with which profile the problem was encountered, respectively;
  • traceId is an internal support identifier useful for tracing the request made for a bug report.
Detail: ``IssueLevel,IssueReferenceType, andIssueType`.

Debug a request #

All generated services have an optional insight argument. This, if included, allows you to ask the server for information about the data retrieval process (e.g., execution time); this is useful for identifying possible bottlenecks or bugs in the GraphQL server.

query {
  # allowed values: FULL, LIGHT
  Employee___get(id: "10101", insight: FULL) {
    full_name
  }
}

In the response, the required insight information is in an additional extensions field, at the same level as data and errors.

{
  "data": { "Employee___get": { ... }},
  "extensions": { ... }
}

Introspection #

In scenarios where GraphiQL cannot be accessed, the generated API still supports introspection as an alternative tool to discover information about the schema. This means that the following standard read services are available:

  • __schema: retrieves metadata about all types defined on the schema and the schema itself;
  • __type(name: String!): retrieves metadata about a type on the schema, given its name.

For more information about introspection service fields, please refer to the official documentation: Introspection.

Glossary #

Below we have collected, in alphabetical order, the GraphQL terms used in this guide.

Argument #
Key-value pair associated with a specific field. A field can have zero or more arguments. All services require at least one input type as an argument.
Enum #
Special type of scalar, restricted to a given set of allowed values, identified by the keyword enum.
Field #
Unit of data that belongs to a type in the schema. Each GraphQL call requires one or more fields on the root object.
Input type #
Special type of object type used as an argument, identified by the keyword input.
Mutation #
A GraphQL operation that creates, modifies, or destroys data.
Object type #
A type in the GraphQL schema containing fields. It is identified by the keyword type.
Operation #
A single query or mutation that the GraphQL server can interpret.
Operation name #
An arbitrary name can be assigned to an operation to distinguish it from others. In GraphiQL, it is mandatory to give a name to each operation if there is more than one query in the same window.
Operation type #
The type of the GraphQL call. It can be either query or mutation. If omitted, the default is query.
Type system #
A collection of types that characterizes the set of data that can be validated, queried and executed on the API.
Query #
Read-only retrieval operation to request data from the GraphQL API.
Root object #
Entry point to the schema containing all available services of the type chosen for the operation.
Scalar #
Type that qualifies data resolved from a GraphQL field, such as strings or numbers. It represents the “leaf” of a query. More information: System Scalar and Livebase Scalar.
Schema #
The schema is at the core of the GraphQL server implementation and describes the functionality available to clients connecting to the API and defines the type system of the GraphQL API.
The schema resides on the server and is used to validate and execute client calls. A client can request information about the schema via introspection.
Service #
A field of type Query or of type Mutation. In the former case, it is a read service, in the latter case a write service. Multiple services of the same type as the operation can be merged in an operation (e.g., a query may require multiple read services).
Variable #
A value that can be passed as an argument to an operation. Variables can be used to replace arguments or be passed as Directives.

Reference #

Types of Issues #

Below is the complete list of IssueTypes returned by the server.

Category: generic error #

IssueTypeDescription
SERVER_ERRORGeneric server error
MALFORMED_REQUESTThe call is valid against the GraphQL schema, but has not passed server validation.

_Examples: the filter_exp field of the <ClassName>PageOptions input

contains an invalid Livebase expression / usage of _id in the <ClassName>Draft of a formAction service._

DATA_TYPEA mutation contains a scalar field valued with an invalid string with respect to the data type.

Example: invalid strings for fields of type ID, date, time, datetime or real (_id: "hello", date: "12345"…)

ENTITY_NOT_FOUNDA mutation mentions the ID of a non-existent object on the database, via the _id field (derived from the SPI EntityNotFoundExceptionexception).
ENTITY_ATTRIBUTE_NOT_FOUNDThe filter_exp field of the <ClassName>PageOptions input contains a Livebase expression mentioning a non-existent attribute on the class (derived from the EntityAttributeNotFoundException exception of the SPI).
SERVICE_HANDLER_ERRORServer error raised by the code of a Plugin, invoked through a formAction type mutation.
ENTITY_LOCK_EDITA mutation mentions the ID of an object of which another Cloudlet user has acquired a temporary lock on the database.

Category: Data validation #

IssueTypeDescription
ENTITY_UNIQUEA mutation violates a unique constraint on the class affected by the write.
ENTITY_DOMAINA mutation violates a constraint defined with a Class warning, configured to be evaluated following a persist action.
ATTRIBUTE_REQUIREDA mutation omits a field related to a required attribute.
ATTRIBUTE_RANGEA mutation contains a value for a scalar field outside the allowed range of values.

Example: out-of-range value for attributes of type integer, real, year, date, time, or datetime.

ATTRIBUTE_REAL_DECIMAL_DIGITSA mutation contains a value for a field of type real with an invalid number of decimal places.

Example: hourly_cost: "3,12345" (by default, the allowed decimal digits are 2).

ATTRIBUTE_STRING_LENGTHA mutation contains a value for a field of type string that violates the maximum or minimum allowed length.

Example: phone_number: "+39 3331111222333444", con lunghezza minima 10.

ATTRIBUTE_FILE_SIZEA mutation attempts to associate, with a field of type file, a pending file that exceeds the maximum allowed file size for that attribute.
ATTRIBUTE_FILE_TYPEA mutation attempts to associate, with a field of type file, a pending file whose type is not among those allowed for that attribute.
ROLE_CARDINALITYA mutation exceeds the minimum or maximum cardinality allowed for a role involved in the write.

_Example: The qualification_->add field of the inputEmployeeCreate contains five references, but on that role the maximum cardinality is 3._

Category: Veto action #

IssueTypeDescription
ENTITY_EDIT_VETOA mutation of type create, update or save violates a constraint defined with a Class warning, configured to be evaluated following a Create or Edit event.
ENTITY_DELETE_VETOA mutation of type delete violates a constraint defined with a Class warning, configured to be evaluated following a Delete event.

Category: Grant #

IssueTypeDescription
APPLICATION_ACCESS_FORBIDDENThe user does not have an enabled profile to interact with the Application Schema / GraphQL endpoint that the call was directed to.

Example: A user with profile “staff manager” has called on the /administration endpoint, for which it is not enabled, instead of /staff_management.

ENTITY_GRANT_READThe user does not have a profile enabled to read a class among those involved in the query.
ENTITY_GRANT_CREATEThe user does not have a profile enabled to read a class among those involved in the query.
ENTITY_GRANT_EDITThe user does not have a profile enabled to modify objects of a class in a mutation of type update or save.
ENTITY_GRANT_DELETEThe user does not have a profile enabled to delete objects of a class in a mutation of type delete.
ATTRIBUTE_GRANT_READThe user does not have a profile enabled to read one or more of the attributes involved in the query.
ATTRIBUTE_GRANT_EDITThe user does not have a profile enabled to write one or more of the attributes involved in the mutation.
ROLE_GRANT_READThe user does not have a profile enabled to expand one or more of the roles involved in the query.
ROLE_GRANT_CREATEThe user does not have a profile enabled to create part / associate objects on a role in a mutation of type create or save.
ROLE_GRANT_EDITThe user does not have a profile enabled to edit a role in an update or save mutation.
ROLE_GRANT_DELETEThe user does not have a profile enabled to delete part / remove associations in a delete mutation.