groot

package module
v0.1.6 Latest Latest
Warning

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

Go to latest
Published: Mar 9, 2022 License: MIT Imports: 7 Imported by: 0

README

Groot

An easier way to create GraphQL APIs in Go. View the full documentation here.

Groot is built on top of github.com/graphql-go/graphql, which means it should support most existing tooling built for it.

Motivation

Go already has a couple of implementation of GraphQL, so why another one?

Type Safety

Go is statically typed, and GraphQL is type safe, which means we don't need to and shouldn't use interface{} anywhere. A simple user struct with custom resolvers would look something like this. Although most type checking is done by Go, additional checks like resolver return types are done by Groot on startup to avoid type errors altogether.

type User struct {
	ID   int    `json:"id"`
	Name string `json:"name"`
}
Code First

Although the schema first aproach has its advantages, code first is arguably easier to maintain in the long run without having to deal with federated schemas and schema stitching. For more info check out this blog post.

When you work with Groot, in a way you're defining your schema first as well since you're defining the structure of your data (struct, interfaces, enums, etc) first.

No Boilerplate and Code Duplication

The only thing we want to worry about is our types, resolvers, and business logic, nothing more. We also don't want to redeclare our types in Go as well as GraphQL, it can get cumbersome to maintain and keep track of.

Simple To Use

Seriously, it is.

Getting Started

Let's see how we can create the below GraphQL schema.

type User {
  id: ID!
  name: String!
  email: String!
  posts: [Post!]
}

type Post {
  id: ID!
  title: String!
  body: String!
  author: User!
  timestamp: Int!
}

type Query {
  user(id: ID!): User
  post(id: ID!): Post
}

type Mutation {
  createUser(name: String!, email: String!, password: String!): User
  createPost(title: String!, body: String!): Post
}

We can define User and Post objects as regular structs.

type User struct {
	ID    string  `json:"id"`
	Name  string  `json:"name"`
	Email string  `json:"email"`
	// we use a pointer to make the field nullable
	Posts *[]Post `json:"posts"`
}

type Post struct {
	ID        string `json:"id"`
	Title     string `json:"title"`
	Body      string `json:"body"`
	Author    User   `json:"author"`
	Timestamp int64  `json:"timestamp"`
}

Yes, that's it!

Custom Resolvers

The library will automatically generate the resolvers for all the fields. But, you can create custom resolvers for any field by defining a method with the name Resolve{field-name}. For example, if we want to define a custom resolver for the Posts field on User we can write the below method:

func (user User) ResolvePosts() (*[]Post, error) {
	posts, err := db.GetPostsByUserID(user.ID)
	if err != nil {
		return nil, err
	}
	return posts, nil
}

If the return type of the resolver is not the same as the return type of the field, Groot will panic on startup. For more details on resolvers and arguments check the Field Resolvers section.

Queries

The Query type is just another type with a special name, which means we can define it just like we defined the User and Post types with custom resolvers.

type Query struct {
	// we use pointers to make the field nullable
	User *User `json:"user"`
	Post *Post `json:"post"`
}

type IDArgs struct {
	ID string `json:"id"`
}

func (q Query) ResolveUser(args IDArgs) (*User, error) {
	user, err := db.GetUserByID(args.ID)
	if err != nil {
		return nil, err
	}
	return user, nil
}

func (p Query) ResolvePost(args IDArgs) (*Post, error) {
	post, err := db.GetPostByID(args.ID)
	if err != nil {
		return nil, err
	}
	return post, nil
}

Notice how we were able to accept arguments by having the type of first argument of the resolver as a struct.

For a larger schema, you may think we would need to define a lot of fields and methods on the single Query type. While you would be right, we can avoid that by just embedding structs. You can find more info on embedding and composition in the Composition section.

Mutations

Similar to the Query struct we can define a Mutation struct to define our mutations.

type Mutation struct {
	CreateUser *User `json:"createUser"`
	CreatePost *Post `json:"createPost"`
}

type NewUserArgs struct {
	Name     string `json:"name"`
	Email    string `json:"email"`
	Password string `json:"password"`
}

type NewPostArgs struct {
	Title string `json:"title"`
	Body  string `json:"body"`
}

func (m Mutation) ResolveCreateUser(args NewUserArgs) (*User, error) {
	user, err := db.CreateUser(args.Name, args.Email, args.Password)
	if err != nil {
		return nil, err
	}
	return user, nil
}

