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 NewWebSocketHandler(params WebSocketParams) http.HandlerFunc
- func RegisterObjectType(name string, typeFactory func() *graphql.Object) *graphql.Object
- func UnmarshalSubscriptionMessage[T any](msg *Message) (*T, error)
- func ValidateGraphQLQuery(queryString string, schema *graphql.Schema) error
- type Connection
- type FieldConfig
- type FieldGenerator
- type FieldMiddleware
- type FieldResolveFn
- type GenericTypeInfo
- type GraphContext
- type InMemoryPubSub
- func (p *InMemoryPubSub) Close() error
- func (p *InMemoryPubSub) Publish(ctx context.Context, topic string, data interface{}) error
- func (p *InMemoryPubSub) Subscribe(ctx context.Context, topic string) <-chan *Message
- func (p *InMemoryPubSub) Unsubscribe(ctx context.Context, subscriptionID string) error
- type JSONTime
- type Message
- type MutationField
- type PageInfo
- type PaginatedResponse
- type PaginationArgs
- type PubSub
- type QueryField
- type ResolveParams
- type SchemaBuilder
- type SchemaBuilderParams
- type SubscriptionField
- type SubscriptionFilterFn
- type SubscriptionResolveFn
- type SubscriptionResolver
- func (s *SubscriptionResolver[T]) BuildSubscription() SubscriptionField
- func (s *SubscriptionResolver[T]) WithArgs(args graphql.FieldConfigArgument) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithDescription(desc string) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithFilter(fn SubscriptionFilterFn[T]) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithMiddleware(middleware FieldMiddleware) *SubscriptionResolver[T]
- func (s *SubscriptionResolver[T]) WithResolver(fn SubscriptionResolveFn[T]) *SubscriptionResolver[T]
- 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]
- type WSMessage
- type WebSocketManager
- type WebSocketParams
Constants ¶
const ( // Client -> Server (graphql-ws) MessageTypeConnectionInit = "connection_init" MessageTypeSubscribe = "subscribe" MessageTypePing = "ping" // Client -> Server (subscriptions-transport-ws - legacy) MessageTypeStart = "start" // Legacy equivalent of "subscribe" MessageTypeStop = "stop" // Legacy equivalent of "complete" // Server -> Client (graphql-ws) MessageTypeConnectionAck = "connection_ack" MessageTypeNext = "next" MessageTypeError = "error" MessageTypeComplete = "complete" MessageTypePong = "pong" // Server -> Client (subscriptions-transport-ws - legacy) MessageTypeData = "data" // Legacy equivalent of "next" MessageTypeConnectionError = "connection_error" // Legacy connection error MessageTypeConnectionKeepAlive = "ka" // Legacy keep-alive )
GraphQL WebSocket Protocol message types Supports both graphql-ws (new) and subscriptions-transport-ws (legacy) protocols
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 ( ErrPubSubClosed = newError("pubsub is closed") ErrSubscriptionNotFound = newError("subscription not found") )
Common errors
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 automatically detects WebSocket upgrade requests and handles them appropriately when subscriptions are enabled (EnableSubscriptions: true).
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)
- WebSocket upgrade requests are handled when EnableSubscriptions: true
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 without subscriptions:
// 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)
Example with subscriptions:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
handler := graph.NewHTTP(&graph.GraphContext{
SchemaParams: &graph.SchemaBuilderParams{
QueryFields: []graph.QueryField{getUserQuery()},
MutationFields: []graph.MutationField{createUserMutation()},
SubscriptionFields: []graph.SubscriptionField{userSubscription(pubsub)},
},
PubSub: pubsub,
EnableSubscriptions: true,
DEBUG: false,
})
http.Handle("/graphql", handler)
http.ListenAndServe(":8080", nil)
func NewWebSocketHandler ¶ added in v1.1.0
func NewWebSocketHandler(params WebSocketParams) http.HandlerFunc
NewWebSocketHandler creates an HTTP handler for WebSocket connections. This handler upgrades HTTP connections to WebSocket and manages GraphQL subscriptions.
Example:
params := graph.WebSocketParams{
Schema: schema,
PubSub: pubsub,
CheckOrigin: func(r *http.Request) bool {
origin := r.Header.Get("Origin")
return origin == "https://example.com"
},
AuthFn: func(r *http.Request) (interface{}, error) {
token := ExtractBearerToken(r)
return validateToken(token)
},
}
http.Handle("/graphql", graph.NewWebSocketHandler(params))
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 UnmarshalSubscriptionMessage ¶ added in v1.1.0
Helper function to unmarshal subscription messages
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 Connection ¶ added in v1.1.0
type Connection struct {
// contains filtered or unexported fields
}
Connection represents a single WebSocket connection.
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
// PubSub: PubSub system for subscriptions (optional, only needed for subscriptions)
// Use NewInMemoryPubSub() for development or RedisPubSub for production
PubSub PubSub
// EnableSubscriptions: Enable WebSocket support for GraphQL subscriptions
// Default: false (subscriptions disabled)
// Requires PubSub to be configured
EnableSubscriptions bool
// WebSocketPath: Path for WebSocket endpoint (default: same as HTTP endpoint)
// If not set, WebSocket connections will be handled on the same path as HTTP
WebSocketPath string
// WebSocketCheckOrigin: Custom function to check WebSocket upgrade origin
// If not provided, all origins are allowed (only use in development!)
WebSocketCheckOrigin func(r *http.Request) bool
// 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 InMemoryPubSub ¶ added in v1.1.0
type InMemoryPubSub struct {
// contains filtered or unexported fields
}
InMemoryPubSub is a simple in-memory implementation of PubSub. It's suitable for development, testing, and single-instance deployments. For production multi-instance deployments, use RedisPubSub or similar.
func NewInMemoryPubSub ¶ added in v1.1.0
func NewInMemoryPubSub() *InMemoryPubSub
NewInMemoryPubSub creates a new in-memory PubSub implementation.
Example:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
ctx := context.Background()
sub := pubsub.Subscribe(ctx, "events")
go func() {
for msg := range sub {
fmt.Println("Event:", string(msg.Data))
}
}()
pubsub.Publish(ctx, "events", map[string]string{"type": "user_created"})
func (*InMemoryPubSub) Close ¶ added in v1.1.0
func (p *InMemoryPubSub) Close() error
Close shuts down the PubSub and closes all active subscriptions.
func (*InMemoryPubSub) Publish ¶ added in v1.1.0
func (p *InMemoryPubSub) Publish(ctx context.Context, topic string, data interface{}) error
Publish sends data to all subscribers of the topic. Slow subscribers are skipped to prevent blocking.
func (*InMemoryPubSub) Subscribe ¶ added in v1.1.0
func (p *InMemoryPubSub) Subscribe(ctx context.Context, topic string) <-chan *Message
Subscribe creates a subscription to a topic. The subscription is automatically cleaned up when the context is canceled.
func (*InMemoryPubSub) Unsubscribe ¶ added in v1.1.0
func (p *InMemoryPubSub) Unsubscribe(ctx context.Context, subscriptionID string) error
Unsubscribe removes a subscription by ID (not commonly used with context-based cleanup).
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 Message ¶ added in v1.1.0
type Message struct {
// Topic is the channel/topic name where this message was published
Topic string
// Data is the JSON-encoded payload
Data []byte
}
Message represents a published message with its topic and data payload.
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 PubSub ¶ added in v1.1.0
type PubSub interface {
// Publish sends data to all subscribers of a topic.
// The data will be JSON-marshaled automatically.
//
// Returns an error if:
// - JSON marshaling fails
// - Context is canceled
// - PubSub is closed
Publish(ctx context.Context, topic string, data interface{}) error
// Subscribe creates a new subscription to a topic.
// Returns a channel that receives messages published to the topic.
//
// The subscription remains active until:
// - The context is canceled
// - Unsubscribe is called with the subscription ID
// - The PubSub is closed
//
// The returned channel will be closed when the subscription ends.
Subscribe(ctx context.Context, topic string) <-chan *Message
// Unsubscribe removes a subscription by its ID.
// The subscription's message channel will be closed.
Unsubscribe(ctx context.Context, subscriptionID string) error
// Close shuts down the PubSub system and closes all active subscriptions.
Close() error
}
PubSub defines the interface for publishing and subscribing to events. Implementations can use in-memory channels, Redis, Kafka, or other message brokers.
Example Usage:
pubsub := graph.NewInMemoryPubSub()
defer pubsub.Close()
// Subscribe to a topic
ctx := context.Background()
subscription := pubsub.Subscribe(ctx, "messages:channel1")
// Publish an event
pubsub.Publish(ctx, "messages:channel1", map[string]string{"text": "Hello"})
// Receive events
for msg := range subscription {
fmt.Println("Received:", string(msg.Data))
}
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"`
// SubscriptionFields: List of subscription fields to include in the schema
// Requires WebSocket support and PubSub configuration
SubscriptionFields []SubscriptionField `group:"subscription_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 SubscriptionField ¶ added in v1.1.0
type SubscriptionField interface {
// Serve returns the GraphQL field configuration
Serve() *graphql.Field
// Name returns the subscription field name
Name() string
}
SubscriptionField represents a GraphQL subscription field that can be added to a schema. It follows the same interface pattern as QueryField and MutationField.
Create subscription fields using NewSubscription:
sub := NewSubscription[MessageEvent]("messageAdded").
WithArgs(...).
WithResolver(...).
BuildSubscription()
type SubscriptionFilterFn ¶ added in v1.1.0
type SubscriptionFilterFn[T any] func(ctx context.Context, data *T, p ResolveParams) bool
SubscriptionFilterFn filters events before sending them to clients. Return true to send the event, false to skip it.
This is useful for:
- User-specific filtering based on permissions
- Content-based filtering based on subscription arguments
- Rate limiting or throttling
Example:
func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
userID, _ := GetRootString(p, "userID")
return canUserViewMessage(userID, data.ID)
}
type SubscriptionResolveFn ¶ added in v1.1.0
type SubscriptionResolveFn[T any] func(ctx context.Context, p ResolveParams) (<-chan *T, error)
SubscriptionResolveFn is the resolver function for subscriptions. It returns a channel that emits events of type T.
The resolver should:
- Create a buffered channel to prevent blocking
- Start a goroutine to handle event publishing
- Close the channel when done or context is canceled
- Handle errors by returning nil channel and an error
Example:
func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent, 10)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
if err := json.Unmarshal(msg.Data, &event); err == nil {
events <- &event
}
}
}()
return events, nil
}
type SubscriptionResolver ¶ added in v1.1.0
type SubscriptionResolver[T any] struct { // contains filtered or unexported fields }
SubscriptionResolver builds type-safe subscription fields with extensive customization capabilities. It provides a fluent API similar to UnifiedResolver for building subscriptions.
Type Parameters:
- T: The Go struct type that will be sent to subscribers
The resolver function should return a channel that emits events of type T. When the channel is closed or the context is canceled, the subscription ends.
Basic Usage:
type MessageEvent struct {
ID string `json:"id"`
Content string `json:"content"`
Timestamp time.Time `json:"timestamp"`
}
sub := NewSubscription[MessageEvent]("messageAdded").
WithDescription("Subscribe to new messages").
WithArgs(graphql.FieldConfigArgument{
"channelID": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
},
}).
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
json.Unmarshal(msg.Data, &event)
events <- &event
}
}()
return events, nil
}).
WithFilter(func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
// Optional: filter events before sending to client
return true
}).
WithMiddleware(AuthMiddleware("user")).
BuildSubscription()
Advanced Features:
// Middleware support
sub := NewSubscription[Event]("events").
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("admin")).
// ... rest of configuration
// Field-level customization (like UnifiedResolver)
sub := NewSubscription[ComplexEvent]("complexEvent").
WithFieldResolver("computedField", func(p ResolveParams) (interface{}, error) {
event := p.Source.(ComplexEvent)
return computeValue(event), nil
}).
// ... rest of configuration
func NewSubscription ¶ added in v1.1.0
func NewSubscription[T any](name string) *SubscriptionResolver[T]
NewSubscription creates a new subscription resolver with the specified name. The type parameter T determines the event type that will be sent to subscribers.
Example:
type UserStatusEvent struct {
UserID string `json:"userID"`
Status string `json:"status"`
Timestamp time.Time `json:"timestamp"`
}
sub := NewSubscription[UserStatusEvent]("userStatusChanged").
WithArgs(graphql.FieldConfigArgument{
"userID": &graphql.ArgumentConfig{Type: graphql.String},
}).
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *UserStatusEvent, error) {
// Implementation
}).
BuildSubscription()
func (*SubscriptionResolver[T]) BuildSubscription ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) BuildSubscription() SubscriptionField
BuildSubscription builds and returns a SubscriptionField that can be added to the schema.
This method:
- Auto-generates the GraphQL type from the Go struct T
- Applies field-level customizations and middleware
- Creates the subscription and resolve functions
- Registers the type in the global type registry
Example:
sub := NewSubscription[MessageEvent]("messageAdded").
WithArgs(...).
WithResolver(...).
BuildSubscription()
// Add to schema
schema := graph.NewSchemaBuilder(graph.SchemaBuilderParams{
SubscriptionFields: []graph.SubscriptionField{sub},
})
func (*SubscriptionResolver[T]) WithArgs ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithArgs(args graphql.FieldConfigArgument) *SubscriptionResolver[T]
WithArgs sets custom arguments for the subscription.
Example:
WithArgs(graphql.FieldConfigArgument{
"channelID": &graphql.ArgumentConfig{
Type: graphql.NewNonNull(graphql.String),
Description: "Channel to subscribe to",
},
"filter": &graphql.ArgumentConfig{
Type: graphql.String,
Description: "Optional filter pattern",
},
})
func (*SubscriptionResolver[T]) WithDescription ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithDescription(desc string) *SubscriptionResolver[T]
WithDescription adds a description to the subscription field.
func (*SubscriptionResolver[T]) WithFieldMiddleware ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFieldMiddleware(fieldName string, middleware FieldMiddleware) *SubscriptionResolver[T]
WithFieldMiddleware adds middleware to a specific field in the event type.
Example:
WithFieldMiddleware("sensitiveData", AuthMiddleware("admin"))
func (*SubscriptionResolver[T]) WithFieldResolver ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFieldResolver(fieldName string, resolver graphql.FieldResolveFn) *SubscriptionResolver[T]
WithFieldResolver overrides the resolver for a specific field in the event type. This allows customizing how specific fields are resolved.
Example:
WithFieldResolver("author", func(p ResolveParams) (interface{}, error) {
event := p.Source.(MessageEvent)
return userService.GetByID(event.AuthorID), nil
})
func (*SubscriptionResolver[T]) WithFilter ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithFilter(fn SubscriptionFilterFn[T]) *SubscriptionResolver[T]
WithFilter adds a filter function to filter events before sending to clients. Only events that pass the filter (return true) will be sent.
Example:
WithFilter(func(ctx context.Context, data *MessageEvent, p ResolveParams) bool {
userID, _ := GetRootString(p, "userID")
return data.AuthorID != userID // Don't send user's own messages
})
func (*SubscriptionResolver[T]) WithMiddleware ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithMiddleware(middleware FieldMiddleware) *SubscriptionResolver[T]
WithMiddleware adds middleware to the subscription resolver. Middleware is executed in the order it's added.
Example:
WithMiddleware(LoggingMiddleware).
WithMiddleware(AuthMiddleware("user"))
func (*SubscriptionResolver[T]) WithResolver ¶ added in v1.1.0
func (s *SubscriptionResolver[T]) WithResolver(fn SubscriptionResolveFn[T]) *SubscriptionResolver[T]
WithResolver sets the subscription resolver function. The resolver should return a channel that emits events of type T.
Example:
WithResolver(func(ctx context.Context, p ResolveParams) (<-chan *MessageEvent, error) {
channelID, _ := GetArgString(p, "channelID")
events := make(chan *MessageEvent, 10)
subscription := pubsub.Subscribe(ctx, "messages:"+channelID)
go func() {
defer close(events)
for msg := range subscription {
var event MessageEvent
if err := json.Unmarshal(msg.Data, &event); err == nil {
events <- &event
}
}
}()
return events, nil
})
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()
type WSMessage ¶ added in v1.1.0
type WSMessage struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Payload map[string]interface{} `json:"payload,omitempty"`
}
WSMessage represents a GraphQL WebSocket Protocol message. This follows the graphql-ws protocol specification.
type WebSocketManager ¶ added in v1.1.0
type WebSocketManager struct {
// contains filtered or unexported fields
}
WebSocketManager manages WebSocket connections for GraphQL subscriptions. It handles connection lifecycle, authentication, and message routing.
func (*WebSocketManager) CloseAllConnections ¶ added in v1.1.0
func (m *WebSocketManager) CloseAllConnections()
CloseAllConnections closes all active WebSocket connections. This is useful for graceful shutdown.
func (*WebSocketManager) HandleWebSocket ¶ added in v1.1.0
func (m *WebSocketManager) HandleWebSocket(w http.ResponseWriter, r *http.Request)
HandleWebSocket upgrades HTTP connections to WebSocket and manages the connection lifecycle.
type WebSocketParams ¶ added in v1.1.0
type WebSocketParams struct {
// Schema: The GraphQL schema with subscription fields
Schema *graphql.Schema
// PubSub: The PubSub system for event distribution
PubSub PubSub
// CheckOrigin: Function to check WebSocket upgrade origin
// If nil, all origins are allowed (development only!)
CheckOrigin func(r *http.Request) bool
// AuthFn: Authentication function to extract user details from request
// Called during connection_init phase
AuthFn func(r *http.Request) (interface{}, error)
// RootObjectFn: Custom function to set up root object for each connection
// Similar to HTTP handler's RootObjectFn
RootObjectFn func(ctx context.Context, r *http.Request) map[string]interface{}
// PingInterval: Interval for sending ping messages (default: 30 seconds)
// Set to 0 to disable automatic pinging
PingInterval time.Duration
// ConnectionTimeout: Timeout for connection_init message (default: 10 seconds)
ConnectionTimeout time.Duration
}
WebSocketParams configures the WebSocket handler for subscriptions.