You can treat GraphQL schema wrapping and transformation as a form of automated API documentation and governance.

Let’s say you have an existing REST API. You want to expose it as GraphQL. You could write a whole new GraphQL server, but that’s a lot of work. Instead, you can use a tool like graphql-http or Apollo Server to create a GraphQL endpoint that acts as a wrapper around your REST API.

Here’s a simplified example using Node.js and express-graphql:

const express = require('express');
const graphqlHTTP = require('express-graphql');
const { buildSchema } = require('graphql');
const axios = require('axios'); // To make HTTP requests to your REST API

// Your existing REST API endpoint
const REST_API_URL = 'http://localhost:3000/users';

// Define your GraphQL schema
const schema = buildSchema(`
  type User {
    id: ID!
    name: String!
    email: String
  }

  type Query {
    users: [User!]!
    user(id: ID!): User
  }
`);

// Resolver functions to fetch data from your REST API
const rootValue = {
  users: async () => {
    const response = await axios.get(REST_API_URL);
    return response.data; // Assuming your REST API returns an array of user objects
  },
  user: async ({ id }) => {
    const response = await axios.get(`${REST_API_URL}/${id}`);
    return response.data; // Assuming your REST API returns a single user object
  }
};

const app = express();
app.use('/graphql', graphqlHTTP({
  schema: schema,
  rootValue: rootValue,
  graphiql: true, // Enable the GraphiQL interface
}));

app.listen(4000, () => {
  console.log('GraphQL server running on http://localhost:4000/graphql');
});

When a GraphQL query comes in, graphql-http (or Apollo Server) intercepts it. It parses the query, figures out which fields are requested, and then calls the corresponding resolver functions. In our example, the resolvers make HTTP requests to the underlying REST API. The data is then transformed into the shape defined by your GraphQL schema and sent back to the client.

This pattern isn’t just about exposing REST as GraphQL. You can also use it to:

  • Aggregate multiple data sources: Your GraphQL resolvers can fetch data from different REST APIs, databases, or even other GraphQL services.
  • Add authorization and validation: You can implement logic within your resolvers to check permissions or validate incoming arguments before hitting the underlying data source.
  • Transform data formats: If your REST API returns XML, your resolvers can parse it and return JSON. If it returns snake_case fields, you can map them to camelCase in your GraphQL schema.
  • Version your APIs: You can have multiple GraphQL schemas pointing to different versions of your underlying services, allowing for controlled migration.

The real power comes when you start thinking about how this enables granular control over your API’s interface. Instead of exposing your entire REST API, you can carefully craft a GraphQL schema that exposes only what’s necessary, in the exact shape clients need. This decouples clients from the internal implementation details of your backend services.

Consider a scenario where your REST API returns a user object like this:

{
  "user_id": 123,
  "full_name": "Jane Doe",
  "contact_details": {
    "email_address": "jane.doe@example.com",
    "phone_number": "555-1234"
  }
}

And you want your GraphQL API to look like this:

type User {
  id: ID!
  name: String!
  email: String
}

Your resolver would then look something like this:

// ... inside rootValue
user: async ({ id }) => {
  const response = await axios.get(`${REST_API_URL}/${id}`);
  const restUserData = response.data;
  return {
    id: restUserData.user_id.toString(), // Transform ID type
    name: restUserData.full_name,         // Rename field
    email: restUserData.contact_details?.email_address // Deeply nested field
  };
}
// ...

This transformation layer is where much of the value lies. It’s not just about connecting two systems; it’s about creating a curated, governed, and often more developer-friendly API layer on top of existing, potentially complex, or legacy systems. The GraphQL schema becomes the single source of truth for what data is available and how it’s structured, abstracting away the heterogenous nature of the underlying data sources.

A common, and often overlooked, aspect of this wrapping is how mutations are handled. You’re not just reading data; you’re also writing it. When you define a Mutation type in your GraphQL schema, the resolvers for those mutations will typically make POST, PUT, or DELETE requests to your underlying REST API. The arguments you define for your GraphQL mutation become the payload for these HTTP requests. You can also use this layer to transform your GraphQL mutation arguments into a different format required by the REST API, or to perform validation that the REST API itself might not enforce.

The next step is to explore how to use schema stitching or federation to combine multiple such wrapped GraphQL APIs into a single, unified GraphQL gateway.

Want structured learning?

Take the full Graphql-tools course →