The Guild LogoThe Guild Monogram

Search docs

Search icon

Products by The Guild

Products

Hive logoHive blurred logo

Hive

Schema Registry for your GraphQL Workflows

Skip to main content

Schema Transformation

Introduction to Transforms#

GraphQL Mesh allow you to do schema transformations easily, you can use one of the built-in transforms, or write your own.

Each transformer can manipulate the schema the way it needs, and return the modified schema.

Transforms are specified as a list of objects, and they are executed in order, and you can apply them over a specific input source, or over the unified schema (after merging all sources).

Handler-level transforms#

To specify transforms over a specific source, add it to your sources section under the source you wish to modify.

The following example prefixes an input source to make it simpler later to merge and avoid conflicts:

sources:
- name: Wiki
handler:
openapi:
source: https://api.apis.guru/v2/specs/wikimedia.org/1.0.0/swagger.yaml
transforms:
- prefix:
value: Wiki_

Root-level transforms#

To specify transforms over unified schema, you should put it in the root of your config file. This could be used in case you need access fields or types from all your data source, for example for linking two data sources together.

The following example prefixes an input source to make it simpler later to merge and avoid conflicts:

sources:
- name: Users
handler: #...
- name: Posts
handler: #...
transforms:
- cache:
- field: Query.user
cacheKey: user-{args.id}
invalidates:
effectingOperations:
- operation: Mutation.updateUser
matchKey: { args.userIdToUpdate }

The example above uses cache transform on root and when someone uses updateUser with a specific user id, it will update the data record, and then invalidate the cache automatically.

You can learn more about caching in the dedicated docs.

Two different modes#

By default, most of the transforms manipulating schemas, works by wrapping the original schema; but recently we have also introduced a new "bare" mode to replace the original schema with the transformed one.
Although apparently both bare and wrap modes do achieve the same result, their behaviours are very different.
Let's take a look at how they operate.

Wrap#

Wrap applies transformations by adding a wrapping layer to the original GraphQL schema. The handler generates a GraphQL schema and passes it to the transform. When in "wrap" mode, the transform, receives this schema and rather than updating this, it will apply a layer on top of it; with the scope of serving your transformations as an addition to the original schema generated by the handler.
This approach is safe as we have used it extensively in graphql-tools; however, be mindful of the implications below.

Implications#

Wrap is the default mode for schema manipulation transforms, because is safe and works across all data sources. However, you might want to be aware of the following implications.

  • Runtime implications
    Schema wrapping is performed during initialisation only and so won't affect runtime GraphQL operations. However, transforms altering the original schema shape using "wrap" mode, achieve this by intercepting both the incoming request and original response in order to do the mapping required to transform the original schema into the desired shape.
    Not all transforms require interception of both request and response and some require very simple mapping, so the runtime overhead could hopefully be negligible; however there will always be some.
  • Multiple wrapping layers
    When using "wrap" mode, the required transformation can be achieved by adding at least one wrapping layer per each transform rule defined. We cannot have a wrapping layer per transform, but we need one per rule since each rule is unique in the way it transforms different parts of the schema. Some rules might even require multiple wrapping layers, f.i. when transforming a field the transform need to be applied to RootFields, ObjectFields and InputObjectFields.
    As explained on the previous point, the wrapping layers are registered during initialisation only, however, each wrapping layer will always have some runtime implications, even if hopefully negligible.
  • Working with fixed-schema sources
    As mentioned, "wrap" is the only mode that works for sources that "speaks" GraphQL natively. However, when you work with fixed schema sources, such as JSON-schema, OpenApi, SOAP, etc.; schema wrapping might have some undesired effects; f.i. you won't have access to the original "fixed-contract" response from your data source.
    This might be not ideal, for example, when implementing custom resolvers, where you mght want to access several properties returned by your REST service in order to compute custom data; but instead you will only be able to access properties requested with the GraphQL query.
    If you don't want/can't opt into "bare" mode, this can be easily solved by explicitly declaring a SelectionSet, within your custom resolver, to list all properties required in order to compute your custom data.