func (m Mutation) ResolveCreatePost(args NewPostArgs) (*Post, error) {
	post, err := db.CreatePost(args.Title, args.Body)
	if err != nil {
		return nil, err
	}
	return post, nil
}
Creating Schema

Finally, to create the schema, we can use the NewSchema function. We can also use the github.com/graphql-go/handler library to create a handler for the schema since groot.NewSchema returns a schema of type graphql.Schema where the graphql package refers to the github.com/graphql-go/graphql library.

import (
	"reflect"
	"net/http"
	"github.com/shreyas44/groot"
	"github.com/graphql-go/handler"
)

func main() {
	schema := groot.NewSchema(groot.SchemaConfig{
		Query:    groot.MustParseObject(Query{}),
		Mutation: groot.MustParseObject(Mutation{}),
	})

	h := handler.New(&handler.Config{
		Schema: &schema,
		Pretty: true,
		Playground: true,
	})

	http.Handle("/graphql", h)
	log.Fatal(http.ListenAndServe(":8080", nil)
}

Features Not Supported but Coming Soon:

Note, the library isn't tested yet.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func GetNullable

func GetNullable(t graphql.Type) graphql.Nullable

func MustParseEnum added in v0.1.0

func MustParseEnum(i interface{}) *parser.Enum

func MustParseInputObject added in v0.1.0

func MustParseInputObject(i interface{}) *parser.Input

func MustParseInterface added in v0.1.0

func MustParseInterface(i interface{}) *parser.Interface

func MustParseObject added in v0.1.0

func MustParseObject(i interface{}) *parser.Object

func MustParseScalar added in v0.1.0

func MustParseScalar(i interface{}) *parser.Scalar

func MustParseUnion added in v0.1.0

func MustParseUnion(i interface{}) *parser.Union

func NewArgument

func NewArgument(parserArg *parser.Argument, builder *SchemaBuilder) *graphql.ArgumentConfig

func NewArray

func NewArray(t graphql.Type) graphql.Type

func NewEnum

func NewEnum(t *parser.Enum, builder *SchemaBuilder) *graphql.Enum

func NewField

func NewField(parserField *parser.Field, builder *SchemaBuilder) *graphql.Field

func NewInputObject

func NewInputObject(input *parser.Input, builder *SchemaBuilder) *graphql.InputObject

func NewInterface

func NewInterface(parserInterface *parser.Interface, builder *SchemaBuilder) *graphql.Interface

func NewNonNull

func NewNonNull(t graphql.Type) *graphql.NonNull

func NewObject

func NewObject(parserObject *parser.Object, builder *SchemaBuilder) *graphql.Object

func NewScalar

func NewScalar(parserScalar *parser.Scalar, builder *SchemaBuilder) *graphql.Scalar

func NewSchema

func NewSchema(config SchemaConfig) (graphql.Schema, error)

func NewUnion

func NewUnion(parserUnion *parser.Union, builder *SchemaBuilder) *graphql.Union

func ParseEnum added in v0.1.0

func ParseEnum(i interface{}) (*parser.Enum, error)

func ParseInputObject added in v0.1.0

func ParseInputObject(i interface{}) (*parser.Input, error)

func ParseInterface added in v0.1.0

func ParseInterface(i interface{}) (*parser.Interface, error)

func ParseObject added in v0.1.0

func ParseObject(i interface{}) (*parser.Object, error)

func ParseScalar added in v0.1.0

func ParseScalar(i interface{}) (*parser.Scalar, error)

func ParseUnion added in v0.1.0

func ParseUnion(i interface{}) (*parser.Union, error)

Types

type EnumType

type EnumType = parser.EnumType

type ID added in v0.1.1

type ID string

type InterfaceType

type InterfaceType = parser.InterfaceType

type ScalarType added in v0.1.0

type ScalarType = parser.ScalarType

type SchemaBuilder

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

func NewSchemaBuilder

func NewSchemaBuilder() *SchemaBuilder

type SchemaConfig

type SchemaConfig struct {
	Query        *parser.Object
	Mutation     *parser.Object
	Subscription *parser.Object
	Types        []parser.Type
	Extensions   []graphql.Extension
}

type UnionType

type UnionType = parser.UnionType

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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