Examples
Copy-paste starters, subscription wiring, router mounts - with the full Go source on the page.
This section shows complete applications that you can run from the repository root. Start with the basic server if you are learning grx. Then choose an example that matches your application, such as authentication, subscriptions, or a custom transport mount.
Every example keeps the same shape:
- Define a schema with
Schema(). - Create a server with
grx.NewServer. - Mount the returned handler in your HTTP service.
- Add host concerns such as authentication, CORS, logging, and deployment policy around the GraphQL runtime.
The snippets below mirror the Go source under examples/, so you can use them
as working references rather than isolated API fragments.
Runs from repo root
The commands assume your working directory is a clone of github.com/grx-gql/grx. Prefer your own module path when you copy snippets into another module (everything under examples/*/ is importable paths from this repo).
Starter servers
Basic GraphQL HTTP server
GraphiQL at GET /, POST /graphql for queries and mutations - the smallest complete loop.
examples/basic/graph/schema.go
package graph
import "github.com/grx-gql/grx/schema"
type Query struct{}
func Schema() schema.Config {
return schema.Config{
Query: Query{},
}
}examples/basic/graph/user.go
package graph
import (
"context"
)
type User struct {
ID string `gql:"id,nonNull"`
Name string `gql:"name,nonNull"`
Email *string `gql:"email"`
}
type UserArgs struct {
ID string `gql:"id,nonNull"`
}
func (Query) User(ctx context.Context, args UserArgs) (*User, error) {
email := "ada@example.com"
return &User{ID: args.ID, Name: "Ada Lovelace", Email: &email}, nil
}examples/basic/main.go
package main
import (
"log"
"net/http"
"github.com/grx-gql/grx"
"github.com/grx-gql/grx/examples/basic/graph"
)
func main() {
srv, err := grx.NewServer(
grx.WithSchema(graph.Schema()),
grx.WithPlaygroundPath("/"),
)
if err != nil {
log.Fatal(err)
}
log.Println("GraphQL playground: http://localhost:4000/")
log.Println("GraphQL endpoint: POST http://localhost:4000/graphql")
log.Fatal(http.ListenAndServe(":4000", srv))
}go run ./examples/basicTry inside GraphiQL:
{
user(id: "user_1") {
id
name
email
}
}Next concepts: Run your first server · Queries and mutations
Bearer token + field authorizer
Shows how host HTTP middleware turns Authorization: Bearer … into a request-scoped subject, and how WithFieldAuthorizer blocks viewer until that subject exists - the same narrative as Authentication & authorization. Runs on http://localhost:4010/ with playground at /.
examples/auth/session/context.go
package session
import (
"context"
)
type subjectKey struct{}
func ContextWithSubject(ctx context.Context, subject string) context.Context {
return context.WithValue(ctx, subjectKey{}, subject)
}
func Subject(ctx context.Context) (string, bool) {
s, ok := ctx.Value(subjectKey{}).(string)
if !ok || s == "" {
return "", false
}
return s, true
}examples/auth/graph/schema.go
package graph
import (
"context"
"errors"
"github.com/grx-gql/grx/examples/auth/session"
"github.com/grx-gql/grx/schema"
)
type Query struct{}
type User struct {
ID string `gql:"id,nonNull"`
DisplayName string `gql:"displayName,nonNull"`
}
func Schema() schema.Config {
return schema.Config{
Query: Query{},
}
}
func (Query) Ping() string {
return "ok"
}
func (Query) Viewer(ctx context.Context) (*User, error) {
sub, ok := session.Subject(ctx)
if !ok {
return nil, errors.New("unauthenticated")
}
return &User{ID: sub, DisplayName: "Signed-in subject " + sub}, nil
}examples/auth/main.go
package main
import (
"context"
"fmt"
"log"
"net/http"
"strings"
"github.com/grx-gql/grx"
"github.com/grx-gql/grx/examples/auth/graph"
"github.com/grx-gql/grx/examples/auth/session"
)
const demoBearerToken = "demo-alice-token"
func parseBearer(header string) (token string, ok bool) {
parts := strings.Fields(strings.TrimSpace(header))
if len(parts) != 2 || !strings.EqualFold(parts[0], "Bearer") {
return "", false
}
return parts[1], true
}
func bearerAuth(next http.Handler) http.Handler {
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
ctx := r.Context()
token, hasBearerScheme := parseBearer(r.Header.Get("Authorization"))
if hasBearerScheme {
switch token {
case demoBearerToken:
ctx = session.ContextWithSubject(ctx, "alice")
case "":
http.Error(w, `invalid Authorization header`, http.StatusBadRequest)
return
default:
http.Error(w, `invalid bearer token`, http.StatusUnauthorized)
return
}
}
next.ServeHTTP(w, r.WithContext(ctx))
})
}
func requireViewerField() grx.FieldAuthorizer {
return func(ctx context.Context, fc grx.FieldAuthorizationContext) error {
if fc.ParentType != "Query" || fc.FieldName != "viewer" {
return nil
}
if _, ok := session.Subject(ctx); !ok {
return fmt.Errorf("viewer requires Authorization: Bearer %s", demoBearerToken)
}
return nil
}
}
func main() {
srv, err := grx.NewServer(
grx.WithSchema(graph.Schema()),
grx.WithPlaygroundPath("/"),
grx.WithFieldAuthorizer(requireViewerField()),
)
if err != nil {
log.Fatal(err)
}
log.Println(`GraphQL playground: http://localhost:4010/`)
log.Println(`For { viewer { id } } add header Authorization: Bearer ` + demoBearerToken)
log.Fatal(http.ListenAndServe(":4010", bearerAuth(srv)))
}go run ./examples/authGraphiQL Headers pane:
{ "Authorization": "Bearer demo-alice-token" }Then query:
{
viewer {
id
displayName
}
}Guide: Authentication & authorization
Subscriptions (WebSocket + SSE)
Memory pub/sub feeds graphql-transport-ws - plus bundled SSE. Configure browser CORS at the host HTTP layer as shown in CORS & browsers.
examples/subscriptions/main.go
package main
import (
"log"
"log/slog"
"net/http"
"os"
"github.com/grx-gql/grx"
"github.com/grx-gql/grx/examples/subscriptions/graph"
"github.com/grx-gql/grx/plugins/logger"
"github.com/grx-gql/grx/pubsub/memory"
"github.com/grx-gql/grx/sse"
"github.com/grx-gql/grx/websocket"
)
const (
port = "4000"
playgroundPath = "/"
)
func main() {
loggerPlugin, err := logger.New(logger.Config{
Logger: slog.New(slog.NewJSONHandler(os.Stdout, &slog.HandlerOptions{Level: slog.LevelDebug})),
})
if err != nil {
log.Fatal(err)
}
bus := memory.New()
defer bus.Close()
srv, err := grx.NewServer(
grx.WithSchema(graph.Schema(bus)),
grx.WithPlugins(loggerPlugin),
grx.WithPlaygroundPath(playgroundPath),
grx.WithTransports(
websocket.New(),
sse.New(),
),
)
if err != nil {
log.Fatal(err)
}
log.Println("Server is running on port ", port)
log.Println("GraphQL Playground is available at http://localhost:" + port + srv.PlaygroundPath)
log.Println("GraphQL HTTP (queries/mutations): POST http://localhost:" + port + srv.GraphqlPath)
log.Println("Subscriptions (WebSocket/SSE): http://localhost:" + port + srv.SubscriptionPath)
log.Fatal(http.ListenAndServe(":"+port, srv))
}With graph.Schema(nil), only queries and createPost are exposed. Passing a bus with
graph.Schema(bus) also exposes createUser, postMessage, and the subscription root.
The snippets below are included from the tested example source.
examples/subscriptions/graph/schema.go
package graph
import (
"github.com/grx-gql/grx/pubsub"
"github.com/grx-gql/grx/schema"
)
// Query is the schema's root query type. Each entity contributes its own
// resolver struct (e.g. UserQuery, PostQuery) which is embedded here so its
// methods become fields on the root Query type. To add a new entity, define
// its <Entity>Query struct in its own file and embed it below.
type Query struct {
UserQuery
PostQuery
}
// Mutation is the schema's root mutation type. Embed each entity's
// <Entity>Mutation struct to expose its mutation fields. Mutations
// that publish domain events take a pubsub dependency so subscriptions
// can fan the events out to connected clients.
type Mutation struct {
*UserMutation
PostMutation
*MessageMutation
}
// Subscription is the schema's root subscription type. Each entity
// that publishes streams contributes its own <Entity>Subscription
// struct, receiving the same bus instance as the matching mutation
// so events flow end-to-end.
type Subscription struct {
UserSubscription
MessageSubscription
}
// Schema composes schema.Config. A nil bus returns the query and non-event
// mutation schema; a bus adds event mutations and subscriptions.
func Schema(bus pubsub.PubSub) schema.Config {
config := schema.Config{
Query: Query{},
Mutation: PostMutation{},
}
if bus == nil {
return config
}
config.Mutation = Mutation{
PostMutation: PostMutation{},
UserMutation: &UserMutation{Bus: bus},
MessageMutation: &MessageMutation{Bus: bus},
}
config.Subscription = Subscription{
UserSubscription: UserSubscription{Bus: bus},
MessageSubscription: MessageSubscription{Bus: bus},
}
return config
}examples/subscriptions/graph/user.go
package graph
import (
"context"
"encoding/json"
"fmt"
"sync/atomic"
"github.com/grx-gql/grx/pubsub"
)
// userCreatedTopic is the bus topic on which User events are published.
// Centralising it here avoids drift between publisher and subscriber.
const userCreatedTopic = "user.created"
type User struct {
ID string `gql:"id,nonNull"`
Name string `gql:"name,nonNull"`
Email *string `gql:"email"`
}
type UserArgs struct {
ID string `gql:"id,nonNull"`
}
type UserCreateInput struct {
Name string `gql:"name,nonNull"`
Email *string `gql:"email"`
}
type UserCreateArgs struct {
Input UserCreateInput `gql:"input,nonNull"`
}
type UserCreatePayload struct {
User *User `gql:"user,nonNull"`
}
// UserQuery groups every read-side resolver for the User entity.
type UserQuery struct{}
func (UserQuery) User(ctx context.Context, args UserArgs) (*User, error) {
email := "ada@example.com"
return &User{ID: args.ID, Name: "Ada Lovelace", Email: &email}, nil
}
func (UserQuery) ErrorExample(ctx context.Context) (string, error) {
return "", fmt.Errorf("example error from basic server")
}
// UserMutation groups every write-side resolver for the User entity.
// The Bus dependency is wired by New so the mutation can publish
// domain events that subscriptions consume.
type UserMutation struct {
Bus pubsub.PubSub
nextID atomic.Uint64
}
func (m *UserMutation) CreateUser(ctx context.Context, args UserCreateArgs) (*UserCreatePayload, error) {
id := m.nextID.Add(1)
user := &User{
ID: fmt.Sprintf("user_%d", id),
Name: args.Input.Name,
Email: args.Input.Email,
}
if m.Bus != nil {
payload, err := json.Marshal(user)
if err != nil {
return nil, err
}
if err := m.Bus.Publish(ctx, userCreatedTopic, payload); err != nil {
return nil, err
}
}
return &UserCreatePayload{User: user}, nil
}
// UserSubscription groups every stream resolver for the User entity. The
// Bus dependency is wired by New; UserCreated relays User events
// published by CreateUser to every active GraphQL subscription.
type UserSubscription struct {
Bus pubsub.PubSub
}
func (s UserSubscription) UserCreated(ctx context.Context) (<-chan *User, error) {
msgs, err := s.Bus.Subscribe(ctx, userCreatedTopic)
if err != nil {
return nil, err
}
out := make(chan *User)
go func() {
defer close(out)
for msg := range msgs {
var user User
if err := json.Unmarshal(msg.Payload, &user); err != nil {
continue
}
u := user
select {
case <-ctx.Done():
return
case out <- &u:
}
}
}()
return out, nil
}Post provides queries and non-event mutations. Message publishes JSON payloads to a separate topic for each room; its subscription uses the same topic name.
examples/subscriptions/graph/post.go
package graph
import "context"
type Post struct {
ID string `gql:"id,nonNull"`
Title string `gql:"title,nonNull"`
Body string `gql:"body,nonNull"`
Author *User `gql:"author,nonNull"`
}
type PostArgs struct {
ID string `gql:"id,nonNull"`
}
type PostCreateInput struct {
Title string `gql:"title,nonNull"`
Body string `gql:"body,nonNull"`
AuthorID string `gql:"authorId,nonNull"`
}
type PostCreateArgs struct {
Input PostCreateInput `gql:"input,nonNull"`
}
type PostCreatePayload struct {
Post *Post `gql:"post,nonNull"`
}
type PostQuery struct{}
func (PostQuery) Post(ctx context.Context, args PostArgs) (*Post, error) {
email := "ada@example.com"
return &Post{
ID: args.ID,
Title: "Hello, grx",
Body: "Composing root types from per-entity resolver structs.",
Author: &User{ID: "user_1", Name: "Ada Lovelace", Email: &email},
}, nil
}
type PostMutation struct{}
func (PostMutation) CreatePost(ctx context.Context, args PostCreateArgs) (*PostCreatePayload, error) {
post := &Post{
ID: "post_1",
Title: args.Input.Title,
Body: args.Input.Body,
Author: &User{ID: args.Input.AuthorID, Name: "Ada Lovelace"},
}
return &PostCreatePayload{Post: post}, nil
}examples/subscriptions/graph/message.go
package graph
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/grx-gql/grx/pubsub"
)
// messagePostedTopicPrefix is the bus topic prefix for chat message events.
// Each room gets its own topic so subscribers receive only that room's traffic.
const messagePostedTopicPrefix = "message.posted."
func messagePostedTopic(roomID string) string {
return messagePostedTopicPrefix + roomID
}
type Message struct {
ID string `gql:"id,nonNull"`
RoomID string `gql:"roomId,nonNull"`
Author string `gql:"author,nonNull"`
Body string `gql:"body,nonNull"`
PostedAt string `gql:"postedAt,nonNull"`
}
type PostMessageInput struct {
RoomID string `gql:"roomId,nonNull"`
Author string `gql:"author,nonNull"`
Body string `gql:"body,nonNull"`
}
type PostMessageArgs struct {
Input PostMessageInput `gql:"input,nonNull"`
}
type PostMessagePayload struct {
Message *Message `gql:"message,nonNull"`
}
type MessagePostedArgs struct {
RoomID string `gql:"roomId,nonNull"`
}
// MessageMutation owns the write side of the Message entity. It assigns
// a monotonic ID, stamps the server time, and publishes the new message
// on the room's topic so subscribers for that room receive it.
type MessageMutation struct {
Bus pubsub.PubSub
nextID atomic.Uint64
}
func (m *MessageMutation) PostMessage(ctx context.Context, args PostMessageArgs) (*PostMessagePayload, error) {
if strings.TrimSpace(args.Input.RoomID) == "" {
return nil, fmt.Errorf("roomId is required")
}
if strings.TrimSpace(args.Input.Body) == "" {
return nil, fmt.Errorf("body is required")
}
id := m.nextID.Add(1)
message := &Message{
ID: fmt.Sprintf("msg_%d", id),
RoomID: args.Input.RoomID,
Author: args.Input.Author,
Body: args.Input.Body,
PostedAt: time.Now().UTC().Format(time.RFC3339Nano),
}
if m.Bus != nil {
payload, err := json.Marshal(message)
if err != nil {
return nil, err
}
if err := m.Bus.Publish(ctx, messagePostedTopic(message.RoomID), payload); err != nil {
return nil, err
}
}
return &PostMessagePayload{Message: message}, nil
}
// MessageSubscription owns the streaming side of the Message entity.
// MessagePosted subscribes to the room-specific topic so each GraphQL
// subscription receives only messages for its requested roomId.
type MessageSubscription struct {
Bus pubsub.PubSub
}
func (s MessageSubscription) MessagePosted(ctx context.Context, args MessagePostedArgs) (<-chan *Message, error) {
if strings.TrimSpace(args.RoomID) == "" {
return nil, fmt.Errorf("roomId is required")
}
msgs, err := s.Bus.Subscribe(ctx, messagePostedTopic(args.RoomID))
if err != nil {
return nil, err
}
out := make(chan *Message)
go func() {
defer close(out)
for msg := range msgs {
var message Message
if err := json.Unmarshal(msg.Payload, &message); err != nil {
continue
}
m := message
select {
case <-ctx.Done():
return
case out <- &m:
}
}
}()
return out, nil
}go run ./examples/subscriptionsDig deeper: Realtime subscriptions
Subscriptions over SSE only
Registers sse.New() as the only subscription transport, so subscriptions are ordinary HTTP requests answered with a text/event-stream response - no WebSocket upgrade. The bundled playground detects this and subscribes over SSE too.
examples/sse/main.go
package main
import (
"log"
"net/http"
"github.com/grx-gql/grx"
"github.com/grx-gql/grx/examples/sse/graph"
"github.com/grx-gql/grx/pubsub/memory"
"github.com/grx-gql/grx/sse"
)
const port = "4000"
func main() {
bus := memory.New()
defer bus.Close()
// sse.New() is the only subscription transport, so subscriptions are
// plain HTTP requests answered with a text/event-stream response. The
// bundled playground detects this and subscribes over SSE as well.
srv, err := grx.NewServer(
grx.WithSchema(graph.Schema(bus)),
grx.WithPlaygroundPath("/"),
grx.WithTransports(sse.New()),
)
if err != nil {
log.Fatal(err)
}
log.Println("GraphQL playground: http://localhost:" + port + srv.PlaygroundPath)
log.Println("GraphQL endpoint: POST http://localhost:" + port + srv.GraphqlPath)
log.Println("Subscriptions (SSE): POST http://localhost:" + port + srv.SubscriptionPath + " with Accept: text/event-stream")
log.Fatal(http.ListenAndServe(":"+port, srv))
}countdown produces its own stream and closes it when it reaches zero, which ends the response with a complete event. notifications relays events published by sendNotification through the in-memory bus.
examples/sse/graph/schema.go
package graph
import (
"context"
"time"
"github.com/grx-gql/grx/pubsub"
"github.com/grx-gql/grx/schema"
)
// Query is the schema's root query type.
type Query struct{}
// ServerTime returns the server's current UTC time.
func (Query) ServerTime(ctx context.Context) (string, error) {
return time.Now().UTC().Format(time.RFC3339), nil
}
// Subscription is the schema's root subscription type. Countdown streams on
// its own; Notifications relays events published by SendNotification.
type Subscription struct {
CountdownSubscription
NotificationSubscription
}
// Schema composes schema.Config. The bus carries notification events from
// the mutation to every active subscription.
func Schema(bus pubsub.PubSub) schema.Config {
return schema.Config{
Query: Query{},
Mutation: &NotificationMutation{Bus: bus},
Subscription: Subscription{
NotificationSubscription: NotificationSubscription{Bus: bus},
},
}
}examples/sse/graph/countdown.go
package graph
import (
"context"
"fmt"
"time"
)
// maxCountdown bounds how long a single countdown stream can stay open.
const maxCountdown = 60
type Countdown struct {
Remaining int `gql:"remaining,nonNull"`
}
type CountdownArgs struct {
From int `gql:"from,nonNull"`
}
// CountdownSubscription needs no pub/sub: the resolver produces the stream
// itself and closes the channel when it is done, which ends the SSE response
// with a "complete" event.
type CountdownSubscription struct {
// Interval is the delay between ticks. Zero means one second.
Interval time.Duration
}
func (s CountdownSubscription) Countdown(ctx context.Context, args CountdownArgs) (<-chan *Countdown, error) {
if args.From < 0 || args.From > maxCountdown {
return nil, fmt.Errorf("from must be between 0 and %d", maxCountdown)
}
interval := s.Interval
if interval <= 0 {
interval = time.Second
}
out := make(chan *Countdown)
go func() {
defer close(out)
ticker := time.NewTicker(interval)
defer ticker.Stop()
for remaining := args.From; remaining >= 0; remaining-- {
select {
case <-ctx.Done():
return
case out <- &Countdown{Remaining: remaining}:
}
if remaining == 0 {
return
}
select {
case <-ctx.Done():
return
case <-ticker.C:
}
}
}()
return out, nil
}examples/sse/graph/notification.go
package graph
import (
"context"
"encoding/json"
"fmt"
"strings"
"sync/atomic"
"time"
"github.com/grx-gql/grx/pubsub"
)
// notificationTopicPrefix is the bus topic prefix for notification events.
// Each channel gets its own topic so subscribers only receive its traffic.
const notificationTopicPrefix = "notification.sent."
func notificationTopic(channel string) string {
return notificationTopicPrefix + channel
}
type Notification struct {
ID string `gql:"id,nonNull"`
Channel string `gql:"channel,nonNull"`
Text string `gql:"text,nonNull"`
SentAt string `gql:"sentAt,nonNull"`
}
type SendNotificationInput struct {
Channel string `gql:"channel,nonNull"`
Text string `gql:"text,nonNull"`
}
type SendNotificationArgs struct {
Input SendNotificationInput `gql:"input,nonNull"`
}
type NotificationsArgs struct {
Channel string `gql:"channel,nonNull"`
}
// NotificationMutation publishes each notification on its channel's topic.
type NotificationMutation struct {
Bus pubsub.PubSub
nextID atomic.Uint64
}
func (m *NotificationMutation) SendNotification(ctx context.Context, args SendNotificationArgs) (*Notification, error) {
channel := strings.TrimSpace(args.Input.Channel)
if channel == "" {
return nil, fmt.Errorf("channel is required")
}
if strings.TrimSpace(args.Input.Text) == "" {
return nil, fmt.Errorf("text is required")
}
notification := &Notification{
ID: fmt.Sprintf("notification_%d", m.nextID.Add(1)),
Channel: channel,
Text: args.Input.Text,
SentAt: time.Now().UTC().Format(time.RFC3339Nano),
}
payload, err := json.Marshal(notification)
if err != nil {
return nil, err
}
if err := m.Bus.Publish(ctx, notificationTopic(channel), payload); err != nil {
return nil, err
}
return notification, nil
}
// NotificationSubscription relays the notifications published on a channel
// to every client subscribed to it.
type NotificationSubscription struct {
Bus pubsub.PubSub
}
func (s NotificationSubscription) Notifications(ctx context.Context, args NotificationsArgs) (<-chan *Notification, error) {
channel := strings.TrimSpace(args.Channel)
if channel == "" {
return nil, fmt.Errorf("channel is required")
}
msgs, err := s.Bus.Subscribe(ctx, notificationTopic(channel))
if err != nil {
return nil, err
}
out := make(chan *Notification)
go func() {
defer close(out)
for msg := range msgs {
var notification Notification
if err := json.Unmarshal(msg.Payload, ¬ification); err != nil {
continue
}
select {
case <-ctx.Done():
return
case out <- ¬ification:
}
}
}()
return out, nil
}go run ./examples/sseSubscribe from a terminal:
curl -N http://localhost:4000/graphql \
-H 'Content-Type: application/json' \
-H 'Accept: text/event-stream' \
-d '{"query":"subscription { countdown(from: 3) { remaining } }"}'Dig deeper: Realtime subscriptions
Router integrations
grx answers ServeHTTP with exact path matches - you usually pair http.StripPrefix("/api", gql) with WithPlaygroundPath("/playground") and WithGraphQLPath("/query") so callers see /api/playground and POST /api/query.
Build graph any way you like (see **Get started** or Basic HTTP above), then reuse this fragment before each snippet:
gql, err := grx.NewServer(
grx.WithSchema(graph.Schema()),
grx.WithPlaygroundPath("/playground"),
grx.WithGraphQLPath("/query"),
)
if err != nil {
log.Fatal(err)
}
delegated := http.StripPrefix("/api", gql)Long-form guides: net/http ServeMux · Chi · Gin · Echo · Fiber
The Gin, Echo, and Fiber excerpts below reuse delegated from the fragment above. Chi repeats the full grx.NewServer block for copy-paste convenience.
net/http ServeMux
mux := http.NewServeMux()
mux.Handle("/health", http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("ok"))
}))
mux.Handle("/api/", delegated)Chi
go get github.com/go-chi/chi/v5package main
import (
"log"
"net/http"
"example.com/hello-grx/graph"
"github.com/go-chi/chi/v5"
"github.com/grx-gql/grx"
)
func main() {
gql, err := grx.NewServer(
grx.WithSchema(graph.Schema()),
grx.WithPlaygroundPath("/playground"),
grx.WithGraphQLPath("/query"),
)
if err != nil {
log.Fatal(err)
}
r := chi.NewRouter()
r.Get("/health", func(w http.ResponseWriter, _ *http.Request) { w.Write([]byte("ok")) })
r.Mount("/api", http.StripPrefix("/api", gql))
log.Fatal(http.ListenAndServe(":8080", r))
}Gin
go get github.com/gin-gonic/gin engine := gin.Default()
engine.GET("/health", func(c *gin.Context) { c.String(200, "ok") })
engine.Any("/api/*proxyPath", gin.WrapH(delegated))
log.Fatal(engine.Run(":8080"))Echo
go get github.com/labstack/echo/v4 e := echo.New()
e.GET("/health", func(c echo.Context) error { return c.String(200, "ok") })
e.Any("/api/*", echo.WrapHandler(delegated))
log.Fatal(e.Start(":8080"))Fiber
go get github.com/gofiber/fiber/v2 app := fiber.New()
app.Get("/health", func(c *fiber.Ctx) error { return c.SendString("ok") })
app.All("/api/*", adaptor.HTTPHandler(delegated))
log.Fatal(app.Listen(":8080"))(import github.com/gofiber/fiber/v2 and github.com/gofiber/fiber/v2/middleware/adaptor)
Measuring throughput
Micro-benchmarks help compare resolver hot paths - they are not a substitute for profiling your full HTTP/RPS workload.
See Benchmarks for scenarios, methodology, and latest tables.
Source
- GitHub repository - history, CI,
examples/tree mirroring these snippets. - Discussions - integrations and project questions.
Doc cross-links
Last updated on