Skip to content

Example Application

Wallace Ricardo edited this page Apr 8, 2026 · 1 revision

Example Application

The example/ directory in the repository contains a complete working GraphQL-native CLI — a book management tool built with gqlgen and gqlcli's inline execution.

What It Demonstrates

  • GraphQL as the interface language — No subcommands or flags, just queries and mutations
  • Schema introspection — AI agents can discover capabilities with describe and types
  • Inline execution — No HTTP server, runs in-process against a gqlgen schema
  • Forced resolvers@goField(forceResolver: true) for lazy-loading relationships
  • File-based persistence — Data stored in store.json
  • Split schema files — Organized with follow-schema layout

Building

cd example
make build

This runs gqlgen generate then go build -o myapp.

Usage

# Query all books
./myapp query '{ books { id title author { id name } } }'

# Add a book
./myapp mutation 'mutation {
  addBook(input: {title: "Clean Code", authorName: "Robert C. Martin"}) {
    id title author { id name }
  }
}'

# Fetch a specific book
./myapp query '{ book(id: "1") { id title author { name } } }'

# Explore the schema
./myapp describe Book
./myapp describe Query
./myapp describe AddBookInput
./myapp types

Schema

type Query {
  books: [Book!]!
  book(id: ID!): Book
}

type Mutation {
  addBook(input: AddBookInput!): Book!
}

type Book {
  id: ID!
  title: String!
  author: Author! @goField(forceResolver: true)
}

type Author {
  id: ID!
  name: String!
}

input AddBookInput {
  title: String!
  authorName: String!
}

Persistence

Data is stored in store.json:

{
  "books": [
    {
      "id": "1",
      "title": "The Go Programming Language",
      "author": {
        "id": "a1",
        "name": "Donovan & Kernighan"
      }
    }
  ],
  "authors": [
    {"id": "a1", "name": "Donovan & Kernighan"}
  ],
  "nextId": 2
}

Each mutation calls save() to write changes. No database needed.

Code Organization

main.go

r := graph.NewResolver()
execSchema := graph.NewExecutableSchema(graph.Config{Resolvers: r})

exec := gqlcli.NewInlineExecutor(execSchema,
  gqlcli.WithSchemaHints(),
)

commands := gqlcli.NewInlineCommandSet(exec)
app := &cli.App{Name: "myapp", Usage: "Book management CLI"}
commands.Mount(app)
app.Run(os.Args)

File Structure

example/
├── main.go
├── graph/
│   ├── book.graphqls          # Schema definitions for Book/Query/Mutation
│   ├── author.graphqls        # Schema for Author type
│   ├── book.resolvers.go      # Resolver implementations (edit this)
│   ├── generated.go           # NEVER EDIT
│   ├── model/
│   │   └── models_gen.go      # NEVER EDIT
│   └── resolver.go            # Root resolver with persistence
├── store.json                 # Runtime data
└── Makefile

Resolver Highlights

Author deduplication — when adding a book, authors are reused by name:

func (r *Resolver) getOrCreateAuthor(name string) *model.Author {
  for _, a := range r.authors {
    if a.Name == name {
      return a
    }
  }
  author := &model.Author{ID: fmt.Sprintf("a%d", r.nextID), Name: name}
  r.authors = append(r.authors, author)
  return author
}

Forced resolverBook.author uses @goField(forceResolver: true) to generate a dedicated resolver stub instead of auto-binding to the struct field. Useful for lazy-loading.

For AI Agents

This pattern is especially useful for agents:

# Agent discovers what's available
./myapp types
./myapp describe Query
./myapp describe AddBookInput

# Agent constructs a complex query
./myapp query '{
  books { id title author { id name } }
  specificBook: book(id: "1") { title }
}'

# Agent explores input type before mutating
./myapp describe AddBookInput
# → input AddBookInput { title: String!, authorName: String! }

# Agent executes mutation
./myapp mutation 'mutation {
  addBook(input: {title: "SICP", authorName: "Abelson & Sussman"}) {
    id title author { name }
  }
}'

The schema is self-documenting — no external docs needed.

Development Cycle

After modifying the schema:

# Regenerate resolver stubs (preserves your implementations)
make generate

# Rebuild
make build

# Test
./myapp query '{ books { id title } }'

Regenerating After Schema Changes

# Install gqlgen if needed (Makefile handles this)
go install github.com/99designs/gqlgen@latest

make generate

gqlgen preserves existing resolver bodies and only adds stubs for new fields.

Extending the Example

This is a minimal MVP. To make it production-ready:

Feature How
Real database Replace store.json with your ORM in resolver.go
Auth Add middleware in main.go
Pagination Add first, after args to list fields
Subscriptions Use gqlgen's subscription support
N+1 prevention Add DataLoaders
Logging Inject structured logger via context

Clone this wiki locally