SOFA — The best way to REST (is GraphQL)

Uri Goldshtein

Ending the REST vs. GraphQL Debate Once and for All

TL;DR

  • Don’t choose between REST and GraphQL — create a fully RESTful API automatically from your GraphQL implementation (with a library and a single line of code)
  • Get most of the benefits of GraphQL on the backend and frontend, while using and exposing REST
  • Support all your existing clients with REST while improving your backend stack with GraphQL
  • Create custom, perfectly client-aligned REST endpoints for your frontend simply by naming a route and attaching a query
  • Stop arguing about REST vs GraphQL. Use GraphQL, generate REST and get the best from both
  • In the other way around (REST to GraphQL) you won’t get the best of both world but less powerful, harder to maintain server implementation with some benefits of GraphQL. It is a good and fast start for a migration though.

Wait, What!?

Many articles have been written about the pros and cons of GraphQL and REST APIs and how to decide which one to use. I’m not going to repeat those here..

A lot of time and energy spent by smart consultants to write those articles, read those articles, while most of them are finished with the “it depends on your use case” summary, without actually specifying those use cases!

I’ve been working with REST, GraphQL and SOAP APIs for many years. So I thought, why not come up with a list of those use cases and for each one of those to check — what can’t you do in GraphQL that you can do with REST and what you wouldn’t want to do with GraphQL and you would prefer REST.

After creating that list, I suddenly had a thought — what if there was another option — what if my powerful GraphQL server could just generate a REST API for me?

Then I could get the best of both worlds!

The more I dived into the idea and implementation then more I realized it’s not only that we can have both types of APIs created for us, but even if we just want to expose REST APIs, and none of our clients use GraphQL, GraphQL is the best way the create REST APIs!

How Does the above Sentence Even Make Sense?!

Usually when we (The Guild) help companies and organizations to modernize their APIs, the first to understand the benefits of GraphQL are the frontend developers, for obvious reasons. But as soon as the backend developers “Get it”, they become the biggest advocates of the technology. But they still need to support existing clients and 3rd party partners.

That’s why those newly generated REST APIs get a lot of the features and benefits from the internal GraphQL implementation that make backend developers happy:

  • Fully generated documentation that is always up-to-date (Swagger, OpenAPI and GraphiQL)
  • Truly RESTful API out of the box
  • GraphQL Subscriptions as Webhooks
  • Runtime validation of data — be 100% sure that fetched data matches schema’s and query’s structure. You send exactly what I want to send, string is a string, an object has exactly the same properties.
  • Creating a custom endpoint is now a matter of choose a route name and attaching a query to it. done. No more manual work of creating and maintaining client specific endpoints!
  • Use GraphQL’s philosophy of evolving APIs through schemas — no more painful V1 — V2 API migrations.
  • Use modern technology that is easier to hire people to. Companies like Facebook, Airbnb and others have moved to GraphQL. None of them has gone back.
  • The power of GraphQL resolvers to create your API implementation, instead of manually written controllers from MVC

What I get from having resolvers?

  • Easier to transform data, so it matches the response (GraphQL Schema). That’s because every entity has its own resolvers, so the mapping is moved into smaller pieces and reused across an entire app.
  • GraphQL allows you to easily share data across every resolver, we call it Context.
  • Forces you to define and resolve data in an opinionated way that actually helps to build an API. It runs functions in parallel (functions that are nested at the same level), handles async and at the end, it is responsible for merging all of that into a single object, so you don’t have to think about it.

SOFA — Use GraphQL to Create RESTful API

So we created SOFA (pun intended), an open source library you install on your GraphQL server to create a fully RESTful and configurable API gateway. Use GraphQL to REST.

”How to” Tutorial

Let’s create a short step-by-step tutorial on how to create a RESTful API.

Step 1: npm install the sofa-api package and add the following line of code:

import express from 'express'
import sofa from 'sofa-api'
 
const app = express()
 
app.use(sofa({ schema }))

Step 2: Go REST on a Sofa, you’re done.

Kamil Kisiela added Sofa to the SpaceX GraphQL API implementation by Carlos Rufo, in a single commit.

