Documentation
¶
Overview ¶
Package graph provides a modern, secure GraphQL handler for Go with built-in authentication, validation, and an intuitive builder API.
Built on top of graphql-go (github.com/graphql-go/graphql), this package simplifies GraphQL server development with sensible defaults while maintaining full flexibility.
Features ¶
- Zero Config Start: Default hello world schema included
- Fluent Builder API: Clean, type-safe schema construction
- Built-in Authentication: Automatic Bearer token extraction
- Security First: Query depth, complexity, and introspection protection
- Response Sanitization: Remove field suggestions from errors
- Framework Agnostic: Works with net/http, Gin, Chi, or any HTTP framework
Quick Start ¶
Start immediately with the default schema:
import "github.com/paulmanoni/graph"
func main() {
handler := graph.NewHTTP(&graph.GraphContext{
Playground: true,
DEBUG: true,
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
}
Builder Pattern ¶
Use the fluent builder API for clean schema construction:
func getUser() graph.QueryField {
return graph.NewResolver[User]("user").
WithArgs(graphql.FieldConfigArgument{
"id": &graphql.ArgumentConfig{Type: graphql.String},
}).
WithResolver(func(p graphql.ResolveParams) (interface{}, error) {
id, _ := graph.GetArgString(p, "id")
return User{ID: id, Name: "Alice"}, nil
}).BuildQuery()
}
Authentication ¶
Automatic Bearer token extraction with optional user details fetching:
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getProtectedQuery()},
},
UserDetailsFn: func(token string) (interface{}, error) {
return validateAndGetUser(token)
},
})
Access token in resolvers:
func getProtectedQuery() graph.QueryField {
return graph.NewResolver[User]("me").
WithResolver(func(p graphql.ResolveParams) (interface{}, error) {
token, err := graph.GetRootString(p, "token")
if err != nil {
return nil, fmt.Errorf("authentication required")
}
// Use token...
}).BuildQuery()
}
Security ¶
Enable security features for production:
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false, // Enable security features
EnableValidation: true, // Max depth: 10, Max aliases: 4, Max complexity: 200
EnableSanitization: true, // Remove field suggestions from errors
Playground: false, // Disable playground in production
})
Helper Functions ¶
Extract arguments safely:
name, err := graph.GetArgString(p, "name") age, err := graph.GetArgInt(p, "age") active, err := graph.GetArgBool(p, "active")
Access root values:
token, err := graph.GetRootString(p, "token") var user User err := graph.GetRootInfo(p, "details", &user)
For more information, see https://github.com/paulmanoni/graph
Index ¶
- Constants
- Variables
- func AsyncFieldResolver(resolver graphql.FieldResolveFn) graphql.FieldResolveFn
- func CachedFieldResolver(cacheKey func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) graphql.FieldResolveFn
- func ConditionalResolver(condition func(graphql.ResolveParams) bool, ...) graphql.FieldResolveFn
- func DataTransformResolver(transform func(interface{}) interface{}) graphql.FieldResolveFn
- func ExtractBearerToken(r *http.Request) string
- func GenerateArgsFromStruct[T any]() graphql.FieldConfigArgument
- func GenerateGraphQLFields[T any]() graphql.Fields
- func GenerateGraphQLObject[T any](name string) *graphql.Object
- func GenerateInputObject[T any](name string) *graphql.InputObject
- func GetArg(p ResolveParams, key string, target interface{}) error
- func GetArgBool(p ResolveParams, key string) (bool, error)
- func GetArgInt(p ResolveParams, key string) (int, error)
- func GetArgString(p ResolveParams, key string) (string, error)
- func GetRootInfo(p ResolveParams, key string, target interface{}) error
- func GetRootString(p ResolveParams, key string) (string, error)
- func GetTypeName[T any]() string
- func LazyFieldResolver(fieldName string, loader func(interface{}) (interface{}, error)) graphql.FieldResolveFn
- func New(graphCtx GraphContext) (*handler.Handler, error)
- func NewHTTP(graphCtx *GraphContext) http.HandlerFunc
- func RegisterObjectType(name string, typeFactory func() *graphql.Object) *graphql.Object
- func ValidateGraphQLQuery(queryString string, schema *graphql.Schema) error
- type FieldConfig
- type FieldGenerator
- type FieldMiddleware
- type FieldResolveFn
- type GenericTypeInfo
- type GraphContext
- type JSONTime
- type MutationField
- type PageInfo
- type PaginatedResponse
- type PaginationArgs
- type QueryField
- type ResolveParams
- type SchemaBuilder
- type SchemaBuilderParams
- type TypedArgsResolver
- func (r *TypedArgsResolver[T, A]) AsList() *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) AsPaginated() *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) BuildMutation() MutationField
- func (r *TypedArgsResolver[T, A]) BuildQuery() QueryField
- func (r *TypedArgsResolver[T, A]) WithDescription(desc string) *TypedArgsResolver[T, A]
- func (r *TypedArgsResolver[T, A]) WithResolver(resolver func(ctx context.Context, p ResolveParams, args A) (*T, error)) *TypedArgsResolver[T, A]
- type UnifiedResolver
- func (r *UnifiedResolver[T]) AsList() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) AsMutation() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) AsPaginated() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) Build() interface{}
- func (r *UnifiedResolver[T]) BuildMutation() MutationField
- func (r *UnifiedResolver[T]) BuildQuery() QueryField
- func (r *UnifiedResolver[T]) Name() string
- func (r *UnifiedResolver[T]) Serve() *graphql.Field
- func (r *UnifiedResolver[T]) WithArgs(args graphql.FieldConfigArgument) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithArgsFromStruct(structType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithAsyncField(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithCachedField(fieldName string, cacheKeyFunc func(graphql.ResolveParams) string, ...) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithComputedField(name string, fieldType graphql.Output, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithCustomField(name string, field *graphql.Field) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithDescription(desc string) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithFieldResolvers(overrides map[string]graphql.FieldResolveFn) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObject(inputType interface{}) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObjectFieldName(name string) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithInputObjectNullable() *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithLazyField(fieldName string, loader func(interface{}) (interface{}, error)) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithMiddleware(middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithPermission(middleware FieldMiddleware) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithResolver(resolver func(p ResolveParams) (*T, error)) *UnifiedResolver[T]
- func (r *UnifiedResolver[T]) WithTypedResolver(typedResolver interface{}) *UnifiedResolver[T]
Constants ¶
const SpringShortLayout = "2006-01-02T15:04"
SpringShortLayout is the time format used by Spring Boot for DateTime serialization. Format: yyyy-MM-dd'T'HH:mm (e.g., "2024-01-15T14:30")
Variables ¶
var DateTime = graphql.NewScalar(graphql.ScalarConfig{ Name: "DateTime", Description: "The `DateTime` scalar type formatted as yyyy-MM-dd'T'HH:mm", Serialize: serializeDateTime, ParseValue: unserializeDateTime, ParseLiteral: func(valueAST ast.Value) interface{} { if v, ok := valueAST.(*ast.StringValue); ok { return unserializeDateTime(v.Value) } return nil }, })
DateTime is a GraphQL scalar type for date-time values. It uses the Spring Boot date format: yyyy-MM-dd'T'HH:mm (e.g., "2024-01-15T14:30"). All times are automatically converted to UTC.
Usage in struct fields:
type Event struct {
Name string `json:"name"`
StartTime time.Time `json:"startTime"` // Will use DateTime scalar
}
The scalar automatically handles:
- Serialization: time.Time → "2024-01-15T14:30"
- Deserialization: "2024-01-15T14:30" → time.Time
- UTC conversion for all values
Functions ¶
func AsyncFieldResolver ¶
func AsyncFieldResolver(resolver graphql.FieldResolveFn) graphql.FieldResolveFn
AsyncFieldResolver executes a resolver asynchronously
func CachedFieldResolver ¶
func CachedFieldResolver(cacheKey func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) graphql.FieldResolveFn
CachedFieldResolver caches field results with a key function
func ConditionalResolver ¶
func ConditionalResolver(condition func(graphql.ResolveParams) bool, ifTrue, ifFalse graphql.FieldResolveFn) graphql.FieldResolveFn
ConditionalResolver resolves based on a condition
func DataTransformResolver ¶
func DataTransformResolver(transform func(interface{}) interface{}) graphql.FieldResolveFn
DataTransformResolver applies a transformation to a field value
func ExtractBearerToken ¶
ExtractBearerToken extracts the Bearer token from the Authorization header. It performs case-insensitive matching for the "Bearer " prefix and trims whitespace.
Returns an empty string if:
- The Authorization header is missing
- The header doesn't start with "Bearer " (case-insensitive)
- The token value is empty
Example:
// Authorization: Bearer abc123xyz token := graph.ExtractBearerToken(r) // Returns: "abc123xyz"
func GenerateArgsFromStruct ¶
func GenerateArgsFromStruct[T any]() graphql.FieldConfigArgument
func GenerateGraphQLFields ¶
func GenerateInputObject ¶
func GenerateInputObject[T any](name string) *graphql.InputObject
func GetArg ¶
func GetArg(p ResolveParams, key string, target interface{}) error
GetArg safely extracts a value from p.Args and unmarshals it into the target. This is useful for extracting complex types like structs, slices, or maps.
The function handles:
- Primitive types (string, int, bool) with optimized direct assignment
- Complex types using JSON marshaling/unmarshaling for type conversion
- Type mismatches with descriptive error messages
Returns an error if:
- The argument key doesn't exist
- Type conversion fails
Example:
var input CreateUserInput
if err := graph.GetArg(p, "input", &input); err != nil {
return nil, err
}
// Use input.Name, input.Email, etc.
func GetArgBool ¶
func GetArgBool(p ResolveParams, key string) (bool, error)
GetArgBool safely extracts a bool argument from p.Args. Returns an error if the argument doesn't exist or is not a boolean.
Example:
active, err := graph.GetArgBool(p, "active")
func GetArgInt ¶
func GetArgInt(p ResolveParams, key string) (int, error)
GetArgInt safely extracts an int argument from p.Args. Handles both int and float64 types (JSON numbers are parsed as float64). Returns an error if the argument doesn't exist or is not a number.
Example:
age, err := graph.GetArgInt(p, "age")
func GetArgString ¶
func GetArgString(p ResolveParams, key string) (string, error)
GetArgString safely extracts a string argument from p.Args. Returns an error if the argument doesn't exist or is not a string.
Example:
name, err := graph.GetArgString(p, "name")
func GetRootInfo ¶
func GetRootInfo(p ResolveParams, key string, target interface{}) error
GetRootInfo safely extracts a value from p.Info.RootValue and unmarshals it into the target. This is commonly used to retrieve user details set by UserDetailsFn in the GraphContext.
The function handles:
- Primitive types (string, int) with optimized direct assignment
- Complex types using JSON marshaling/unmarshaling for type conversion
- Type mismatches with descriptive error messages
Returns an error if:
- Root value is nil or not a map
- The key doesn't exist in the root value
- Type conversion fails
Example:
// In your resolver
var user UserDetails
if err := graph.GetRootInfo(p, "details", &user); err != nil {
return nil, fmt.Errorf("authentication required")
}
// Use user.ID, user.Email, etc.
func GetRootString ¶
func GetRootString(p ResolveParams, key string) (string, error)
GetRootString safely extracts a string value from p.Info.RootValue. This is commonly used to retrieve the authentication token.
Returns an error if:
- Root value is nil or not a map
- The key doesn't exist in the root value
- The value is not a string
Example:
// Get authentication token
token, err := graph.GetRootString(p, "token")
if err != nil {
return nil, fmt.Errorf("authentication required")
}
// Validate token...
func GetTypeName ¶
func LazyFieldResolver ¶
func LazyFieldResolver(fieldName string, loader func(interface{}) (interface{}, error)) graphql.FieldResolveFn
LazyFieldResolver loads a field only when requested
func New ¶
func New(graphCtx GraphContext) (*handler.Handler, error)
New creates a GraphQL handler from the provided GraphContext. It builds the schema and sets up authentication with token extraction and user details.
The handler automatically:
- Extracts tokens using TokenExtractorFn (defaults to Bearer token extraction)
- Fetches user details using UserDetailsFn if provided
- Adds token and details to the root value for access in resolvers
Returns an error if schema building fails.
Example:
handler, err := graph.New(graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
Playground: true,
})
func NewHTTP ¶
func NewHTTP(graphCtx *GraphContext) http.HandlerFunc
NewHTTP creates a standard http.HandlerFunc with built-in validation and sanitization support. This is the recommended way to create a GraphQL handler for production use.
The handler is fully compatible with net/http and any HTTP framework (Gin, Chi, Echo, etc.). If graphCtx is nil, defaults to DEBUG mode with Playground enabled.
Behavior:
- In DEBUG mode (DEBUG: true): Skips all validation and sanitization for easier development
- In production (DEBUG: false): Enables validation and sanitization based on configuration
- Panics during initialization if schema building fails (fail-fast approach)
Security Features (when DEBUG: false):
- EnableValidation: Validates query depth (max 10), aliases (max 4), complexity (max 200), and blocks introspection
- EnableSanitization: Removes field suggestions from error messages to prevent information disclosure
Example:
// Development setup
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
DEBUG: true,
Playground: true,
})
// Production setup
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false,
EnableValidation: true,
EnableSanitization: true,
Playground: false,
UserDetailsFn: func(token string) (interface{}, error) {
return validateToken(token)
},
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
func RegisterObjectType ¶
RegisterObjectType registers a GraphQL object type in the global registry Returns existing type if already registered, otherwise creates and registers new type
func ValidateGraphQLQuery ¶
ValidateGraphQLQuery validates a GraphQL query against security rules. This function implements multiple layers of protection against malicious or expensive queries.
Validation Rules:
- Max Query Depth: 10 levels (prevents deeply nested queries)
- Max Aliases: 4 per query (prevents alias-based DoS attacks)
- Max Complexity: 200 (prevents computationally expensive queries)
- Introspection: Blocked (__schema and __type queries are rejected)
Returns an error if:
- Query depth exceeds 10 levels
- Query contains more than 4 aliases
- Query complexity exceeds 200
- Query contains __schema or __type introspection fields
- Query parsing fails (though parsing errors are allowed to pass through)
Example usage:
if err := graph.ValidateGraphQLQuery(queryString, schema); err != nil {
// Reject query with HTTP 400
return fmt.Errorf("invalid query: %w", err)
}
// Query is safe to execute
Enable this in production with GraphContext.EnableValidation = true.
Types ¶
type FieldConfig ¶
type FieldConfig struct {
Resolver graphql.FieldResolveFn
Description string
Args graphql.FieldConfigArgument
DeprecationReason string
}
type FieldGenerator ¶
type FieldGenerator[T any] struct { // contains filtered or unexported fields }
func NewFieldGenerator ¶
func NewFieldGenerator[T any]() *FieldGenerator[T]
type FieldMiddleware ¶
type FieldMiddleware func(next FieldResolveFn) FieldResolveFn
FieldMiddleware wraps a field resolver with additional functionality (auth, logging, caching, etc.)
func AuthMiddleware ¶
func AuthMiddleware(requiredRole string) FieldMiddleware
AuthMiddleware requires a specific user role
func CacheMiddleware ¶
func CacheMiddleware(cacheKey func(ResolveParams) string) FieldMiddleware
CacheMiddleware caches field results based on a key function
type FieldResolveFn ¶
type FieldResolveFn func(p ResolveParams) (interface{}, error)
func LoggingMiddleware ¶
func LoggingMiddleware(next FieldResolveFn) FieldResolveFn
LoggingMiddleware logs field resolution time
type GenericTypeInfo ¶
type GenericTypeInfo struct {
IsGeneric bool
IsWrapper bool
BaseTypeName string
ElementType reflect.Type
WrapperFields map[string]reflect.Type
}
GenericTypeInfo holds information about a generic type
type GraphContext ¶
type GraphContext struct {
// Schema: Provide either Schema OR SchemaParams (not both)
// If both are nil, a default "hello world" schema will be created
Schema *graphql.Schema
// SchemaParams: Alternative to Schema - will be built automatically
// If nil and Schema is also nil, defaults to hello world query/mutation
SchemaParams *SchemaBuilderParams
// Pretty: Pretty-print JSON responses
Pretty bool
// GraphiQL: Enable GraphiQL interface (deprecated, use Playground instead)
GraphiQL bool
// Playground: Enable GraphQL Playground interface
Playground bool
// DEBUG mode skips validation and sanitization for easier development
// Default: false (validation enabled)
DEBUG bool
// RootObjectFn: Custom function to set up root object for each request
// Called before token extraction and user details fetching
RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
// TokenExtractorFn: Custom token extraction from request
// If not provided, default Bearer token extraction will be used
TokenExtractorFn func(*http.Request) string
// UserDetailsFn: Custom user details fetching based on token
// If not provided, user details will not be added to rootValue
// The details are accessible in resolvers via GetRootInfo(p, "details", &user)
UserDetailsFn func(token string) (interface{}, error)
// EnableValidation: Enable query validation (depth, complexity, introspection checks)
// Default: false (validation disabled)
// When enabled: Max depth=10, Max aliases=4, Max complexity=200, Introspection blocked
EnableValidation bool
// EnableSanitization: Enable response sanitization (removes field suggestions from errors)
// Default: false (sanitization disabled)
// Prevents information disclosure by removing "Did you mean X?" suggestions
EnableSanitization bool
}
GraphContext configures a GraphQL handler with schema, authentication, and security settings.
Schema Configuration (choose one):
- Schema: Use a pre-built graphql.Schema
- SchemaParams: Use the builder pattern with QueryFields and MutationFields
- Neither: A default "hello world" schema will be created
Security Modes:
- DEBUG mode (DEBUG: true): Disables all validation and sanitization for development
- Production mode (DEBUG: false): Enables validation and sanitization based on configuration flags
Authentication:
- TokenExtractorFn: Extract tokens from requests (defaults to Bearer token extraction)
- UserDetailsFn: Fetch user details from the extracted token
- RootObjectFn: Custom root object setup for advanced use cases
Example Development Setup:
ctx := &graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
},
DEBUG: true,
Playground: true,
}
Example Production Setup:
ctx := &graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{...},
DEBUG: false,
EnableValidation: true, // Max depth: 10, Max aliases: 4, Max complexity: 200
EnableSanitization: true, // Remove field suggestions from errors
Playground: false,
UserDetailsFn: func(token string) (interface{}, error) {
return validateJWT(token)
},
}
type JSONTime ¶
JSONTime is a custom time type that handles flexible JSON time formats. It supports both RFC3339 strings and array formats commonly used in some APIs.
Supported input formats:
- RFC3339 string: "2024-01-15T14:30:00Z"
- Array format: [year, month, day, hour, minute, second, nanosecond]
- Minimum array: [2024, 1, 15] (time components default to 0)
Output format:
- Always RFC3339 string: "2024-01-15T14:30:00Z"
Example usage:
type Event struct {
Name string `json:"name"`
StartTime graph.JSONTime `json:"startTime"`
}
// Accepts: {"startTime": "2024-01-15T14:30:00Z"}
// Accepts: {"startTime": [2024, 1, 15, 14, 30, 0]}
// Outputs: {"startTime": "2024-01-15T14:30:00Z"}
func (JSONTime) MarshalJSON ¶
MarshalJSON implements json.Marshaler interface. Serializes JSONTime to RFC3339 format string.
func (JSONTime) Time ¶
Time converts JSONTime back to the standard time.Time type. This is useful when you need to perform time operations or comparisons.
Example:
var event Event
json.Unmarshal(data, &event)
standardTime := event.StartTime.Time()
// Now use standard time operations
if standardTime.After(time.Now()) { ... }
func (*JSONTime) UnmarshalJSON ¶
UnmarshalJSON implements json.Unmarshaler interface. Deserializes from either RFC3339 string or array format.
Array format: [year, month, day, hour?, minute?, second?, nanosecond?] Missing time components default to 0. All times are assumed to be UTC.
type MutationField ¶
type MutationField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the field name used in the GraphQL schema
Name() string
}
MutationField represents a GraphQL mutation field with its configuration. Implementations must provide both the field configuration and its name.
Use NewResolver to create MutationField instances:
mutation := graph.NewResolver[User]("createUser").
WithInputObject(CreateUserInput{}).
WithResolver(...).
BuildMutation()
type PageInfo ¶
type PageInfo struct {
HasNextPage bool `json:"hasNextPage" description:"Whether there are more pages"`
HasPreviousPage bool `json:"hasPreviousPage" description:"Whether there are previous pages"`
StartCursor string `json:"startCursor" description:"Cursor for the first item"`
EndCursor string `json:"endCursor" description:"Cursor for the last item"`
}
PageInfo contains pagination information
type PaginatedResponse ¶
type PaginatedResponse[T any] struct { Items []T `json:"items" description:"List of items"` TotalCount int `json:"totalCount" description:"Total number of items"` PageInfo PageInfo `json:"pageInfo" description:"Pagination information"` }
PaginatedResponse represents a paginated response structure
type PaginationArgs ¶
type PaginationArgs struct {
First *int `json:"first" description:"Number of items to fetch"`
After *string `json:"after" description:"Cursor to start after"`
Last *int `json:"last" description:"Number of items to fetch from end"`
Before *string `json:"before" description:"Cursor to start before"`
}
PaginationArgs contains pagination arguments
type QueryField ¶
type QueryField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the field name used in the GraphQL schema
Name() string
}
QueryField represents a GraphQL query field with its configuration. Implementations must provide both the field configuration and its name.
Use NewResolver to create QueryField instances:
query := graph.NewResolver[User]("user").
WithArgs(...).
WithResolver(...).
BuildQuery()
type ResolveParams ¶
type ResolveParams graphql.ResolveParams
type SchemaBuilder ¶
type SchemaBuilder struct {
// contains filtered or unexported fields
}
SchemaBuilder builds GraphQL schemas from QueryFields and MutationFields. Use NewSchemaBuilder to create an instance and Build() to generate the schema.
func NewSchemaBuilder ¶
func NewSchemaBuilder(params SchemaBuilderParams) *SchemaBuilder
NewSchemaBuilder creates a new schema builder with the provided query and mutation fields.
Example:
params := graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
MutationFields: []graph.MutationField{createUserMutation()},
}
builder := graph.NewSchemaBuilder(params)
schema, err := builder.Build()
func (*SchemaBuilder) Build ¶
func (sb *SchemaBuilder) Build() (graphql.Schema, error)
Build constructs and returns a graphql.Schema from the configured fields. It creates Query and Mutation root types based on the provided fields.
Returns an error if:
- Schema construction fails due to type conflicts
- Field configurations are invalid
The schema can have:
- Only queries (no mutations)
- Only mutations (no queries)
- Both queries and mutations
- Neither (empty schema)
type SchemaBuilderParams ¶
type SchemaBuilderParams struct {
// QueryFields: List of query fields to include in the schema
QueryFields []QueryField `group:"query_fields"`
// MutationFields: List of mutation fields to include in the schema
MutationFields []MutationField `group:"mutation_fields"`
}
SchemaBuilderParams configures the fields for building a GraphQL schema. Use this with NewSchemaBuilder to construct schemas using the builder pattern.
Example:
params := graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{
getUserQuery(),
listUsersQuery(),
},
MutationFields: []graph.MutationField{
createUserMutation(),
updateUserMutation(),
},
}
schema, err := graph.NewSchemaBuilder(params).Build()
type TypedArgsResolver ¶
TypedArgsResolver provides type-safe argument handling
func NewArgsResolver ¶
func NewArgsResolver[T any, A any](name string, argName ...string) *TypedArgsResolver[T, A]
func (*TypedArgsResolver[T, A]) AsList ¶
func (r *TypedArgsResolver[T, A]) AsList() *TypedArgsResolver[T, A]
AsList configures the resolver to return a list of items
func (*TypedArgsResolver[T, A]) AsPaginated ¶
func (r *TypedArgsResolver[T, A]) AsPaginated() *TypedArgsResolver[T, A]
AsPaginated configures the resolver to return paginated results
func (*TypedArgsResolver[T, A]) BuildMutation ¶
func (r *TypedArgsResolver[T, A]) BuildMutation() MutationField
BuildMutation builds and returns a MutationField
func (*TypedArgsResolver[T, A]) BuildQuery ¶
func (r *TypedArgsResolver[T, A]) BuildQuery() QueryField
BuildQuery builds and returns a QueryField
func (*TypedArgsResolver[T, A]) WithDescription ¶
func (r *TypedArgsResolver[T, A]) WithDescription(desc string) *TypedArgsResolver[T, A]
WithDescription sets the field description
func (*TypedArgsResolver[T, A]) WithResolver ¶
func (r *TypedArgsResolver[T, A]) WithResolver(resolver func(ctx context.Context, p ResolveParams, args A) (*T, error)) *TypedArgsResolver[T, A]
WithResolver sets a type-safe resolver with typed arguments and context support
Example usage:
type GetPostArgs struct {
ID int `graphql:"id,required"`
}
resolver.WithArgs[GetPostArgs]().
WithResolver(func(ctx context.Context, args GetPostArgs) (*Post, error) {
return postService.GetByID(args.ID)
})
type UnifiedResolver ¶
type UnifiedResolver[T any] struct { // contains filtered or unexported fields }
UnifiedResolver handles all GraphQL resolver scenarios with field-level customization
func NewResolver ¶
func NewResolver[T any](name string) *UnifiedResolver[T]
func (*UnifiedResolver[T]) AsList ¶
func (r *UnifiedResolver[T]) AsList() *UnifiedResolver[T]
Query Configuration
func (*UnifiedResolver[T]) AsMutation ¶
func (r *UnifiedResolver[T]) AsMutation() *UnifiedResolver[T]
Mutation Configuration
func (*UnifiedResolver[T]) AsPaginated ¶
func (r *UnifiedResolver[T]) AsPaginated() *UnifiedResolver[T]
func (*UnifiedResolver[T]) Build ¶
func (r *UnifiedResolver[T]) Build() interface{}
func (*UnifiedResolver[T]) BuildMutation ¶
func (r *UnifiedResolver[T]) BuildMutation() MutationField
func (*UnifiedResolver[T]) BuildQuery ¶
func (r *UnifiedResolver[T]) BuildQuery() QueryField
Build Methods
func (*UnifiedResolver[T]) Name ¶
func (r *UnifiedResolver[T]) Name() string
Interface Implementation
func (*UnifiedResolver[T]) Serve ¶
func (r *UnifiedResolver[T]) Serve() *graphql.Field
func (*UnifiedResolver[T]) WithArgs ¶
func (r *UnifiedResolver[T]) WithArgs(args graphql.FieldConfigArgument) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithArgsFromStruct ¶
func (r *UnifiedResolver[T]) WithArgsFromStruct(structType interface{}) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithAsyncField ¶
func (r *UnifiedResolver[T]) WithAsyncField(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithCachedField ¶
func (r *UnifiedResolver[T]) WithCachedField(fieldName string, cacheKeyFunc func(graphql.ResolveParams) string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithComputedField ¶
func (r *UnifiedResolver[T]) WithComputedField(name string, fieldType graphql.Output, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithCustomField ¶
func (r *UnifiedResolver[T]) WithCustomField(name string, field *graphql.Field) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithDescription ¶
func (r *UnifiedResolver[T]) WithDescription(desc string) *UnifiedResolver[T]
Basic Configuration
func (*UnifiedResolver[T]) WithFieldMiddleware ¶
func (r *UnifiedResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithFieldResolver ¶
func (r *UnifiedResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *UnifiedResolver[T]
Field-Level Customization
func (*UnifiedResolver[T]) WithFieldResolvers ¶
func (r *UnifiedResolver[T]) WithFieldResolvers(overrides map[string]graphql.FieldResolveFn) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObject ¶
func (r *UnifiedResolver[T]) WithInputObject(inputType interface{}) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObjectFieldName ¶
func (r *UnifiedResolver[T]) WithInputObjectFieldName(name string) *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithInputObjectNullable ¶
func (r *UnifiedResolver[T]) WithInputObjectNullable() *UnifiedResolver[T]
func (*UnifiedResolver[T]) WithLazyField ¶
func (r *UnifiedResolver[T]) WithLazyField(fieldName string, loader func(interface{}) (interface{}, error)) *UnifiedResolver[T]
Utility Methods for Field Configuration
func (*UnifiedResolver[T]) WithMiddleware ¶
func (r *UnifiedResolver[T]) WithMiddleware(middleware FieldMiddleware) *UnifiedResolver[T]
WithMiddleware adds middleware to the main resolver. Middleware functions are applied in the order they are added (first added = outermost layer). This is the foundation for all resolver-level middleware (auth, logging, caching, etc.).
Example usage:
NewResolver[User]("user").
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("admin")).
WithResolver(func(p ResolveParams) (*User, error) {
return userService.GetByID(p.Args["id"].(int))
}).
BuildQuery()
func (*UnifiedResolver[T]) WithPermission ¶
func (r *UnifiedResolver[T]) WithPermission(middleware FieldMiddleware) *UnifiedResolver[T]
WithPermission adds permission middleware to the resolver (similar to Python @permission_classes decorator) This is now just a convenience wrapper around WithMiddleware for backwards compatibility
func (*UnifiedResolver[T]) WithResolver ¶
func (r *UnifiedResolver[T]) WithResolver(resolver func(p ResolveParams) (*T, error)) *UnifiedResolver[T]
WithResolver sets a type-safe resolver function that returns *T instead of interface{} This provides better type safety and eliminates the need for type assertions or casts
Example usage:
NewResolver[User]("user").
WithResolver(func(p graph.ResolveParams) (*User, error) {
id, _ := GetArgInt(p, "id")
return userService.GetByID(id)
}).BuildQuery()
NewResolver[User]("users").
AsList().
WithResolver(func(p graph.ResolveParams) (*[]User, error) {
users := userService.List()
return &users, nil
}).BuildQuery()
NewResolver[string]("hello").
WithResolver(func(p graph.ResolveParams) (*string, error) {
msg := "Hello, World!"
return &msg, nil
}).BuildQuery()
func (*UnifiedResolver[T]) WithTypedResolver ¶
func (r *UnifiedResolver[T]) WithTypedResolver(typedResolver interface{}) *UnifiedResolver[T]
Typed Resolver Support - allows direct struct parameters instead of graphql.ResolveParams
Example usage:
func resolveUser(args GetUserArgs) (*User, error) {
return &User{ID: args.ID, Name: "User"}, nil
}
NewResolver[User]("user", "User").
WithTypedResolver(resolveUser).
BuildQuery()