In this article, we will look at how to implement GraphQL with Golang and Gqlgen.
TL;DR
Link to the GitHub repository
Table of Contents
- Introduction: What is GraphQL and Why Should You Use It in Golang?
- Setting Up Your Environment for Gqlgen
- Getting Started
- Adding Types to Your Schema
- Generating a Schema with Gqlgen
- Adding Custom Resolvers
- Setting up a SQLite Database
- Querying Data from a Database
- Creating Mutations with Gqlgen
- Testing our Server
- Using Directives to Add Custom Logic
- Conclusion
Introduction: What is GraphQL and Why Should You Use It in Golang?
GraphQL is a new technology that has been gaining traction in the world of web development. It is an open source data query language created by Facebook and often used to query databases for information. GraphQL provides developers with greater flexibility, allowing them to create more efficient queries than traditional SQL-based solutions. In this blog post, we’ll discuss why you should be using GraphQL in your Golang projects and how it can help you improve your codebase’s performance.
As developers, we all strive to write the most efficient code possible - GraphQL can help us do just that! By writing specific queries with fewer lines of code and loading only what is required from the database, performance increases are seen across the board. This means less time spent waiting on long queries while debugging or waiting for page loads when developing applications. Additionally, GraphQL makes development easier by providing a unified API across different types of services such as databases, APIs and frontend frameworks like React or AngularJS.
When looking at Golang specifically, there are a few advantages that make it stand out from other languages when incorporating GraphQL into our workflow. Firstly, its statically typed nature allows us to catch errors earlier on in our process which helps reduce bugs throughout development cycles – especially useful if multiple teams are working together on one project! Secondly, Golang’s built-in support for JSON parsing makes it easy to work with popular graphql libraries such as Apollo Server or Relay Modern without having to manually parse requests ourselves every single time; leaving us more time for coding instead of dealing with tedious tasks!
Overall, if you’re building applications with Golang then adding GraphQL into your workflow will bring many benefits including: improved performance due to fewer lines of code needed; simplified development through its unified API approach; reduced bugs through static typing; and quicker integration times thanks to built-in JSON support within Go’s core library system!
Setting Up Your Environment for Gqlgen
Do you want to start building your own GraphQL applications? Well, you’re in luck! Setting up your environment for Gqlgen is easier than ever.
Gqlgen is a popular open source library for automating the creation of GraphQL servers in Go. It’s designed to be simple and flexible so developers can create robust API endpoints quickly and easily. Ready to get started? Here’s what you need to know:
First, make sure that you have the correct version of Go installed on your system – Gqlgen requires at least version 1.11 or later. Once installed, check out the official Getting Started page which walks through setting up a basic server with Gqlgen and provides an overview of how it works. You may also find it helpful to read over the documentation before getting started - this outlines all configuration options available as well as additional features such as logging and authentication setup steps.
Once you’ve got everything set up correctly, it’s time to start coding! There are two main pieces needed when working with Gqlgen – schema definition files (SDL) and resolver functions (or “resolvers”). The SDL defines your data types while the resolvers provide the logic behind each type of operation that can be performed on those types (i.e., queries, mutations). The Getting Started guide includes example code snippets illustrating how these two components work together but if you need more help there are plenty of resources online such as tutorials or sample projects demonstrating various use cases for building GraphQL applications with Gqlgen.
With this knowledge under your belt, setting up a powerful GraphQL application using Gqlgen should now be easy peasy! So don’t wait any longer - get out there and start coding!
Getting Started
First, we’ll create a new project directory, initialize a go mod project and download gqlgen as a dependency. Finally, we will run the init command to bootstrap our project.
mkdir graphql-golang-example
cd graphql-golang-example
go mod init graphql-golang-example
go get github.com/99designs/gqlgen
go run github.com/99designs/gqlgen init
This will create a new gqlgen.yml file in the project directory.
We now have a basic graphql server and we can run it with the following command:
go run server.go
If, for some reason you receive an error message about missing dependencies, you can run the following command to install them:
go mod tidy
To allow us to run gqlgen commands we should also create a tools.go in the project root directory with the following content:
//go:build tools
// +build tools
package tools
import (
_ "github.com/99designs/gqlgen"
)
Adding Types to Your Schema
The first step in creating a schema with Gqlgen is to define the types of data that will be stored within it. This is done using a set of predefined types that can be used to create your own custom data types. OTS types are defined using the following syntax:
type <TypeName> {
<FieldName>: <FieldType>
}
For example, let’s say we want to create a schema that stores information about users. We could define a User type like this:
type User {
id: ID!
name: String!
email: String!
}
Note that the exclamation mark after the type name indicates that the field is required - if this field is missing then an error will be thrown. Also, the ID type is a special case - it is a unique identifier that should be used for all primary keys in your schema. You can read more about the different types available in Gqlgen’s documentation.
Once you’ve defined your types, you can then use them to create your own custom data types. For example, we could create a Todo type that contains a reference to a User type:
type Todo {
id: ID!
text: String!
done: Boolean!
user: User!
}
When we initialized our project, Gqlgen created a schema file inside the graph directory. This is where we will define our types. Open up the schema.graphqls file to explore the default types that were created for us.
type Todo {
id: ID!
text: String!
done: Boolean!
user: User!
}
type User {
id: ID!
name: String!
}
type Query {
todos: [Todo!]!
}
input NewTodo {
text: String!
userId: String
done: Boolean!
}
type Mutation {
createTodo(input: NewTodo!): Todo!
}
Let’s modify this schema to include input arguments so that we can filter our todos.
...
type Query {
todos(filter: TodoFilter): [Todo!]!
}
input TodoFilter {
text: String
done: Boolean
}
...
Generating a Schema with Gqlgen
Once you’ve defined your types, you can generate an updated schema with Gqlgen by running the following command:
go run github.com/99designs/gqlgen generate
Now, if we open up the schema.resolvers.go file in the graph directory, we can see that Gqlgen has automatically generated a resolver for each type in our schema. This resolver is responsible for providing the logic behind each type of operation that can be performed on that type (i.e., queries, mutations). For example, the Todo type has a resolver that allows us to query for a list of todos with our newly defined filter argument:
func (r *queryResolver) Todos(ctx context.Context, filter *model.TodoFilter) ([]*model.Todo, error) {
panic(fmt.Errorf("not implemented: Todos - todos"))
}
Adding Custom Resolvers
By default, Gqlgen will generate resolvers for all of the types defined in your schema. However, you may need to add custom resolvers for certain types or fields in order to provide additional functionality. For example, let’s say that we want to add a custom resolver to our User type to return the user’s full name:
type User {
id: ID!
name: String!
email: String!
fullName: String!
}
We can define this resolver by adding a new function to our resolver.go file:
func (r *userResolver) FullName(ctx context.Context, obj *model.User) (string, error) {
return obj.FirstName + " " + obj.LastName, nil
}
Setting up a SQLite Database
Most tutorials will have you read data from static files but let’s make this example a bit more realistic and include a database.
We’ll setup up a SQLite database to store our data since it it’s lightweight and no need for install any other infrastrucure. The great thing is, you could easily swap this our for any other data storage option such as PostgreSQL, MongoDB etc.
Let’s create a new file called db.go and add the following code to it:
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/mattn/go-sqlite3"
)
var db *sql.DB
func init() {
var err error
db, err = sql.Open("sqlite3", "./foo.db")
if err != nil {
log.Fatal(err)
}
}
Note: The init function will be called automatically when our application starts.
This code will create a new SQLite database called todo.db in our project’s root directory. We can then use this database to store our data.
Creating a Database Table
We’ll create a new table called todos to store our todo items. Let’s add the following code to our db.go file:
func init() {
...
_, err = db.Exec(`
CREATE TABLE IF NOT EXISTS todos (
id INTEGER PRIMARY KEY AUTOINCREMENT,
text TEXT,
done BOOLEAN
);
`)
if err != nil {
log.Fatalf("failed to create table: %v", err)
}
}
Note: We are using the
AUTOINCREMENTkeyword to automatically generate a unique ID for each todo item.
Querying Data from a Database
What is a query?
A query is a GraphQL operation that allows you to fetch data from your schema. In Gqlgen, queries are defined using the following syntax:
type Query {
<FieldName>: <FieldType>
}
For example, let’s say we want to create a query that returns a list of todos. We could define a query like this:
type Query {
todos: [Todo!]!
}
Now that we’ve defined our types, let’s take a look at how we can use them to query data from a database using Gqlgen’s resolvers and connections.
Let’s start by updating our resolver.go which serves as our simple dependency injection object.
type Resolver struct {
DB *sql.DB
}
We can then update our auto-generated server.go file and pass in our database connection:
...
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{DB: db}}))
...
Finally, we can use this connection to query todos from our database in our schema.resolvers.go file:
func (r *queryResolver) Todos(ctx context.Context, filter *model.TodoFilter) ([]*model.Todo, error) {
query := "SELECT id, text, done FROM todos"
args := []interface{}{}
if filter != nil {
if filter.Text != nil {
query += " WHERE text LIKE ?"
args = append(args, "%"+*filter.Text+"%")
}
if filter.Done != nil {
if filter.Text != nil {
query += " AND"
} else {
query += " WHERE"
}
query += " done = ?"
args = append(args, *filter.Done)
}
}
rows, err := r.DB.Query(query, args...)
if err != nil {
return nil, err
}
defer rows.Close()
var todos []*model.Todo
for rows.Next() {
var todo model.Todo
if err := rows.Scan(&todo.ID, &todo.Text, &todo.Done); err != nil {
return nil, err
}
todos = append(todos, &todo)
}
return todos, nil
}
What we’ve done here is create a query resolver that is responsible for querying todos from our database.
Since our filters are optional, we need to conditionally build our query and arguments.
We can then use this resolver to query for a list of todos with our newly defined filter argument:
```graphql
query {
todos(filter: { text: "foo", done: false }) {
id
text
done
}
}
Creating Mutations with Gqlgen
Right now, we have nothing to query!, instead of hardcoding values at startup, let’s finish up the server by adding our mutation logic that will add todos to our database.
What is a Mutation?
A mutation is a type of operation that can be performed on a GraphQL schema. It allows you to modify the state of your data by adding, updating, or deleting records. Mutations are defined using the following syntax:
type Mutation {
<FieldName>(<InputType>): <ReturnType>
}
Previously we defined a mutation called createTodo that takes an input of type NewTodo and returns a Todo type:
type Mutation {
createTodo(input: NewTodo!): Todo!
}
Note: The exclamation mark after the input type indicates that the field is required - if this field is missing then an error will be thrown.
gqlgen already created the boilerplate code for us when we ran the generate command, so now, all that’s required is to implement the resolver logic:
func (r *mutationResolver) CreateTodo(ctx context.Context, input model.NewTodo) (*model.Todo, error) {
res, err := r.DB.Exec("INSERT INTO todos(text, done) VALUES(?, ?)", input.Text, input.Done)
if err != nil {
return nil, err
}
id, err := res.LastInsertId()
if err != nil {
return nil, err
}
return &model.Todo{
ID: fmt.Sprintf("%d", id),
Text: input.Text,
Done: *input.Done,
}, nil
}
Testing our Server
We’ve made quite a few changes to our server, so let’s test it out to make sure everything is working as expected.
First, let’s build our application:
go build -o app
Then, let’s start our server by executing the binary:
./app
Next, let’s open up our GraphQL Playground and run the following query to add some initial todos:
mutation {
createTodo(input: { text: "Learn GraphQL", done: false }) {
id
text
done
}
}
We should see the following response:
{
"data": {
"createTodo": {
"id": 1,
"text": "Learn GraphQL",
"done": false
}
}
}
Now, let’s run the following query to fetch all todos:
query {
todos {
id
text
done
}
}
We should see the following response:
{
"data": {
"todos": [
{
"id": "1",
"text": "Learn GraphQL",
"done": false
}
]
}
}
Using Directives to Add Custom Logic
Gqlgen also supports directives that can be used to add custom logic to your schema. For example, let’s say we want to add a directive that allows us to specify the maximum length of a string field:
directive @maxLength(length: Int!) on FIELD_DEFINITION
type User {
id: ID!
name: String! @maxLength(length: 10)
email: String!
}
We can define this directive by adding a new function to our resolver.go file:
func (r *maxLengthDirectiveResolver) MaxLength(ctx context.Context, obj interface{}, next graphql.Resolver, length int) (res interface{}, err error) {
res, err = next(ctx)
if err != nil {
return res, err
}
if len(res.(string)) > length {
return nil, errors.New("string is too long")
}
return res, nil
}
Conclusion
Congratulations! You’ve made it to the end of this comprehensive guide to getting started with GraphQL in Golang using Gqlgen. We also learned how to use directives to add custom logic to our schema. I hope you now feel confident enough to start diving into complex projects and applications that use GraphQL.
If you enjoyed this tutorial, please consider sharing it with your friends and colleagues. In the meantime, I’ll be working on a follow-up tutorial that will cover more advanced topics such as subscriptions, authentication, and authorization.