Building a Clean Architecture GraphQL App with ent and gqlgen (Part 1)

This tutorial walks through creating a maintainable GraphQL application using Go, demonstrating how to set up a Docker‑based MySQL database, configure Viper and Echo, define ent schemas, run migrations, integrate gqlgen with ent, and implement queries and mutations following Clean Architecture principles.

Code DAO
Code DAO
Code DAO
Building a Clean Architecture GraphQL App with ent and gqlgen (Part 1)

Folder Structure

The project follows a Clean Architecture layout with top‑level directories such as bin, cmd, config, docker, ent, graph, and pkg containing adapters, constants, entities, infrastructure, registry, and use‑case layers.

Setup Development Environment

Run a MySQL 8.0 container via Docker Compose.

Initialize the database with a SQL script and a helper init_db.sh script.

Add a Makefile target setup_db to invoke the script.

Use Viper to load a YAML configuration file ( config.yml) containing database credentials and server address.

version: '3'
services:
  mysql:
    platform: linux/x86_64
    image: mysql:8.0
    volumes:
      - mysql-data:/var/lib/mysql
    command: mysqld --character-set-server=utf8mb4 --collation-server=utf8mb4_0900_as_ci --default-authentication-plugin=mysql_native_password
    environment:
      TZ: "/usr/share/zoneinfo/Asia/Tokyo"
      MYSQL_ROOT_PASSWORD: root
    ports:
      - '3306:3306'
volumes:
  mysql-data:
    driver: local

Echo Server

Install Echo and its middleware, then create cmd/app/main.go to start an HTTP server that reads the configuration, registers a health endpoint, and logs fatal errors on startup.

package main

import (
    "golang-clean-architecture-ent-gqlgen/config"
    "net/http"
    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    config.ReadConfig(config.ReadConfigOption{})
    e := echo.New()
    e.Use(middleware.Recover())
    e.GET("/", func(c echo.Context) error {
        return c.String(http.StatusOK, "Welcome!")
    })
    e.Logger.Fatal(e.Start(":" + config.C.Server.Address))
}

Hot Reload with Air

Install [email protected] and add a .air.toml file to enable automatic recompilation during development.

Ent Setup

Install the Ent code generator, create User and Todo schemas, and run go generate ./ent to produce Go models and migration code.

// Fields of the User.
func (User) Fields() []ent.Field {
    return []ent.Field{
        field.String("name").NotEmpty().MaxLen(255),
        field.Int("age").Positive(),
        field.Time("created_at").Default(time.Now).Immutable(),
        field.Time("updated_at").Default(time.Now).Immutable(),
    }
}

Define a one‑to‑many relationship where a user can have many todos:

// Edges of the Todo.
func (Todo) Edges() []ent.Edge {
    return []ent.Edge{
        edge.From("user", User.Type).Ref("todos").Unique().Field("user_id"),
    }
}

// Edges of the User.
func (User) Edges() []ent.Edge {
    return []ent.Edge{
        edge.To("todos", Todo.Type),
    }
}

Database Migration

Create cmd/migration/main.go to read the config, open an Ent client, and call client.Schema.Create with drop‑index and drop‑column options.

func createDBSchema(client *ent.Client) {
    if err := client.Schema.Create(context.Background(), migrate.WithDropIndex(true), migrate.WithDropColumn(true)); err != nil {
        log.Fatalf("failed creating schema resources: %v", err)
    }
}

Add a migrate_schema target in the Makefile and run make migrate_schema to apply the schema.

gqlgen Integration

Install gqlgen and initialize it to generate the GraphQL skeleton. Update gqlgen.yml to autobind the Ent package.

autobind:
  - "golang-clean-architecture-ent-gqlgen/ent"

Create GraphQL schema files ( graph/user.graphqls, graph/todo.graphqls) defining types, queries, and mutations. Bind the generated resolvers to the Ent client.

type User {
    id: ID!
    name: String!
    age: Int!
    createdAt: String!
    updatedAt: String!
    todos: [Todo!]!
}

extend type Query {
    user(id: ID): User
    todo(id: ID): Todo
}

Resolver example for fetching a user by ID:

func (r *queryResolver) User(ctx context.Context, id *int) (*ent.User, error) {
    u, err := r.client.User.Query().Where(user.IDEQ(*id)).Only(ctx)
    if err != nil {
        return nil, err
    }
    return u, nil
}

Mutations

Add input types CreateUserInput and UpdateUserInput to the GraphQL schema, generate corresponding Ent mutation input structs, and implement resolver functions that call client.User.Create().SetInput(input).Save and client.User.UpdateOneID(input.ID).SetInput(input).Save.

func (r *mutationResolver) CreateUser(ctx context.Context, input ent.CreateUserInput) (*ent.User, error) {
    u, err := r.client.User.Create().SetInput(input).Save(ctx)
    if err != nil {
        return nil, err
    }
    return u, nil
}

Running the Application

Start the server with make start (which runs air) and access the GraphQL Playground at http://localhost:8080/playground. Example queries and mutations are shown in the article with screenshot images.

Air output log
Air output log
Database tables after migration
Database tables after migration
User query result
User query result
CreateUser mutation result
CreateUser mutation result

The guide ends with a note that further steps will be covered in subsequent parts.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

DockerGoClean ArchitectureMySQLGraphQLEchoentgqlgen
Code DAO
Written by

Code DAO

We deliver AI algorithm tutorials and the latest news, curated by a team of researchers from Peking University, Shanghai Jiao Tong University, Central South University, and leading AI companies such as Huawei, Kuaishou, and SenseTime. Join us in the AI alchemy—making life better!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.