NOTE: "wrap" is the only approach that works with data sources that already "speaks" GraphQL, or when you want to transform at all-sources (root) level, unless you're using merger-bare. If you want to remove the possible runtime implications, consider either moving your renames at the data source level, or opting into merger-bare; in order to take advantage of "bare" mode.

Example:

sources:
- name: Countries
handler:
graphql:
endpoint: https://api.../graphql
transforms:
- rename:
mode: wrap # bare won't work here, since this data source already "speaks" GraphQL
renames:
- from:
type: Country
field: admin1Admins
to:
type: Country
field: admin1
- name: Users
handler:
openapi:
source: https://api.../swagger.yaml
transforms:
- rename:
mode: wrap # you can use either wrap or bare here
renames:
- from:
type: User
field: lastName
to:
type: User
field: surname
transforms:
- rename:
mode: wrap # bare won't work here at all-sources (root) level, because you're not using merger-bare
renames:
- from:
type: Country
field: ISO-3166_Code
to:
type: Country
field: code

ProTip: When you want to use "wrap", you can omit the "mode" property since this is already applied by default.

Bare#

Bare is a recent addition and works by replacing the original schema. The handler generates a GraphQL schema and passes it to the transform. When in "bare" mode, the transform, receives the schema generated by your handler, applies the transform rules defined and finally returns an updated version of the original schema.
This means that the transformed schema replaces the original schema from the handler and so Mesh deals with the latter schema only, as opposed to an original schema plus a wrapping layer.
Bare mode does remove all the implications of "wrap" mode, however, be mindful of the restrictions below.

Restrictions#

Bare does provide performance improvements over "wrap", however it has a main restriction: it needs to access the bare schema. Here are some reasons why this might not work:

  • Your data source already "speaks" GraphQL
    In this case "bare" won't work as it cannot replace a native GraphQL schema. This is not the same as transforming a "translated" GraphQL schema (e.g. from JSON-schema, OpenApi, SOAP, etc.).
    The suggestion in this case is to apply "wrap" transforms to your GraphQL data sources and "bare" transforms to sources "translated" into GraphQL.

  • You are applying transforms at all-sources (root) level
    This means that "bare" would receive a composed GraphQL schema, rather than a bare and "translated" schema. If you do want to use "bare" at the root level, your only choice is to opt into merger-bare, which lets transforms access the bare schemas; because it merges sources without wrapping them. This works when you don't have (or you take care of) conflicts between your sources, and you are not applying root-level transforms to data sources that already "speaks" GraphQL.

  • You are mixing transforms that supports "bare" with transforms that don't
    Again, "bare" always needs to access the bare schema. If you define other transforms that don't support "bare" mode, you will most likely have troubles, since those transforms will apply a wrapping layer which will provide "bare" transforms the wrapping layer, as opposed to the original bare schema.
    In order to take advantage of "bare" performance improvements, the suggestion here is to apply "wrap" transforms at the all-sources (root) level and "bare" transforms within the data sources level; so that at least you are able to reduce the number of wrapping layers that would otherwise be created if not using "bare" at all.

Example:

sources:
- name: Countries
handler:
soap:
wsdl: http://webservices.../wso?WSDL
- name: Users
handler:
openapi:
source: https://api.../swagger.yaml
transforms:
- rename:
mode: bare # bare is a great choice here, at the data source level
renames:
- from:
type: User
field: lastName
to:
type: User
field: surname
merger: bare # this lets transforms access the bare schemas
transforms:
- rename:
mode: bare # bare will work here, at all-sources (root) level, because you're using merger-bare
renames:
- from:
type: Country
field: ISO-3166_Code
to:
type: Country
field: code

Modes support#

The table below illustrates how "bare" and "wrap" modes are supported across all transforms.
If you have use cases for which you would require to introduce either "bare" or "wrap" mode to one of the transforms, feel free to open a feature request.

TransformBareWrapDocs
Cachedocs
Encapsulatedocs
Extenddocs
Federationdocs
Filter Schemadocs
Mockdocs
Naming Conventiondocs
Prefixdocs
Renamedocs
Resolvers Compositiondocs
Snapshotdocs