GraphQL As A Migration Strategy For REST APIs

Traditional API architectures like SOAP and REST have been powering the web for a long time. But over the past five years, a change towards a more flexible and frontend-driven approach to API protocols has started, using GraphQL. At StepZen, we believe this change is an important one that will dictate the way we handle data through APIs for a very long time. But moving from a traditional API architecture like REST towards GraphQL can be challenging. Not only does your team need to adapt to GraphQL, but it also means you could be losing your investment in your current API architecture.

Luckily, you can create a clear migration path from REST to GraphQL using StepZen. You get the advantage of adding GraphQL to your stack, but you can still leverage all your current APIs. In this post, I'll show how you can use your existing REST API endpoints while migrating to GraphQL.

Mapping REST API endpoints to GraphQL

REST APIs traditionally require you to request data per entity. If you have an E-commerce API and want to retrieve a specific product, the data for this product comes from an endpoint called /API/products/[id]. The data for this endpoint would probably come from a database table with the same name.

But if you also want to get the ratings for this product, and the data is a different database table, the ratings should be requested from another endpoint. As REST APIs have this entity-based approach, the address would be available at the endpoint /API/products/[id]/rating.

Entity-based REST endpoint

When migrating to GraphQL, you can make use of this entity-based setup. You can map every REST API endpoint to a GraphQL type. This GraphQL type is the response of a GraphQL operation, typically a query when your REST API endpoint supports a GET request. The request's response to /API/products/[id] is a JSON object with the fields id, name, and thumbnail. This response can be translated to a GraphQL type called Product. You can do the same for the request to get the ratings for this product.

Mapping REST API endpoints to GraphQL

Typically when you create a GraphQL server, you need to write resolver code that gets the data from your data source and returns it according to your GraphQL schema. Every resolver would request data from one endpoint, or sometimes multiple, and the response of that request would correlate to a type in your GraphQL schema.

When using StepZen, you no longer have to write resolvers but define the REST API endpoint per GraphQL operation. With StepZen, you can resolve GraphQL operations to REST API endpoints using a custom directive called @rest that StepZen supports. This directive takes the endpoint of the REST API endpoint that you want to transform into a GraphQL operation. Next to the endpoint, you can also specify arguments and header information to handle authentication and caching.

If you want to turn the REST API requests to /API/products/[id] and /API/products/[id]/rating into GraphQL, you need to write the following GraphQL SDL to make that possible with StepZen:

type Product {
  id: ID!
  name: String
  thumbnail: String
}

type Rating {
  id: ID!
  productId: ID!
  score: Float
}

type Query {
  getProduct(id: ID!): Product @rest(endpoint: "/api/products/$id")
  getProductRating(productId: ID!): Rating
    @rest(endpoint: "/api/products/$productId/rating")
}

In the above example, the queries getProduct and getProductRating use the @rest directive. With this directive, you create a one-to-one mapping of a REST API endpoint to a GraphQL operation, which is still entity-based. But you now have complete control over the data returned by the GraphQL query instead of receiving the whole data structure of the REST API endpoint. If you only want to receive the name for a product, you can specify this in the query getProduct:

query {
  getProduct(id: 1) {
    name
  }
}

This query returns only the field name from the JSON response of the REST API endpoint /API/products/1. All the fields that you don't specify cannot be queried from GraphqL. It doesn't stop here as StepZen can do much more. By combining the @rest directive with other StepZen directives, you can also combine REST API endpoints in one GraphQL query.

Combining REST API endpoints

Mapping your REST API endpoints to a single GraphQL query is the beginning, as the true power is combining multiple endpoints into one GraphQL operation. This allows you to remove the REST API constraints of dealing with entity-based requests. Together with the flexible data structure of the GraphQL response, this gives you more control over the output of the REST API endpoints. Combining these endpoints is done on the GraphQL type instead of the operation.

Combining REST API endpoints with StepZen

The REST API endpoints that you've previously mapped to a GraphQL type and operation can be combined with the @materializer directive. The type Product in the diagram above is the combined response type, of the queries getProduct and getProductRating. Most fields are coming from getProduct, only the field rating comes from getProductRating.

