Help us shape the future of Go! Take the 2026 Go Developer Survey

graphqlws

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 29, 2026 License: BSD-2-Clause Imports: 10 Imported by: 0

README

graphql-transport-ws

WebSocket transport for GraphQL servers in Go.

The supported protocol is graphql-transport-ws as defined in the specification: GraphQL over WebSocket Protocol.

With graphql-go

Wrap your HTTP GraphQL handler with graphqlws.NewHandlerFunc:

schema := graphql.MustParseSchema(schemaSDL, &rootResolver{})
rh := &relay.Handler{Schema: schema}

http.Handle("/graphql", graphqlws.NewHandlerFunc(schema, rh))

Non-WebSocket requests are passed through to the wrapped HTTP handler, so the same endpoint can serve regular GraphQL HTTP traffic and WebSocket subscriptions.

See example_test.go for a runnable server using github.com/graph-gophers/graphql-go.

Client notes

Connect to /graphql with WebSocket subprotocol graphql-transport-ws.

Example subscription:

subscription {
  ticks {
    at
    count
  }
}

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

View Source
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

type ContextGeneratorFunc func(context.Context, *http.Request) (context.Context, error)

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

type Handler struct {
	Upgrader websocket.Upgrader
}

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

func WithCheckOrigin(fn func(*http.Request) bool) Option

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

func WithMaxSubscriptions(n int) Option

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

func WithReadIdleTimeout(d time.Duration) Option

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

func WithReadLimit(limit int64) Option

WithReadLimit limits the maximum size of incoming messages.

func WithWriteTimeout

func WithWriteTimeout(d time.Duration) Option

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.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL