bare vs wrap transforms

By default, most transforms work by wrapping the original schema. bare mode replaces the original schema with the transformed one.

Although both bare and wrap modes achieve the same result, their behaviors are very different.

Supported modes

The following table illustrates how bare and wrap modes are supported across different transforms:

Transform
Bare
Wrap
Docs
Encapsulate
docs
Federation
docs
Filter Schema
docs
Naming Convention
docs
Prefix
docs
Rename
docs
Replace Field
docs

bare

Bare mode 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 defined transform rules, and returns an updated version of the original schema.

The transformed schema replaces the original schema from the handler, so API Mesh deals with the transformed schema only, as opposed to an original schema plus one or more wrapping layers.

bare mode is recommended, however, there are a few restrictions to consider.

Restrictions

bare mode provides performance improvements over wrap mode, however, it needs to access the bare schema. Here are some reasons this might not work:

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 you can reduce the number of wrapping layers that would otherwise be created if not using bare at all.

Example:

{
  "sources": [
    {
      "name": "Countries",
      "handler": {
        "graphql": {
          "endpoint": "https://api.../graphql"
        }
      }
    },
    {
      "name": "Users",
      "handler": {
        "openapi": {
          "source": "https://api.../swagger.yaml"
        }
      },
      "transforms": [
        {
          "rename": {
            "mode": "bare",
            "renames": [
              {
                "from": {
                  "type": "User",
                  "field": "lastName"
                },
                "to": {
                  "type": "User",
                  "field": "surname"
                }
              }
            ]
          }
        }
      ]
    }
  ],
  "merger": "bare",
  "transforms": [
    {
      "rename": {
        "mode": "bare",
        "renames": [
          {
            "from": {
              "type": "Country",
              "field": "ISO-3166_Code"
            },
            "to": {
              "type": "Country",
              "field": "code"
            }
          }
        ]
      }
    }
  ]
}

wrap

data-variant=warning
data-slots=text
wrap mode adds significant overhead and can cause longer processing times. When using API Mesh, we strongly recommend using bare mode.

The wrap mode 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. Rather than updating it, 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

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

Schema wrapping is performed during initialization only and so won't affect runtime GraphQL operations. However, transforms that alter the original schema shape using wrap mode, achieve this by intercepting both the incoming request and the original response to do the mapping required to transform the original schema into the desired shape.

Not all transforms require interception of both request and response. Some require straightforward mapping, so the runtime overhead could hopefully be negligible; however, there will always be some.

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 needs to be applied to RootFields, ObjectFields, and InputObjectFields.

As explained in the previous point, the wrapping layers are registered during initialization only. However, each wrapping layer will always have some runtime implications, even if hopefully negligible.

data-variant=info
data-slots=text
wrap is the only approach that works with data sources that already "speaks" GraphQL, or when you want to transform all sources at the (root) level unless you're using merger-bare. If you want to remove the possible runtime implications, consider either moving your transforms from the data source level or opting into merger-bare to take advantage of bare mode.

Example:

{
  "sources": [
    {
      "name": "Countries",
      "handler": {
        "graphql": {
          "endpoint": "https://api.../graphql"
        }
      },
      "transforms": [
        {
          "rename": {
            "mode": "wrap",
            "renames": [
              {
                "from": {
                  "type": "Country",
                  "field": "admin1Admins"
                },
                "to": {
                  "type": "Country",
                  "field": "admin1"
                }
              }
            ]
          }
        }
      ]
    },
    {
      "name": "Users",
      "handler": {
        "openapi": {
          "source": "https://api.../swagger.yaml"
        }
      },
      "transforms": [
        {
          "rename": {
            "mode": "wrap",
            "renames": [
              {
                "from": {
                  "type": "User",
                  "field": "lastName"
                },
                "to": {
                  "type": "User",
                  "field": "surname"
                }
              }
            ]
          }
        }
      ]
    }
  ],
  "transforms": [
    {
      "rename": {
        "mode": "wrap",
        "renames": [
          {
            "from": {
              "type": "Country",
              "field": "ISO-3166_Code"
            },
            "to": {
              "type": "Country",
              "field": "code"
            }
          }
        ]
      }
    }
  ]
}
data-variant=info
data-slots=text
When you want to use wrap, you can omit the mode property since this is already applied by default.