Implementing the @materializer directive can be done in any GraphQL schema built with StepZen. The field rating gets its data from the query getProductRating in the example below, and the field id is passed to this query as a parameter.

type Product {
  id: ID!
  name: String
  thumbnail: String
  rating: Rating
    @materializer(
      query: "getProductRating"
      arguments: [{ name: "productId", field: "id" }]
    )
}

When you're building your GraphQL API with StepZen, you can now query a product's rating as a relation. You don't need to write any additional resolvers, and the logic to get the standalone ratings for a product is reusable.

This query gets data from both /api/products/[id] and /api/products/[id]/rating:

query {
  getProduct(id: 1) {
    name
    rating {
      score
    }
  }
}

When you don't request the field rating, the @materializer directive is not querying the getProductRating query. Thus StepZen won't be calling the REST API endpoint /API/products/[id]/rating. This helps you solve the N+1 constraint that many REST API endpoints are suffering from.

Migration path with Authentication

When migrating REST to GraphQL, you use how REST APIs are typically set up by mapping the response of REST API endpoints to GraphQL types. Mapping an endpoint to GraphQL is done with the @rest directive, while REST API endpoint responses are combined with @materializer. In short, the migration path that we've discussed is as follows:

  1. Map REST API endpoints responses to GraphQL types
  2. Create operations for every REST API endpoint
  3. Combine GraphQL types using operations

But this migration path isn't complete, as no solutions to authentication is mentioned. How you can migrate authentication to GraphQL depends on how authentication is implemented in your REST API. A popular standard for authentication is OAuth2. With OAuth2, you can get access to an application without worrying about leaking user credentials such as passwords. Also, with OAuth you can set up communication with third-party APIs as you can read in this post about combining StepZen with Spotify.

When your REST API has implemented OAuth2, it has an endpoint to request a token with user credentials. When the credentials are valid, a token will be returned. Your application can use this token to send requests to REST API endpoints that require authentication. The token requested earlier is added to the HTTP headers' requests. This process will follow this flow:

OAuth2 flow for REST API

The same flow can be applied to a GraphQL API. Instead of sending HTTP requests to the endpoints to request and receive a token, you would use GraphQL queries or mutations. These operations will get the user credentials as arguments and return the token when these credentials are valid. The returned token can be appended in the headers when you call the GraphQL API or as an argument to operations requiring authentication.

Migrating an existing OAuth2 flow to GraphQL with StepZen, means you don't have to reimplement this flow. Your application can keep using the same flow; only it needs to use GraphQL operations instead of HTTP requests to the REST API:

type Auth {
  id: Int!
  token: String!
}

type Query {
  login(username: String, password: String): Auth
    @rest(
      endpoint: "/api/login?username=$username&password=$password"
      configuration: "auth"
    )
}

The login query takes the user credentials username and password. These will be appended to call to the REST API endpoint /API/login. Also, additional configuration is passed to the endpoint, which could be client credentials for the OAuth2 service.

The token that the login query will return can be passed to any other GraphQL operation as an argument. By making use of the @rest directive option to set the headers to a REST API endpoint, you can pass this token to the underlying REST API:

type Query {
  getProduct(id: ID!, token: String!): [Product]
    @rest(
      endpoint: "/API/products/$id"
      headers: [{ name: "Authorization", value: "Bearer $token" }]
    )
}

In the code block above, the GraphQL query getProduct is now passing the token to the REST API endpoint /API/products/[id]. This way, you don't have to set up a new authentication service when migrating to GraphQL as you can leverage the investments you already made for your existing REST API.

Summary

After reading this post, you've learned how to migrate an existing REST API to GraphQL using StepZen - you create a GraphQL API without having to write any resolvers. The implementation is done with GraphQL SDL only, using directives.

You map REST API endpoints to GraphQL types and operations. These types and operations can combine the responses of multiple REST API endpoints at once. If you've already invested in your application's authentication flow, you can leverage these investments by adding this flow to your GraphQL schema.

Want to learn more about StepZen? Try it out here or ask any question on the Discord here.