GraphQL has become a popular alternative to REST for building flexible and efficient APIs. With its ability to request exactly the data you need, GraphQL is an excellent choice for modern web and mobile applications. In the Go ecosystem, the gqlgen
library stands out for its type-safe and code-first approach to building GraphQL APIs.
In this tutorial, you will learn how to build a GraphQL API using Golang, gqlgen, and PostgreSQL as the backend database. You’ll start by setting up a new Go project, defining your GraphQL schema, generating resolvers, and connecting everything to a PostgreSQL database to perform actual CRUD operations.
By the end of this guide, you’ll have a fully functional GraphQL API server that can create and fetch users from a PostgreSQL database — a solid foundation to expand into a production-ready system.
🔍 What You’ll Learn:
-
How to set up a Go project with gqlgen
-
How to define GraphQL schemas and generate resolvers
-
How to connect Go with PostgreSQL using
pgx
or GORM -
How to handle queries and mutations with real database interaction
Let’s dive in and build a clean and powerful GraphQL API in Go!
Prerequisites
Before we get started, make sure you have the following tools and technologies installed and ready to use:
✅ Required Tools
-
Go installed (version 1.20 or higher)
👉 Download Go -
PostgreSQL is installed and running locally
👉 Download PostgreSQL -
gqlgen installed (GraphQL code generator for Go)
You can install it with:go install github.com/99designs/gqlgen@latest
-
Git for version control (optional but recommended)
📚 Basic Knowledge
-
Familiarity with the Go programming language
-
Basic understanding of GraphQL concepts (queries, mutations, schema)
-
Basic understanding of SQL and PostgreSQL
Once everything is ready, let’s create the project and initialize the Go module.
Project Setup
Let’s start by setting up the project structure and initializing a new Go module for our GraphQL API.
1. Create the Project Directory
Open your terminal and run:
mkdir go-graphql-api
cd go-graphql-api
2. Initialize the Go Module
Initialize a new Go module:
go mod init github.com/yourusername/go-graphql-api
Replace yourusername
with your GitHub or personal username.
3. Install Required Dependencies
We’ll need a few packages to get started:
go get github.com/99designs/gqlgen
go get github.com/jackc/pgx/v5
go get github.com/joho/godotenv
✅ Optional: If you prefer to use GORM instead of
pgx
, install:
go get gorm.io/gorm go get gorm.io/driver/postgres
Let me know which one you'd like to use — pgx
(more low-level, performant) or GORM
(ORM with abstraction and simplicity).
4. Initialize gqlgen
Now, initialize gqlgen with its boilerplate code:
go run github.com/99designs/gqlgen init
This will generate the initial folder structure:
├── go.mod
├── go.sum
├── gqlgen.yml
├── graph
│ ├── generated
│ ├── model
│ ├── resolver.go
│ └── schema.graphqls
├── server.go
We’ll edit these files as we go.
Define GraphQL Schema
Now that your project is set up, let’s define the core of your GraphQL API: the schema. This schema will describe the shape of the data and the operations (queries and mutations) your API supports.
1. Open the Schema File
Edit the graph/schema.graphqls
file and replace its contents with the following:
type User {
id: ID!
name: String!
email: String!
}
type Query {
users: [User!]!
user(id: ID!): User
}
type Mutation {
createUser(name: String!, email: String!): User!
}
Explanation:
-
User: Our data model with
id
,name
, andemail
fields. -
Query:
-
users
: Returns a list of all users. -
user(id: ID!)
: Fetches a single user by ID.
-
-
Mutation:
-
createUser(name, email)
: Creates a new user and returns it.
-
2. Generate Code from Schema
After saving your schema, modify gqlgen.yml:
# Where are all the schema files located? globs are supported eg src/**/*.graphqls
schema:
- graph/*.graphqls
# Where should the generated server code go?
exec:
package: generated
layout: single-file # Only other option is "follow-schema," ie multi-file.
# Only for single-file layout:
filename: graph/generated/generated.go
# Only for follow-schema layout:
# dir: graph
# filename_template: "{name}.generated.go"
# Optional: Maximum number of goroutines in concurrency to use per child resolvers(default: unlimited)
# worker_limit: 1000
# Uncomment to enable federation
# federation:
# filename: graph/federation.go
# package: graph
# version: 2
# options:
# computed_requires: true
# Where should any generated models go?
model:
filename: graph/model/models_gen.go
package: model
# Optional: Pass in a path to a new gotpl template to use for generating the models
# model_template: [your/path/model.gotpl]
Then run:
go run github.com/99designs/gqlgen generate
This command will:
-
Generate Go types for your schema in
graph/model/
-
Create resolver method stubs in
graph/schema.resolvers.go
Now gqlgen has created the basic plumbing, and you’re ready to connect it to a real PostgreSQL database.
Connect to PostgreSQL
In this step, we'll set up a PostgreSQL connection using the pgx
driver and environment variables for configuration.
1. Create a .env
File
At the root of your project, create a .env
file to store your database credentials:
DATABASE_URL=postgres://youruser:yourpassword@localhost:5432/yourdb?sslmode=disable
Replace youruser
, yourpassword
, and yourdb
with your actual PostgreSQL settings.
psql postgres -U djamware
create database go_graphql;
\q
2. Create a db
Package
Create a new folder called db
, and inside it, create a file named postgres.go
:
mkdir db
touch db/postgres.go
Now add the following code to db/postgres.go
:
package db
import (
"context"
"fmt"
"os"
"github.com/jackc/pgx/v5/pgxpool"
"github.com/joho/godotenv"
)
var Pool *pgxpool.Pool
func Connect() error {
// Load environment variables
err := godotenv.Load()
if err != nil {
return fmt.Errorf("error loading .env file: %w", err)
}
databaseUrl := os.Getenv("DATABASE_URL")
if databaseUrl == "" {
return fmt.Errorf("DATABASE_URL not set in environment")
}
// Create connection pool
pool, err := pgxpool.New(context.Background(), databaseUrl)
if err != nil {
return fmt.Errorf("unable to create DB pool: %w", err)
}
// Test the connection
err = pool.Ping(context.Background())
if err != nil {
return fmt.Errorf("unable to connect to DB: %w", err)
}
Pool = pool
fmt.Println("✅ Connected to PostgreSQL")
return nil
}
3. Initialize the DB Connection
Open server.go
and update it to initialize the DB connection when the app starts:
package main
import (
"log"
"net/http"
"github.com/didinj/go-graphql-api/db" // <-- replace with your actual module name
"github.com/didinj/go-graphql-api/graph"
"github.com/didinj/go-graphql-api/graph/generated"
"github.com/99designs/gqlgen/graphql/handler"
"github.com/99designs/gqlgen/graphql/playground"
)
const defaultPort = "8080"
func main() {
if err := db.Connect(); err != nil {
log.Fatalf("❌ DB connection failed: %v", err)
}
port := defaultPort
srv := handler.NewDefaultServer(generated.NewExecutableSchema(generated.Config{Resolvers: &graph.Resolver{}}))
http.Handle("/", playground.Handler("GraphQL playground", "/query"))
http.Handle("/query", srv)
log.Printf("🚀 Server is running at http://localhost:%s/", port)
log.Fatal(http.ListenAndServe(":"+port, nil))
}
Now, when you run the app, it should connect to your PostgreSQL database successfully.
Create the User Model and Database Table
We’ll define a User
model in Go, connect it to PostgreSQL using pgx
, and ensure the database table exists.
1. Create model/user.go
Inside the graph/model/
folder (which gqlgen
manages), don’t edit models_gen.go
— that file is auto-generated.
Instead, create a new file for your custom types:
touch graph/model/user_db.go
Add this Go struct for the PostgreSQL version of the user:
package model
type DBUser struct {
ID int `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
We use
DBUser
to differentiate it from the GraphQL-generatedUser
type.
2. Create SQL Table Manually
You can create the table in PostgreSQL like this:
CREATE TABLE users (
id SERIAL PRIMARY KEY,
name VARCHAR(100) NOT NULL,
email VARCHAR(100) UNIQUE NOT NULL
);
Or via a DB tool like pgAdmin, DBeaver, or command line:
psql -U youruser -d yourdb
Then paste the SQL above.
3. Create a Repository Layer (Optional but Clean)
Create a new file db/user_repository.go
:
package db
import (
"context"
"go-graphql-api/graph/model"
)
func GetUsers(ctx context.Context) ([]*model.DBUser, error) {
rows, err := Pool.Query(ctx, "SELECT id, name, email FROM users")
if err != nil {
return nil, err
}
defer rows.Close()
var users []*model.DBUser
for rows.Next() {
var u model.DBUser
if err := rows.Scan(&u.ID, &u.Name, &u.Email); err != nil {
return nil, err
}
users = append(users, &u)
}
return users, nil
}
func GetUserByID(ctx context.Context, id int) (*model.DBUser, error) {
var u model.DBUser
err := Pool.QueryRow(ctx, "SELECT id, name, email FROM users WHERE id=$1", id).
Scan(&u.ID, &u.Name, &u.Email)
if err != nil {
return nil, err
}
return &u, nil
}
func CreateUser(ctx context.Context, name, email string) (*model.DBUser, error) {
var u model.DBUser
err := Pool.QueryRow(ctx,
"INSERT INTO users (name, email) VALUES ($1, $2) RETURNING id, name, email",
name, email,
).Scan(&u.ID, &u.Name, &u.Email)
if err != nil {
return nil, err
}
return &u, nil
}
With the model and repository set up, next we'll connect these to your GraphQL resolvers.
Implement Resolvers in schema.resolvers.go
✏️ Open graph/schema.resolvers.go
Inside this file, gqlgen already stubbed out functions like Query_users
, Query_user
, and Mutation_createUser
.
We’ll implement those now.
1. Query_users
func (r *queryResolver) Users(ctx context.Context) ([]*model.User, error) {
dbUsers, err := db.GetUsers(ctx)
if err != nil {
return nil, err
}
var gqlUsers []*model.User
for _, u := range dbUsers {
gqlUsers = append(gqlUsers, &model.User{
ID: fmt.Sprintf("%d", u.ID),
Name: u.Name,
Email: u.Email,
})
}
return gqlUsers, nil
}
2. Query_user
func (r *queryResolver) User(ctx context.Context, id string) (*model.User, error) {
intID, err := strconv.Atoi(id)
if err != nil {
return nil, fmt.Errorf("invalid ID format")
}
dbUser, err := db.GetUserByID(ctx, intID)
if err != nil {
return nil, err
}
return &model.User{
ID: fmt.Sprintf("%d", dbUser.ID),
Name: dbUser.Name,
Email: dbUser.Email,
}, nil
}
3. Mutation_createUser
func (r *mutationResolver) CreateUser(ctx context.Context, name string, email string) (*model.User, error) {
dbUser, err := db.CreateUser(ctx, name, email)
if err != nil {
return nil, err
}
return &model.User{
ID: fmt.Sprintf("%d", dbUser.ID),
Name: dbUser.Name,
Email: dbUser.Email,
}, nil
}
4. Add These Imports (if missing)
At the top of schema.resolvers.go
:
import (
"context"
"fmt"
"strconv"
"github.com/didinj/go-graphql-api/db" // update if you're using another module name
"github.com/didinj/go-graphql-api/graph/model"
)
If you're using github.com/didinj/go-graphql-api
, adjust those accordingly.
Done! You now have:
-
Working GraphQL schema
-
Connected PostgreSQL DB
-
Resolvers using real data
You can now run:
go run server.go
And test in the browser at http://localhost:8080
using queries like:
query {
users {
id
name
email
}
}
or:
mutation {
createUser(name: "Alice", email: "[email protected]") {
id
name
email
}
}
Conclusion
In this tutorial, you’ve learned how to build a fully functional GraphQL API using Go and gqlgen. We covered schema definition, resolver implementation, error handling, and best practices for structuring your code. With this foundation, you can now extend your API with authentication, database integration, and advanced GraphQL features like subscriptions and middleware. Go’s performance combined with GraphQL’s flexibility makes this a powerful stack for modern web development.
You can get the full working source code on our GitHub.
That is just the basics. If you need more deep learning about the Golang (Go) language and frameworks, you can take the following cheap course:
- Real-World GoLang Project: Car Management System
- Building GUI Applications with Fyne and Go (Golang)
- AWS Cognito Using Golang
- Google Go Programming for Beginners (golang)
- Building a module in Go (Golang)
- Go/Golang Professional Interview Questions
Thanks!