Documentation
¶
Overview ¶
Package graphqlws implements GraphQL over WebSocket using the "graphql-transport-ws" subprotocol.
The package exposes an HTTP handler wrapper that upgrades WebSocket requests and multiplexes GraphQL operations over a single socket. Non-WebSocket requests are delegated to the wrapped HTTP handler, allowing a single endpoint (for example, "/graphql") to serve both HTTP and WebSocket traffic.
Protocol behavior follows the GraphQL over WebSocket RFC, including connection initialisation, ping/pong, subscribe/next/error/complete flow, and protocol close codes for invalid client behavior.
A Subscriber implementation is required to execute operations:
type Subscriber interface {
Subscribe(ctx context.Context, document string, operation string, variables map[string]any) (<-chan any, error)
}
Common configuration is provided through options such as read limit, write timeout, custom origin checks, and maximum concurrent subscriptions per connection.
Security note: the default upgrader accepts all origins for ease of local development. Production deployments should set WithCheckOrigin to restrict trusted origins.
See README.md and example_test.go for end-to-end usage.
Example ¶
package main
import (
"context"
"encoding/json"
"fmt"
"net/http/httptest"
"strings"
"github.com/gorilla/websocket"
graphql "github.com/graph-gophers/graphql-go"
"github.com/graph-gophers/graphql-go/relay"
graphqlws "github.com/graph-gophers/graphql-transport-ws"
)
const schemaSDL = `
schema {
query: Query
subscription: Subscription
}
type Query {
hello: String!
}
type Subscription {
ticks: TickEvent!
}
type TickEvent {
at: String!
count: Int!
}
`
type rootResolver struct{}
type queryResolver struct{}
type subscriptionResolver struct{}
type tickEventResolver struct {
at string
count int32
}
func (*rootResolver) Query() *queryResolver {
return &queryResolver{}
}
func (*rootResolver) Subscription() *subscriptionResolver {
return &subscriptionResolver{}
}
func (*queryResolver) Hello() string {
return "hello from graphql-transport-ws"
}
func (*subscriptionResolver) Ticks(ctx context.Context) (chan *tickEventResolver, error) {
updates := make(chan *tickEventResolver, 1)
updates <- &tickEventResolver{at: "2026-03-28T00:00:00Z", count: 1}
close(updates)
return updates, nil
}
func (r *tickEventResolver) At() string {
return r.at
}
func (r *tickEventResolver) Count() int32 {
return r.count
}
func main() {
schema := graphql.MustParseSchema(schemaSDL, &rootResolver{})
rh := &relay.Handler{Schema: schema}
h := graphqlws.NewHandlerFunc(schema, rh)
srv := httptest.NewServer(h)
defer srv.Close()
wsURL := "ws" + strings.TrimPrefix(srv.URL, "http")
dialer := websocket.Dialer{Subprotocols: []string{graphqlws.ProtocolGraphQLTransportWS}}
conn, _, err := dialer.Dial(wsURL, nil)
if err != nil {
panic(err)
}
defer conn.Close()
type message struct {
ID string `json:"id,omitempty"`
Type string `json:"type"`
Payload json.RawMessage `json:"payload,omitempty"`
}
if err := conn.WriteJSON(message{Type: "connection_init"}); err != nil {
panic(err)
}
if err := conn.WriteJSON(message{
ID: "1",
Type: "subscribe",
Payload: json.RawMessage(`{
"query": "subscription { ticks { at count } }"
}`),
}); err != nil {
panic(err)
}
var ack message
if err := conn.ReadJSON(&ack); err != nil {
panic(err)
}
var next message
if err := conn.ReadJSON(&next); err != nil {
panic(err)
}
var complete message
if err := conn.ReadJSON(&complete); err != nil {
panic(err)
}
fmt.Println(conn.Subprotocol())
fmt.Println(ack.Type)
fmt.Println(string(next.Payload))
fmt.Println(complete.Type)
}
Output: graphql-transport-ws connection_ack {"data":{"ticks":{"at":"2026-03-28T00:00:00Z","count":1}}} complete
Index ¶
Examples ¶
Constants ¶
const ( // ProtocolGraphQLTransportWS is the modern websocket subprotocol ID for GraphQL over WebSocket. // see https://github.com/enisdenjo/graphql-ws ProtocolGraphQLTransportWS = "graphql-transport-ws" )
Variables ¶
This section is empty.
Functions ¶
func NewHandlerFunc ¶
func NewHandlerFunc(svc Subscriber, httpHandler http.Handler, options ...Option) http.HandlerFunc
NewHandlerFunc returns an http.HandlerFunc that supports GraphQL over websockets
Types ¶
type ContextGenerator ¶
type ContextGenerator interface {
BuildContext(context.Context, *http.Request) (context.Context, error)
}
A ContextGenerator handles any changes made to the the connection context prior to creating the websocket connection routine.
type ContextGeneratorFunc ¶
The ContextGeneratorFunc takes a context and the http request it can be used to take values out of the request context and assign them to a new context that will be supplied to the websocket connection go routine and be accessible in the resolver. The http request context should not be modified as any changes made will not be accessible in the resolver.
func (ContextGeneratorFunc) BuildContext ¶
func (f ContextGeneratorFunc) BuildContext(ctx context.Context, r *http.Request) (context.Context, error)
BuildContext calls f(ctx, r) and returns a context and error
type Handler ¶
Handler is an http.Handler that supports GraphQL over WebSocket connections.
func NewHandler ¶
func NewHandler() Handler
NewHandler creates new GraphQL over websocket Handler with default websocket Upgrader.
func (*Handler) NewHandlerFunc ¶
func (h *Handler) NewHandlerFunc(svc Subscriber, httpHandler http.Handler, options ...Option) http.HandlerFunc
NewHandlerFunc returns an http.HandlerFunc that supports GraphQL over websockets
type Option ¶
type Option interface {
// contains filtered or unexported methods
}
Option applies configuration when a graphql websocket connection is handled
func WithCheckOrigin ¶
WithCheckOrigin sets a custom origin-checking function for the WebSocket upgrader. In production, restrict connections to trusted origins to prevent Cross-Site WebSocket Hijacking (CSWSH), e.g.:
WithCheckOrigin(func(r *http.Request) bool {
return r.Header.Get("Origin") == "https://example.com"
})
func WithContextGenerator ¶
func WithContextGenerator(f ContextGenerator) Option
WithContextGenerator specifies that the background context of the websocket connection go routine should be built upon by executing provided context generators.
func WithMaxSubscriptions ¶
WithMaxSubscriptions limits the number of concurrent GraphQL subscriptions allowed per WebSocket connection. The default is 100. Pass 0 to disable the limit (not recommended for public-facing servers). Negative values are treated as 0 (no limit).
func WithReadIdleTimeout ¶
WithReadIdleTimeout sets a maximum idle duration for established websocket connections after a successful connection_init handshake. When no client message is received within this duration, the server closes the socket. Pass 0 to disable idle timeout enforcement.
func WithReadLimit ¶
WithReadLimit limits the maximum size of incoming messages.
func WithWriteTimeout ¶
WithWriteTimeout sets a timeout for outgoing messages.
type Subscriber ¶
type Subscriber interface {
Subscribe(ctx context.Context, doc string, operation string, vars map[string]any) (<-chan any, error)
}
Subscriber defines the GraphQL subscription capability required by the WebSocket handler. Implementations should execute a subscription and return a channel of results.