rocket

package module
v0.2.0 Latest Latest
Warning

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

Go to latest
Published: Nov 3, 2025 License: MIT Imports: 22 Imported by: 0

README

🚀 Rocket

Developer-friendly GraphQL for Go - Bringing a modern, DX-focused approach to GraphQL development in Golang.

Philosophy

Rocket aims to bring a more developer-friendly approach to GraphQL in Go:

  • Schema-First: Define your API in .graphql files
  • Modular Resolvers: Each module owns its resolvers
  • Declarative: Map-based resolvers, no switch statements
  • Auto-Resolution: Struct fields auto-resolve, override only when needed
  • Field Order: Preserves query field selection order in responses
  • Type-Safe: Leverage Go's type system with minimal boilerplate

Features

  • TypeScript-like resolver pattern - Map-based resolvers you can spread/merge
  • 🎯 Auto-field resolution - Define custom resolvers only when you need them
  • 📦 Modular architecture - Each module is self-contained
  • 🔄 Schema compilation - Concat .graphql files with smart ordering
  • Hot reload support - Auto-recompile schemas on change
  • 🎨 Field order preservation - Responses match query field order
  • 🏗️ Built on Wundergraph - Production-grade GraphQL tools
  • 🎮 Apollo Sandbox - Modern playground with best-in-class DX

Getting Started

Prerequisites
  • Go 1.25+ - Make sure you have Go installed (download here)
  • Basic understanding of GraphQL (helpful but not required)
Installation

Add Rocket to your Go project:

go get github.com/jest-cloud/rocket@latest

Or specify a version:

go get github.com/jest-cloud/rocket@v0.1.0
Create a Simple GraphQL API

Follow these steps to create your first Rocket-powered GraphQL API:

Step 1: Create Your Project
mkdir my-graphql-api
cd my-graphql-api
go mod init my-graphql-api
go get github.com/jest-cloud/rocket
Step 2: Define Your Schema

Create a schema.graphql file:

type Query {
  hello: String!
  users: [User!]!
}

type User {
  id: ID!
  name: String!
  email: String!
}
Step 3: Create Your Resolvers

Create resolvers.go:

package main

import (
    "github.com/jest-cloud/rocket"
)

type Resolvers struct{}

// User represents a user in our system
type User struct {
    ID    string `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

func (r *Resolvers) QueryResolvers() map[string]rocket.FieldResolveFn {
    return map[string]rocket.FieldResolveFn{
        "hello": func(p rocket.ResolveParams) (interface{}, error) {
            return "Hello, Rocket! 🚀", nil
        },
        "users": func(p rocket.ResolveParams) (interface{}, error) {
            // Return structs - Rocket will auto-resolve fields!
            return []*User{
                {ID: "1", Name: "Alice", Email: "alice@example.com"},
                {ID: "2", Name: "Bob", Email: "bob@example.com"},
            }, nil
        },
    }
}

func (r *Resolvers) MutationResolvers() map[string]rocket.FieldResolveFn {
    return map[string]rocket.FieldResolveFn{}
}

func (r *Resolvers) TypeResolvers() map[string]map[string]rocket.FieldResolveFn {
    // For list types, you may need explicit type resolvers
    // Auto-resolution works best for single objects, not slices
    return map[string]map[string]rocket.FieldResolveFn{
        "User": {
            "id": func(p rocket.ResolveParams) (interface{}, error) {
                switch v := p.Source.(type) {
                case *User:
                    return v.ID, nil
                case User:
                    return v.ID, nil
                default:
                    return nil, nil
                }
            },
            "name": func(p rocket.ResolveParams) (interface{}, error) {
                switch v := p.Source.(type) {
                case *User:
                    return v.Name, nil
                case User:
                    return v.Name, nil
                default:
                    return nil, nil
                }
            },
            "email": func(p rocket.ResolveParams) (interface{}, error) {
                switch v := p.Source.(type) {
                case *User:
                    return v.Email, nil
                case User:
                    return v.Email, nil
                default:
                    return nil, nil
                }
            },
        },
    }
}

Note: Rocket's auto-field resolution works with structs, not maps. Use structs with json tags to match your GraphQL schema field names.

Important: When returning slices/lists, you may need to add explicit type resolvers for the fields. This is a known limitation being investigated. See the working example below with explicit resolvers.

Step 4: Build and Run

Create main.go:

package main

import (
    "log"
    "net/http"
    "github.com/jest-cloud/rocket"
)

func main() {
    resolvers := &Resolvers{}
    
    // Build GraphQL schema
    schema, err := rocket.BuildSchema(
        rocket.Config{
            SchemaPath: "schema.graphql",
        },
        resolvers,
    )
    if err != nil {
        log.Fatal(err)
    }
    
    // Create HTTP handlers
    http.Handle("/graphql", rocket.Handler(schema))
    http.HandleFunc("/playground", rocket.PlaygroundHandler("/graphql"))
    
    log.Println("🚀 Server starting on http://localhost:8080")
    log.Println("🚀 GraphQL endpoint: http://localhost:8080/graphql")
    log.Println("🚀 Playground: http://localhost:8080/playground")
    
    log.Fatal(http.ListenAndServe(":8080", nil))
}
Step 5: Test Your API

Run your server:

go run main.go

Then:

  1. Visit the Playground: Open http://localhost:8080/playground in your browser
  2. Try a Query:
    query {
      hello
      users {
        id
        name
        email
      }
    }
    

That's it! You now have a working GraphQL API powered by Rocket. 🎉

Next Steps

Quick Start

1. Define Your Schema
# src/user/schema.graphql
type User {
  id: ID!
  email: String!
  firstName: String!
  lastName: String!
}

extend type Query {
  user(id: ID!): User
  users(limit: Int): [User!]!
}
2. Create Module Resolvers
// src/user/resolvers.go
package user

import "github.com/jest-cloud/rocket"

type Resolvers struct {
    service *Service
}

func (r *Resolvers) QueryResolvers() map[string]rocket.FieldResolveFn {
    return map[string]rocket.FieldResolveFn{
        "user": func(p rocket.ResolveParams) (interface{}, error) {
            id := p.Args["id"].(string)
            return r.service.GetUserByID(p.Context, id)
        },
        "users": func(p rocket.ResolveParams) (interface{}, error) {
            limit := p.Args["limit"].(int)
            return r.service.GetAllUsers(p.Context, limit)
        },
    }
}

func (r *Resolvers) MutationResolvers() map[string]rocket.FieldResolveFn {
    return map[string]rocket.FieldResolveFn{}
}

func (r *Resolvers) TypeResolvers() map[string]map[string]rocket.FieldResolveFn {
    return map[string]map[string]rocket.FieldResolveFn{
        "User": {
            // Fields auto-resolve from User struct!
            // Only override when you need custom logic:
            "lastName": func(p rocket.ResolveParams) (interface{}, error) {
                user := p.Source.(*User)
                return strings.ToUpper(user.LastName), nil
            },
        },
    }
}
3. Build Schema
// main.go
import "github.com/jest-cloud/rocket"

func main() {
    // Initialize modules
    userModule := user.Initialize(db)
    orgModule := org.Initialize(db)
    
    // Build schema with all resolvers
    schema, err := rocket.BuildSchema(
        rocket.Config{
            SchemaPath: "schema/schema.graphql",
        },
        userModule.Resolvers,
        orgModule.Resolvers,
    )
    
    // Create HTTP handlers
    http.Handle("/graphql", rocket.Handler(schema))
    
    // Optional: Add playground
    http.HandleFunc("/playground", rocket.PlaygroundHandler("/graphql"))
    
    http.ListenAndServe(":8080", nil)
}

Comparison with TypeScript GraphQL

TypeScript GraphQL
const resolvers = {
  Query: {
    user: (parent, args, context) => {
      return userService.getUser(args.id);
    },
  },
  User: {
    // Fields auto-resolve!
    lastName: (parent) => parent.lastName.toUpperCase(),
  },
};
Rocket (Go)
func (r *Resolvers) QueryResolvers() map[string]rocket.FieldResolveFn {
    return map[string]rocket.FieldResolveFn{
        "user": func(p rocket.ResolveParams) (interface{}, error) {
            return r.service.GetUser(p.Args["id"].(string))
        },
    }
}

func (r *Resolvers) TypeResolvers() map[string]map[string]rocket.FieldResolveFn {
    return map[string]map[string]rocket.FieldResolveFn{
        "User": {
            // Fields auto-resolve!
            "lastName": func(p rocket.ResolveParams) (interface{}, error) {
                user := p.Source.(*User)
                return strings.ToUpper(user.LastName), nil
            },
        },
    }
}

Same pattern, same philosophy! 🎯

Architecture

Rocket Package
├── Schema Compiler      - Concatenates .graphql files
├── Schema Builder       - Parses and builds executable schema
├── Resolver Registry    - Stitches module resolvers together
├── Execution Engine     - Executes queries with field order
├── HTTP Handler         - Gin/net/http integration
└── Default Resolvers    - Auto-resolution for struct fields

Why Rocket?

  • 🚀 Fast to develop - Write less code, get more done
  • 🎯 Developer-friendly - Intuitive patterns that reduce boilerplate
  • 💪 Production-ready - Built on Wundergraph's battle-tested tools
  • 🔧 Flexible - Override anything when you need to
  • 📦 Modular - Clean separation of concerns

Introspection Support

Rocket fully supports GraphQL introspection queries out of the box:

  • __schema - Get the schema structure
  • __type - Get information about a specific type
  • __typename - Get the type name of an object

The GraphQL Playground automatically uses introspection to provide autocomplete and documentation.

Documentation

TODO

Testing & Quality
  • Add comprehensive unit tests for core functionality
  • Add integration tests for schema building and execution
  • Add benchmark tests for performance monitoring
  • Add test coverage reporting
  • Set up test examples/demos in separate examples directory
  • Add fuzzing for schema parsing and execution
Error Handling & Validation
  • Improve error messages with better context
  • Add validation for resolver function signatures
  • Add schema validation warnings
  • Better error handling for malformed queries
  • Add panic recovery middleware for resolvers
Features & Functionality
  • Subscriptions support (WebSocket)
  • DataLoader implementation for N+1 query prevention
  • Custom scalar types (Date, DateTime, JSON, etc.)
  • Field-level middleware/directives
  • GraphQL Federation support
  • Query complexity analysis
  • Query depth limiting
  • Rate limiting support
  • Caching layer for queries
  • Batch request support
Developer Experience
  • GoDoc comments for all public APIs
  • Code generation tool for resolver boilerplate
  • Schema validation CLI tool
  • Better type safety for ResolveParams Args
  • Middleware support for resolvers
  • Context propagation utilities
  • Logging integration hooks
  • Metrics/observability hooks
Performance
  • Optimize field order preservation
  • Add query result caching
  • Parallel resolver execution where safe
  • Reduce allocations in hot paths
  • Profile and optimize schema building
Documentation
  • Add Go examples to godoc
  • Create interactive tutorials
  • Add migration guide from other GraphQL libraries
  • Add troubleshooting guide
  • API reference documentation
  • Best practices guide
Infrastructure
  • Add Go 1.26+ support
  • Test compatibility across Go versions
  • Add GitHub Actions for automated releases
  • Set up automated dependency updates (Dependabot)
  • Add security scanning (CodeQL)

Coming Soon

These are planned for future releases:

  • Subscriptions support
  • DataLoader for N+1 prevention
  • Custom scalar types
  • Field-level middleware/directives
  • GraphQL Federation
  • Query complexity analysis

Contributing

We welcome contributions from anyone who would like to get involved! Rocket is an open-source project and we'd love your help making it better.

How to Contribute
  • Report bugs - Open an issue if you find a bug
  • Suggest features - Share your ideas for improvements
  • Submit pull requests - Pick any item from the TODO list above, or fix bugs
  • Improve documentation - Help make our docs clearer and more comprehensive
  • Write tests - Increase test coverage and improve reliability
Getting Started
  1. Fork the repository
  2. Create a branch for your contribution (git checkout -b feature/amazing-feature)
  3. Make your changes
  4. Add tests if applicable
  5. Ensure all tests pass (go test ./...)
  6. Commit your changes (git commit -m 'Add amazing feature')
  7. Push to your branch (git push origin feature/amazing-feature)
  8. Open a Pull Request

Check out the TODO section above for ideas on what to work on, or feel free to contribute in any way you'd like!

License

MIT


Rocket: A developer-friendly approach to GraphQL in Go 🚀

Documentation

Overview

Package rocket provides developer-friendly GraphQL patterns for Go Bringing a modern, DX-focused approach to GraphQL development in Golang

Index

Constants

View Source
const (
	DefaultPreserveOrder = true
)

Config defaults

Variables

This section is empty.

Functions

func DefaultFieldResolver

func DefaultFieldResolver(p ResolveParams) (interface{}, error)

DefaultFieldResolver automatically resolves struct fields to GraphQL fields It uses reflection to map GraphQL field names to Go struct fields This provides a developer-friendly auto-resolution feature

func Handler

func Handler(schema *Schema) http.HandlerFunc

Handler creates an HTTP handler for GraphQL requests Works with both net/http and Gin (via gin.WrapH)

func PlaygroundHandler

func PlaygroundHandler(endpoint string) http.HandlerFunc

PlaygroundHandler creates an HTTP handler that serves a GraphQL playground Uses Apollo Sandbox by default (most modern and stable)

func PlaygroundHandlerWithType

func PlaygroundHandlerWithType(endpoint string, playgroundType PlaygroundType) http.HandlerFunc

PlaygroundHandlerWithType creates a playground handler with a specific type

Types

type Config

type Config struct {
	SchemaPath       string // Path to the compiled schema.graphql file
	EnablePlayground bool   // Enable GraphQL playground (default: false)
}

Config holds configuration for building a GraphQL schema

type Error

type Error struct {
	Message string        `json:"message"`
	Path    []interface{} `json:"path,omitempty"`
}

Error represents a GraphQL error

type FieldResolveFn

type FieldResolveFn func(p ResolveParams) (interface{}, error)

FieldResolveFn is a function that resolves a field value

type ModuleResolvers

type ModuleResolvers interface {
	QueryResolvers() map[string]FieldResolveFn
	MutationResolvers() map[string]FieldResolveFn
	TypeResolvers() map[string]map[string]FieldResolveFn
}

ModuleResolvers is the interface that all module resolvers must implement This provides a clean, modular approach to organizing resolvers

type PlaygroundType

type PlaygroundType string

PlaygroundType determines which playground interface to use

const (
	PlaygroundTypeApolloSandbox PlaygroundType = "apollo"     // Apollo Sandbox (recommended)
	PlaygroundTypeGraphiQL      PlaygroundType = "graphiql"   // GraphiQL (stable)
	PlaygroundTypePlayground    PlaygroundType = "playground" // GraphQL Playground (legacy)
)

type Request

type Request struct {
	Query         string                 `json:"query"`
	Variables     map[string]interface{} `json:"variables"`
	OperationName string                 `json:"operationName"`
}

Request represents a GraphQL HTTP request

type ResolveInfo

type ResolveInfo struct {
	FieldName    string
	Path         []string
	ParentType   string
	ReturnType   string
	SelectionSet interface{} // Will be wundergraph AST selection set
}

ResolveInfo contains metadata about the field being resolved

type ResolveParams

type ResolveParams struct {
	Source  interface{}
	Args    map[string]interface{}
	Context context.Context
	Info    ResolveInfo
}

ResolveParams contains all the information for resolving a field

type ResolverRegistry

type ResolverRegistry struct {
	Query    map[string]FieldResolveFn
	Mutation map[string]FieldResolveFn
	Types    map[string]map[string]FieldResolveFn
}

ResolverRegistry holds all resolvers stitched together Combines resolvers from different modules in a clean, modular way

func NewResolverRegistry

func NewResolverRegistry(modules ...ModuleResolvers) *ResolverRegistry

NewResolverRegistry creates a new registry by merging module resolvers This is like:

export const resolvers = {
  Query: { ...userQueries, ...orgQueries },
  Mutation: { ...userMutations, ...orgMutations }
}

func (*ResolverRegistry) GetMutationResolver

func (r *ResolverRegistry) GetMutationResolver(name string) (FieldResolveFn, bool)

GetMutationResolver returns a mutation resolver by name

func (*ResolverRegistry) GetQueryResolver

func (r *ResolverRegistry) GetQueryResolver(name string) (FieldResolveFn, bool)

GetQueryResolver returns a query resolver by name

func (*ResolverRegistry) GetTypeResolver

func (r *ResolverRegistry) GetTypeResolver(typeName, fieldName string) (FieldResolveFn, bool)

GetTypeResolver returns a type's field resolver

type Result

type Result struct {
	Data   interface{} `json:"data,omitempty"`
	Errors []Error     `json:"errors,omitempty"`
}

Result represents the result of a GraphQL operation

func ExecutePlan added in v0.2.0

func ExecutePlan(ctx context.Context, execPlan plan.Plan, variables map[string]interface{}, resolvers *ResolverRegistry) *Result

ExecutePlan executes a GraphQL execution plan using graphql-go-tools resolver

type RocketDataSource added in v0.2.0

type RocketDataSource struct {
	// contains filtered or unexported fields
}

RocketDataSource bridges Rocket's resolver pattern to graphql-go-tools DataSource This allows Rocket to work with graphql-go-tools while maintaining its developer-friendly API

type RocketDataSourceFactory added in v0.2.0

type RocketDataSourceFactory struct {
	// contains filtered or unexported fields
}

RocketDataSourceFactory creates RocketDataSource instances

func NewRocketDataSourceFactory added in v0.2.0

func NewRocketDataSourceFactory(resolvers *ResolverRegistry, schema *ast.Document) *RocketDataSourceFactory

NewRocketDataSourceFactory creates a new factory for Rocket DataSources

func (*RocketDataSourceFactory) Context added in v0.2.0

func (*RocketDataSourceFactory) Planner added in v0.2.0

func (*RocketDataSourceFactory) PlanningBehavior added in v0.2.0

func (*RocketDataSourceFactory) ToDataSourceConfiguration added in v0.2.0

func (f *RocketDataSourceFactory) ToDataSourceConfiguration(id, name string) (plan.DataSourceConfiguration[interface{}], error)

ToDataSourceConfiguration converts the factory to a DataSourceConfiguration This allows it to be used in plan.Configuration.DataSources

func (*RocketDataSourceFactory) UpstreamSchema added in v0.2.0

func (f *RocketDataSourceFactory) UpstreamSchema(config plan.DataSourceConfiguration[interface{}]) (*ast.Document, bool)

type RocketPlanner added in v0.2.0

type RocketPlanner struct {
	// contains filtered or unexported fields
}

RocketPlanner implements plan.DataSourcePlanner for Rocket resolvers It configures how fields are resolved using Rocket's resolver pattern

func (*RocketPlanner) ConfigureFetch added in v0.2.0

func (p *RocketPlanner) ConfigureFetch() resolve.FetchConfiguration

func (*RocketPlanner) ConfigureSubscription added in v0.2.0

func (p *RocketPlanner) ConfigureSubscription() plan.SubscriptionConfiguration

func (*RocketPlanner) DataSourcePlanningBehavior added in v0.2.0

func (p *RocketPlanner) DataSourcePlanningBehavior() plan.DataSourcePlanningBehavior

func (*RocketPlanner) DownstreamResponseFieldAlias added in v0.2.0

func (p *RocketPlanner) DownstreamResponseFieldAlias(downstreamFieldRef int) (alias string, exists bool)

func (*RocketPlanner) ID added in v0.2.0

func (p *RocketPlanner) ID() int

func (*RocketPlanner) ObjectFetchConfiguration added in v0.2.0

func (p *RocketPlanner) ObjectFetchConfiguration() *objectFetchConfiguration

ObjectFetchConfiguration is called during planning to get field-specific configuration This method returns a pointer to the same object, so graphql-go-tools can populate rootFields

func (*RocketPlanner) Register added in v0.2.0

func (p *RocketPlanner) Register(visitor *plan.Visitor, configuration plan.DataSourceConfiguration[interface{}], _ plan.DataSourcePlannerConfiguration) error

func (*RocketPlanner) SetID added in v0.2.0

func (p *RocketPlanner) SetID(id int)

type RocketSource added in v0.2.0

type RocketSource struct {
	// contains filtered or unexported fields
}

RocketSource implements resolve.DataSource for Rocket resolvers This is where we actually execute Rocket's resolver functions

func (*RocketSource) Load added in v0.2.0

func (s *RocketSource) Load(ctx context.Context, input []byte, out *bytes.Buffer) error

Load executes Rocket resolvers and returns the result

func (*RocketSource) LoadWithFiles added in v0.2.0

func (s *RocketSource) LoadWithFiles(ctx context.Context, input []byte, files []*httpclient.FileUpload, out *bytes.Buffer) error

type Schema

type Schema struct {
	// contains filtered or unexported fields
}

Schema represents a compiled and executable GraphQL schema

func BuildSchema

func BuildSchema(config Config, modules ...ModuleResolvers) (*Schema, error)

BuildSchema builds an executable GraphQL schema from .graphql file and module resolvers This is the main entry point for Rocket

func (*Schema) Execute

func (s *Schema) Execute(ctx context.Context, query string, variables map[string]interface{}, operationName string) *Result

Execute executes a GraphQL query/mutation Field order is always preserved for better developer experience

type SchemaCompiler

type SchemaCompiler struct {
	// contains filtered or unexported fields
}

SchemaCompiler compiles multiple .graphql files into a single schema

func NewSchemaCompiler

func NewSchemaCompiler(sourceDir, outputFile string) *SchemaCompiler

NewSchemaCompiler creates a new schema compiler

func (*SchemaCompiler) Compile

func (c *SchemaCompiler) Compile() error

Compile finds all .graphql files and concatenates them into a single schema

func (*SchemaCompiler) CompileIfNeeded

func (c *SchemaCompiler) CompileIfNeeded() (bool, error)

CompileIfNeeded compiles the schema only if source files are newer than output

Jump to

Keyboard shortcuts

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