GraphQL
Gofasta includes GraphQL support powered by gqlgen , a type-safe Go GraphQL server library. Your project ships with a working GraphQL endpoint alongside the REST API, both sharing the same services and repositories.
How It Works
GraphQL in Gofasta follows this flow:
Schema (.gql files) → gqlgen generates → Resolver stubs → You implement → Service layer- You define your schema in
.gqlfiles underapp/graphql/schema/ - Run
go generate ./...(orgofasta g resolver) to generate resolver stubs - Implement the resolver methods by calling your existing services
- Both REST and GraphQL share the same service and repository layers
Project Structure
app/graphql/
├── schema/
│ ├── schema.gql # Root schema (Query, Mutation, Subscription types)
│ ├── user.gql # User type and operations
│ └── product.gql # Product type and operations
├── resolvers/
│ ├── resolver.go # Resolver struct with service dependencies
│ ├── schema.resolvers.go # Generated resolver stubs
│ ├── user.resolvers.go # User resolver implementations
│ └── product.resolvers.go
├── generated/
│ └── generated.go # Auto-generated runtime code (do not edit)
└── model/
└── models_gen.go # Auto-generated Go types from schemaThe gqlgen.yml file at the project root controls code generation paths and type mappings.
Defining a Schema
Schema files use standard GraphQL SDL syntax. Each resource typically gets its own .gql file.
# app/graphql/schema/product.gql
type Product {
id: ID!
name: String!
price: Float!
createdAt: DateTime!
updatedAt: DateTime!
}
input CreateProductInput {
name: String!
price: Float!
}
input UpdateProductInput {
name: String
price: Float
}
extend type Query {
products(page: Int, perPage: Int): ProductConnection!
product(id: ID!): Product!
}
extend type Mutation {
createProduct(input: CreateProductInput!): Product!
updateProduct(id: ID!, input: UpdateProductInput!): Product!
deleteProduct(id: ID!): Boolean!
}The root schema file defines the base types and any custom scalars:
# app/graphql/schema/schema.gql
scalar DateTime
type Query
type Mutation
type Subscription
type PageInfo {
page: Int!
perPage: Int!
total: Int!
totalPages: Int!
}
type ProductConnection {
nodes: [Product!]!
pageInfo: PageInfo!
}Generating Resolvers
After modifying schema files, regenerate the resolver stubs:
go generate ./...Or use the Gofasta CLI:
gofasta g resolverThis updates schema.resolvers.go with new stub methods for any schema additions. Existing implementations are preserved — only new stubs are added.
Implementing Resolvers
The resolver struct holds references to your services, injected via Wire:
// app/graphql/resolvers/resolver.go
package resolvers
import "myapp/app/services/interfaces"
type Resolver struct {
UserService interfaces.UserService
ProductService interfaces.ProductService
}Resolver methods call the same services used by REST controllers:
// app/graphql/resolvers/product.resolvers.go
package resolvers
import (
"context"
"myapp/app/dtos"
"myapp/app/graphql/model"
)
func (r *queryResolver) Products(ctx context.Context, page *int, perPage *int) (*model.ProductConnection, error) {
p := 1
pp := 20
if page != nil {
p = *page
}
if perPage != nil {
pp = *perPage
}
params := &dtos.PaginationParams{Page: p, PerPage: pp}
products, total, err := r.ProductService.FindAll(ctx, params)
if err != nil {
return nil, err
}
totalPages := (int(total) + pp - 1) / pp
return &model.ProductConnection{
Nodes: products,
PageInfo: &model.PageInfo{
Page: p,
PerPage: pp,
Total: int(total),
TotalPages: totalPages,
},
}, nil
}
func (r *queryResolver) Product(ctx context.Context, id string) (*model.Product, error) {
return r.ProductService.FindByID(ctx, id)
}
func (r *mutationResolver) CreateProduct(ctx context.Context, input model.CreateProductInput) (*model.Product, error) {
req := &dtos.CreateProductRequest{
Name: input.Name,
Price: input.Price,
}
return r.ProductService.Create(ctx, req)
}
func (r *mutationResolver) UpdateProduct(ctx context.Context, id string, input model.UpdateProductInput) (*model.Product, error) {
req := &dtos.UpdateProductRequest{
Name: input.Name,
Price: input.Price,
}
return r.ProductService.Update(ctx, id, req)
}
func (r *mutationResolver) DeleteProduct(ctx context.Context, id string) (bool, error) {
err := r.ProductService.Delete(ctx, id)
return err == nil, err
}Subscriptions
gqlgen supports WebSocket-based subscriptions for real-time data:
# app/graphql/schema/product.gql
extend type Subscription {
productCreated: Product!
}Implement the subscription resolver with a channel:
func (r *subscriptionResolver) ProductCreated(ctx context.Context) (<-chan *model.Product, error) {
ch := make(chan *model.Product, 1)
go func() {
defer close(ch)
// Listen for events from your event system
for {
select {
case <-ctx.Done():
return
case product := <-r.ProductService.OnCreated():
ch <- product
}
}
}()
return ch, nil
}GraphQL Playground
In development, the GraphQL Playground is available at:
http://localhost:8080/graphql-playgroundThe GraphQL endpoint itself is at:
http://localhost:8080/graphqlYou can use the playground to explore your schema, run queries, and test mutations interactively.
Authentication in GraphQL
GraphQL requests pass through the same middleware stack as REST. The JWT auth middleware extracts the user from the Authorization header and sets it on the Gin context, which is accessible in resolvers:
func (r *mutationResolver) CreateProduct(ctx context.Context, input model.CreateProductInput) (*model.Product, error) {
ginCtx := ctx.Value("GinContextKey").(*gin.Context)
userID := ginCtx.GetString("user_id")
role := ginCtx.GetString("role")
// Use userID and role for authorization logic
// ...
}gqlgen Configuration
The gqlgen.yml file at your project root controls code generation:
schema:
- app/graphql/schema/*.gql
exec:
filename: app/graphql/generated/generated.go
package: generated
model:
filename: app/graphql/model/models_gen.go
package: model
resolver:
layout: follow-schema
dir: app/graphql/resolvers
package: resolvers
autobind:
- myapp/app/modelsThe autobind setting tells gqlgen to map GraphQL types to your existing Go model structs when names match, avoiding duplicate type definitions.
Adding a New GraphQL Resource
- Create a schema file in
app/graphql/schema/order.gql - Define the type, inputs, and extend Query/Mutation
- Run
go generate ./...to generate resolver stubs - Implement the resolver methods by calling your services
- Add the service dependency to the
Resolverstruct
If you generated the resource with gofasta g scaffold, the schema file and resolver are already created for you.