book

GraphQXL is an extension of the GraphQL language with some additional features that help creating big and scalable server-side schemas.

When following a schema-first approach, there are some challenges that are left unaddressed compared to a code-first approach.

  • With a code-first approach, you have all the tools the programming language you are using provides, like functions for automatically generating repetitive types, inheritance for reusing code pieces, and so on.

  • With a schema-first approach you need to write by hand all the repetitive entities in your schema, and if you want to split your schema across different files for a better maintainability you are bound to language-specific tools for merging them.

GraphQXL provides additional syntax to the original GraphQL language for solving this challenges and making defining server-side GraphQL schemas a nicer experience, without being bound to language-specific tools. At the end of the day, GraphQXL is just a binary executable that will compile your GraphQXL schemas into GraphQL, independently of the programming language that you are using for the backend.

Installation

You can just download the precompiled binary from GitHub releases or use a language-specific wrapper for a better integration with your build tools.

Precompiled binary

There are precompiled binaries that you can download from GitHub releases:

Mac M1

wget https://github.com/gabotechs/graphqxl/releases/latest/download/graphqxl-aarch64-apple-darwin.tar.gz
tar -xvf graphqxl-aarch64-apple-darwin.tar.gz

Mac Intel

wget https://github.com/gabotechs/graphqxl/releases/latest/download/graphqxl-x86_64-apple-darwin.tar.gz
tar -xvf graphqxl-x86_64-apple-darwin.tar.gz

Linux x86_64

wget https://github.com/gabotechs/graphqxl/releases/latest/download/graphqxl-x86_64-unknown-linux-gnu.tar.gz
tar -xvf graphqxl-x86_64-unknown-linux-gnu.tar.gz

Linux aarch64

wget https://github.com/gabotechs/graphqxl/releases/latest/download/graphqxl-aarch64-unknown-linux-gnu.tar.gz
tar -xvf graphqxl-aarch64-unknown-linux-gnu.tar.gz

Language-specific wrappers

Node

node-graphqxl

yarn add -D node-graphqxl

Or

npm install --save-dev node-graphqxl

Usage

GraphQXL is just a compiler that will receive .graphqxl files as an input and will compile them down to a common .graphql file.

For example, given this foo.graphqxl file:

# foo.graphqxl
type MyType {
    foo: String
}

You can do

graphqxl foo.graphqxl

And the file will get compiled to foo.graphql:

# foo.graphql
type MyType {
    foo: String
}

Features

So, the example above was not very useful, as the compilation result is exactly the same as the input. Here is a list of more useful things you can do with GraphQXL:

Inheritance

There will be times that a lot of types or inputs have some fields in common. In GraphQXL you can inherit fields between types and inputs using spread operators.

Spread operator

types and inputs can inherit fields from other types and inputs using spread operators, for example:

Open in sandbox

Source GraphQXL Compiled GraphQL
type Common {
    "Type's ID"
    id: ID!
    "Type's Name"
    name: String!
    "Type's description"
    description: String!
}

type Product {
    ...Common
    "Product's price"
    price: Float!
}





type Common {
    "Type's ID"
    id: ID!
    "Type's Name"
    name: String!
    "Type's description"
    description: String!
}

type Product {
    "Type's ID"
    id: ID!
    "Type's Name"
    name: String!
    "Type's description"
    description: String!
    "Product's price"
    price: Float!
}

Private fields

It is very common that you do not want to expose the Common type in the public API, so you can make it private by prefixing the name with a _ character (or the prefix that you provide in the CLI argument):

Open in sandbox

Source GraphQXL Compiled GraphQL
type _Common {
    "Type's ID"
    id: ID!
    "Type's Name"
    name: String!
    "Type's description"
    description: String!
}

type Product {
    ..._Common
    "Product's price"
    price: Float!
}
type Product {
    "Type's ID"
    id: ID!
    "Type's Name"
    name: String!
    "Type's description"
    description: String!
    "Product's price"
    price: Float!
}




Inheriting interfaces

A common pattern is to declare a GraphQL interface and to implement it in a type, but you need to rewrite all the fields in the type that belong to the interface as they are not implicit. You can use the spread operator also with interfaces:

Open in sandbox

Source GraphQXL Compiled GraphQL
interface Person {
    parent: String!
    childs: [String!]!
}

type Dad implements Person {
    ...Person
    job_title: String!
}

type Kid implements Person {
    ...Person
    school_name: String!
}


interface Person {
    parent: String!
    childs: [String!]!
}

type Dad implements Person {
    parent: String!
    childs: [String!]!
    job_title: String!
}

