-
Notifications
You must be signed in to change notification settings - Fork 0
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.
- GraphQL as the interface language — No subcommands or flags, just queries and mutations
-
Schema introspection — AI agents can discover capabilities with
describeandtypes - 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
cd example
make buildThis runs gqlgen generate then go build -o myapp.
# 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 typestype 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!
}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.
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)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
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 resolver — Book.author uses @goField(forceResolver: true) to generate a dedicated resolver stub instead of auto-binding to the struct field. Useful for lazy-loading.
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.
After modifying the schema:
# Regenerate resolver stubs (preserves your implementations)
make generate
# Rebuild
make build
# Test
./myapp query '{ books { id title } }'# Install gqlgen if needed (Makefile handles this)
go install github.com/99designs/gqlgen@latest
make generategqlgen preserves existing resolver bodies and only adds stubs for new fields.
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 |