Check out the fully generated REST endpoints, the Swagger live documentation, GraphiQL editor and the GraphiQL-Explorer!

By the way, what you see here is a REST API, generated on top of a GraphQL API, created on top of another REST API….

Why did you do that for!?!?

Gradually Migrating from Old REST Implementations

This is actually a good direction to go. In many of the companies we work with, they’ve created REST API layers using old technology on top of their original web-services.

But those REST implementations are problematic (for all the obvious reasons people choose to move to GraphQL).

So our way to go is to create GraphQL implementations on top of those REST layers, migrate the clients to those implementations and then gradually remove the old RESTful layer and call the services directly.

Using Sofa made those transitions much faster because we can offer all the existing clients to migrate to our GraphQL implementation without actually using GraphQL themselves. We simply expose the same REST endpoints on top of GraphQL and they are moving to our layer happily because we can accommodate all of their requests and custom REST endpoints much faster than the original, old REST implementations.


Give Me More Details

Sofa uses Express by default, but you can use any other server framework. Sofa is also GraphQL server implementation agnostic.

Head over to the Sofa website for documentation and to the Github repository for reporting issues and helping out.

How SOFA Works?

Under the hood, Sofa turns each field of Query and Mutation types into routes. First group of routes is available only through GET method, mutations on the other hand get POST.

Sofa uses GraphQL’s AST to create an operation with all possible variables (even those deeply nested) and knows exactly what to fetch. Later on it converts the request’s body into operation’s variables and execute it against the Schema. It happens locally, but it’s also possible to use an external GraphQL Server or even Apollo Link.

Right now Sofa has a built-in support for Express but it’s totally possible to use a different framework. The main concept stays exactly the same so only the way we handle the request differs across different server implementations.

GraphQL Subscriptions as Webhooks?

The way it works is simply, you start a subscription by calling a special route and you get a unique ID that later on might be used to update or even stop the subscription. Subscriptions are Webhooks. Sofa knows exactly when there’s an even happening on your API and notifies you through the endpoint you’ve assigned a subscription to.

Models / Resources?

In some cases you don’t want to expose an entire object but just its id. How you’re able to do that with Sofa? You need to have two queries. First one has to return a single entity based just on its id (which would be an argument) and the second one should resolve a list of those. Also the names should match, for example a resource called User should have two queries: user(id: ID): User and users: [User]. Pretty much the same thing you would do with REST.

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

Before Sofa creates the routes, it looks for those Models and registers them so when the operations are built you don’t fetch everything but only an id.

But what if you want to fetch an entire object but only in few places?

There’s an option called ignore that allows you to do that. You simply pass a path in which you want to overwrite the default behavior.

Given the schema below, you would get just author’s id.

type Book {
  id: ID
  title: String!
  author: User!
}
 
extend type Query {
  book(id: ID!): Book
  books: [Book]
}

With ignore: ['Book.author']you end up with an entire User object.

sofa({
  // ...
  ignore: ['Book.author']
})

Swagger and OpenAPI

Thanks to GraphQL’s type system Sofa is able to generate always up-to-date documentation for your REST API. Right now we support Swagger and its OpenAPI specification but it’s really easy to adopt different specs.

import sofa, { OpenAPI } from 'sofa-api'
 
const openApi = OpenAPI({
  schema,
  info: {
    title: 'Example API',
    version: '3.0.0'
  }
})
 
app.use(
  sofa({
    schema,
    onRoute(info) {
      openApi.addRoute(info, {
        basePath: ''
      })
    }
  })
)
 
openApi.save('./swagger.json')

Summary

sofa-api makes it extremely easy to create a RESTful API with all the best practices of REST from a GraphQL server using all its power.

Stop wasting your life arguing about REST vs GraphQL — Be productive, get the benefits of both worlds and move into the future of API development.

I hope this would become the last REST vs. GraphQL article out there…. if you think it won’t, comment with a use case and let’s try it out!

Thanks to Kamil Kisiela for working with me on this and making this library a reality!

Join our newsletter

Want to hear from us when there's something new? Sign up and stay up to date!

By subscribing, you agree with Beehiiv’s Terms of Service and Privacy Policy.

Recent issues of our newsletter

Similar articles