Generating REST APIs from GraphQL Schemas, a Tutorial
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.