Over the past three years, I've been building dozens of GraphQL servers for companies big and small. But I often get the question of how you need to build your GraphQL server if you (or your team) want to start using GraphQL. Some teams might lack the capacity or knowledge on the backend to introduce GraphQL, while other teams have invested too much in the existing REST API infrastructure. As there is no definite answer on whether you should or should not build your GraphQL server, let's explore some of the options you have. This article will list different ways to build a GraphQL Server, either through an SDL- or code-first approach. Also, a solution to create a GraphQL API without writing any code is demonstrated.
Building Your Own GraphQL Server
When you start introducing GraphQL as a protocol for your APIs, this often begins by investigating how you can build a server that supports GraphQL. Many approaches to creating a GraphQL server have implementations for the most popular programming languages, ranging from Java to JavaScript and from Go to Python. This makes it possible for most teams with a backend capacity to start building their own GraphQL server. But before listing the benefits of building a server yourself, let's look at the different approaches for building one.
The two most used ways to build a GraphQL server that I'll discuss are:
- Code-first
- SDL-first
Both ways have their advantages and disadvantages, which you'll learn about shortly. Let's start by having a look at building a code-first GraphQL server.
Code-first
The code-first way to build a GraphQL server was introduced with the specification and the core implementation for GraphQL in JavaScript. The code-first approach that came with graphql-js lets you programmatically create a schema by writing code, which looks like this:
import {
graphql,
GraphQLSchema,
GraphQLObjectType,
GraphQLString,
GraphQLInt
} from "graphql";
const user = new GraphQLObjectType({
name: "User",
fields: {
id: {
type: GraphQLInt
},
name: {
type: GraphQLString
},
age: {
type: GraphQLInt
}
}
});
const schema = new GraphQLSchema({
query: new GraphQLObjectType({
name: "RootQueryType",
fields: {
user: {
type: user,
resolve() {
return {
id: 1,
name: "Harry Potter",
age: 41
};
}
}
}
})
});
The code above creates a plain JavaScript object called schema
that compiles to a schema in GraphQL SDL (Schema Design Language) and a set of resolvers defined in the resolve
method for every operation or type in the schema. By defining the resolvers within the construction of the schema, you create a so-called executable schema. The schema that will be generated by graphql-js
looks like this:
type User {
id: Int
name: String
age: Int
}
type Query {
user: User
}
When you provide this schema to a library to create a GraphQL server, you can send operations. It accepts a query to get the user of type User
using the following query:
query {
user {
id
name
age
}
}
But this code-first approach is also very verbose and sometimes confusing to developers. As graphql-js
is only the first iteration of a code-first solution, some of these issues got solved by libraries released later. Nowadays, some great libraries and frameworks work with languages like TypeScript or Python.
You can find an example of a modern code-first way to build a GraphQL server is the Python library graphene. You can use Graphene build servers that connect with data sources like a database or custom Python objects. Below you can see what a basic implementation of a GraphQL server with Graphene looks like:
import graphene
class User(graphene.ObjectType):
id = graphene.ID()
name = graphene.String()
age = graphene.Int()
class Query(graphene.ObjectType):
user = graphene.Field(Patron)
def resolve_user(root, info):
return User(id=1, name="Harry Potter", age=41)
schema = graphene.Schema(query=Query)
Instead of constructing an object, it uses classes to define the types and operations for the schema that it generates. The schema generated by this implementation will have the same structure as the schema created with graphql-js
. Using classes to define your schema has the advantage of being less mutable and more structured when writing code. Similar implementations can be found for TypeScript with the library TypeGraphQL or Sangria GraphQL for Scala.
In the code-first approach, resolvers are linked to the types and operations in the schema, meaning you don't have any separation of concern between your schema and resolvers. This separation is present in an SDL-first approach, as you'll see in the next section.
SDL-first
Defining your schema and the resolvers simultaneously led to some issues for developers, as it was hard to decouple the schema from the (business) logic in your resolvers. The SDL-first approach introduced this separation of concerns by defining the complete schema before connecting them to the resolvers and making this schema executable. A version of the SDL-first approach was introduced together with GraphQL specification, and the core implementation for in JavaScript called graphql-js.
With an SDL-first approach, you directly write your schema and write resolvers afterward. A schema created SDL-first with graphql-js
, could be made into a server by using Express or any other Node library to set up an HTTP server. An example implementation of an SDL-first approach in JavaScript will require you to define a schema using SDL:
import { buildSchema } from 'graphql';
const schema = buildSchema(`
type User {
id: Int
name: String
age: Int
}
type Query {
user: User
}
`);
After creating the schema, you can write resolvers that will return a response with the same types as defined in the schema:
const resolvers = {
user: () => {
return {
id: 1,
name: "Harry Potter",
age: 41
};
},
};
And finally bring the two together to create the GraphQL HTTP server that you could run with Express:
import { graphqlHTTP } from 'express-graphql';
import { buildSchema } from 'graphql';
const schema = /* .. */
const resolvers = /* .. */
const server = graphqlHTTP({
schema: schema,
rootValue: resolvers
});
Even though this implementation is written in JavaScript there is an extensive list of similar implementations in languages like Go, PHP, Python, Java, and much more here.
The approach of building a GraphQL server SDL-first (or schema-first) is the oldest and most popular one. It requires you to think about the responses and response types defined in the GraphQL schema before building the implementation - meaning the resolvers. But building a GraphQL server can be difficult, as there are more considerations to be made. In the next section, you'll see how a tool like StepZen makes this process easier.
Creating a GraphQL API with StepZen
So far, you've seen two different approaches to building a GraphQL server, either code-first or SDL-first. If you want to invest your time in building a performant GraphQL server, both code-first and SDL-first are great approaches. Both approaches are somewhat agnostic in terms of programming languages. To connect your data sources like a SQL- or NoSQL-database, you often have to write (a lot of) code or use an ORM. Let alone that you also need to host this server somewhere to make it available for usage.
Most companies want a solution deployed quickly, which is where StepZen steps in. With StepZen, you can create a performant, serverless GraphQL API within minutes. Using a SDL-first, you can quickly connect all your existing data sources without having to write any code. You'll only need to use the following config to generate a GraphQL API with the same query that you've used earlier:
type User {
id: Int!
name: String!
age: Int!
}
type Query {
user: User
@dbquery(type: "mysql", table: "users", configuration: "MySQL_config")
}
The schema is configured using a .graphql
file, and the directive @dbquery
is used to connect a SQL-database and links the users
table to the query to retrieve the user. The value MySQL_config
refers to a configuration file with the credentials to connect with the database.
In the SDL-first approach we discussed earlier, it is hard to keep your resolvers and do for an SDL-first server when adding more operations and types. This particular problem could lead to errors in runtime when people start sending documents to your GraphQL server. For example, you can find that types are missing in the schema or that not all operations refer to a resolver. There are, of course, tools to help you keep them in sync, but these come with a learning curve. With StepZen, you don't have this problem, as the resolvers are generated based on the schema and the connections you make there. Also, you no longer have to write any boilerplate code to connect data sources in your resolver.
Want to learn more about StepZen? Try it out here or ask any question on the Discord here.
Oldest comments (0)