type Kid implements Person {
    parent: String!
    childs: [String!]!
    school_name: String!
}

Generics

Generics can be used to reuse types or inputs that have some small subset of the fields slightly different from each other:

Open in sandbox

Source GraphQXL Compiled GraphQL
type Generic<T> {
    foo: T
}

type FooString = Generic<String!>

type FooInt = Generic<Int!>
type FooString {
    foo: String!
}

type FooInt {
    foo: Int!
}

Notice how the generic type definition is omitted from the generated GraphQL. If a type or an input is declared with generic type parameters, it will not be present in the generated GraphQL.

It can even be combined with inheritance:

Open in sandbox

Source GraphQXL Compiled GraphQL
type Book {
    title: String!
}

type List<T> {
    first: T
    last: T
    content: [T!]!
}

type ListOfBooksWithLength {
    ...List<Book>
    length: Int!
}
type Book {
    title: String!
}

type ListOfBooksWithLength {
    first: Book
    last: Book
    content: [Book!]!
    length: Int!
}




Modifiers

Modifiers are like built-in generic types that will modify the provided type based on some rules. Users cannot define their own modifiers, only use the built-in ones.

These are the currently available modifiers:

Optional

The Optional modifier takes a type or an input as a parameter and outputs a similar object with all its fields marked as "nullable". If the field in the original object was declared required with a ! the output will contain that field without the !, otherwise the field is left untouched.

Open in sandbox

Source GraphQXL Compiled GraphQL
type _SomeType {
    foo: String!
    bar: Int!
    bool: [Float!]
}

type OptionalType = Optional<_SomeType>
type OptionalType {
    foo: String
    bar: Int
    bool: [Float!]
}


Required

The Required modifier takes a type or an input as a parameter and outputs a similar object with all its fields marked as "non-nullable". If a field in the original object did not have a !, it will have it in the new object, otherwise it will be left untouched

Open in sandbox

Source GraphQXL Compiled GraphQL
type _SomeType {
    foo: String
    bar: Int
    bool: [Float]!
}

type RequiredType = Required<_SomeType>
type RequiredType {
    foo: String!
    bar: Int!
    bool: [Float]!
}


Imports

Import statements allow you to split your schema across different files without relying on any opinionated merging tool or some language-specific merging script. This is useful for keeping your schema's code base clean and for improving its maintainability.

An import statement will resolve all the content of the imported file in the current one, for example:

Source GraphQXL Compiled GraphQL
# common-stuff.graphqxl

type Identifier {
    id: ID!
}
# product.graphqxl

import "common-stuff"

type Product {
    id: Identifier
    price: Float!
}
# product.graphql

type Identifier {
    id: ID!
}

type Product {
    id: Identifier
    price: Float!
}





A good pattern for keeping your schema clean could look something like this:

import "products"
import "users"
# import other entities

type Query {
    products: [Product!]!
    product(id: ID!): Product
    users: [User!]!
    user(id: ID!): User
    # queries for the other entities
}

type Mutation {
    createProduct(input: CreateProductInput!): Product!
    deleteProduct(id: ID!): Product!
    createUser(input: CreateUserInput!): User!
    deleteUser(id: ID!): User!
    # mutations for the other entities
}

schema {
    query: Query
    mutation: Mutation
}

Description templates

Let's say that you have a type that you want to reuse in other types:

Source GraphQXL Compiled GraphQL
type _ToBeReused {
    "Field foo from type 'ToBeReused'"
    foo: String!
}

input Bar {
    ..._ToBeReused
}
input Bar {
    "Field foo from type 'ToBeReused'"
    foo: String!
}




There, foo's description says that the field foo belongs to the type ToBeReused, but once that is compiled it is not true, it should say something like:

input Bar {
    "Field foo from input 'Bar'"
    foo: String!
}

Description string interpolation

You can use template variables in the description strings to refer to some contextual values:

Open in sandbox

Source GraphQXL Compiled GraphQL
type _ToBeReused {
    "Field foo from ${{ block.type }} '${{ block.name }}'"
    foo: String!
}

input Bar {
    ..._ToBeReused
}

type Baz {
    ..._ToBeReused
}
input Bar {
    "Field foo from input 'Bar'"
    foo: String!
}

type Baz {
    "Field foo from type 'Baz'"
    foo: String!
}



These are the available values for the templates:

  • block.name: The parent's block name
  • block.type: The parent's block type (type or input)
  • variables.YOUR_GENERIC_VARIABLE: The value of the generic variable once instanced