Building a Golang GraphQL API with gqlgen and PostgreSQL

by Didin J. on Jun 30, 2025 Building a Golang GraphQL API with gqlgen and PostgreSQL

Learn how to build a high-performance GraphQL API using Go and gqlgen, with step-by-step guidance on schema, resolvers, error handling, and best practices.

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, and email 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-generated User 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:

Thanks!