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.
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: localEcho 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.
The guide ends with a note that further steps will be covered in subsequent parts.
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
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!
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.
