Arguing about technology choices is a huge thing, the RESTFul and GraphQL debate is an example. But sometimes there methods to unite the different ways. Sofa is one of them that tries to united REST and GraphQL.

Why

REST and GraphQL are different approaches to build HTTP based APIs, but the opinions on which is best suited for a problem differ from developer to developer.

I don’t know if one of them is superior to the other, but sometimes it can be a dealbreaker if we only provide one type of API to our customers.

Sofa allows us to automatically generate a REST API from a GraphQL schema so that we don’t have to implement two APIs on our own.

What

Sofa is a Node.js package that takes a GraphQL schema definition and creates an Express middleware from it. This middleware provides REST-API endpoints.

How

Let’s set up a small API server with the help of Sofa, GraphQL, and Express and then try to create and read messages from it.

Implementation

First, we use npm to init a new Node.js project and install the packages.

$ mkdir api-server
$ cd api-server
$ npm init
$ npm i express express-graphql graphql-tools sofa-api

Next, we create a GraphQl schema in a new schema.gql file.

type Message {
  id: ID!
  text: String!
}

type Query {
  message(id: ID!): Message
  messages: [Message!]
}

type Mutation {
  writeMessage(title: String!): Message
}

schema {
  query: Query
  mutation: Mutation
}

We only have a Message type and the corresponding types for queries and mutations.

We also need to define resolvers for each query and mutation. Let’s create a new resolver.js file for this.

const messages = [];

module.exports = {
  Query: {
    message: (_, { id }) => messages[id],
    messages: () => messages
  },
  Mutation: {
    writeMessage: (_, { text }) => {
      const message = { id: messages.length, text };
      messages.push(message);
      return message;
    }
  }
};

We use the message array as the data-store and its length to generate IDs for our messages. This will be enough since we don’t have a delete mutation that could mess up the IDs.

Now, we wire that schema up with GraphQL and Sofa. We do this in a new index.js file.

const fs = require("fs");
const { makeExecutableSchema } = require("graphql-tools");
const express = require("express");
const graphql = require("express-graphql");
const sofa = require("sofa-api").default;

const typeDefs = fs.readFileSync("./typeDefs.gql", "utf8");
const resolvers = require("./resolvers");

const schema = makeExecutableSchema({ typeDefs, resolvers });

const server = express();

server.use("/graphql", graphql({ schema, graphiql: true }));
server.use("/rest", sofa({ schema }));

server.listen("9999");

Let’s go through the critical parts of this file.

First, we read the GraphQL type definition from disk and require the corresponding resolvers.

We then use the graphql-tools to create a schema from the resolvers and the type definition.

After that, we create an Express server and set the middlewares for the two endpoints, /graphql and /rest.

We also enable graphiql, the default GraphQL UI so that we can test the GraphQL endpoint in the browser.

Finally, we start a server on port 9999.

Usage

Let’s start the server.

$ node .

And open Graphiql in the browser: http://localhost:9999/graphql

In the text-area on the left, we can now input Queries and mutations. Let’s start with a mutation since our store is empty at the moment.

mutation {
  writeMessage(text: "my messsage") {
    id
    text
  }
}

We can send the message to the server with Ctrl+Enter.

The answer should be as following:

{
  "data": {
    "writeMessage": {
      "id": "0",
      "text": "my messsage"
    }
  }
}

Let’s do this again with a different text, to see if the IDs get counted up as expected:

mutation {
  writeMessage(text: "another message") {
    id
    text
  }
}

The returned JSON should reflect the new text and id.

Let’s try the queries.

First load all messages:

query {
  messages {
    id
    text
  }
}

This should deliver JSON with a list of all of our messages.

Only one query missing: message

This time we need to provide the ID of the message we want to load.

query {
  message(id: 1) {
    id
    text
  }
}

We now know that the GraphQL API works as expected, let’s try the REST version.

The mutation works via a POST request, this can be done with cURL.

$ curl --header "Content-Type: application/json" \
  --request POST \
  --data '{"text":"REST message"}' \
  http://localhost:9999/rest/add-message

The queries work with GET requests so that we can use a simple link in the browser.

Listing all messages:

http://localhost:9999/rest/messages

Getting one message:

http://localhost:9999/rest/message/2

Conclusion

Sofa is certainly an interesting solution to the REST <-> GraphQL divide. It leverages the fact that GraphQL requires a standardized schmema and resolvers to map many concepts back to REST APIs.

This allows API creators to provide their customers with different API types while only having to maintain one code-base.

Moesif is the most advanced API Analytics platform, supporting REST, GraphQL, Web3 Json-RPC and more. Thousands of API developers process billions of API calls through Moesif for debugging, monitoring and discovering insights.

Learn More

Leave a comment