gbgen

package module
v3.2.12 Latest Latest
Warning

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

Go to latest
Published: Apr 28, 2022 License: MIT Imports: 27 Imported by: 0

README

gqlgen-sqlboiler

We want developers to be able to build software faster using modern tools like GraphQL, Golang, React Native without depending on commercial providers like Firebase or AWS Amplify.

Our plugins generate type-safe code between gqlgen and sqlboiler models with support for unique id's across your whole database. We can automatically generate the implementation of queries and mutations like create, update, delete based on your graphql scheme and your sqlboiler models.

Tight coupling between your database and graphql scheme is required otherwise generation will be skipped. The advantage of this program is the most when you have a database already designed. You can write extra GrapQL resolvers, and override the generated functions so you can iterate fast.

Why gqlgen and sqlboiler

They go back to a schema first approach which we like. The generated code with these tools are the most efficient and fast in the Golang system (and probably outside of it too).

It's really amazing how fast a generated api with these techniques is!

Usage

Step 1

Generate database structs with: volatiletech/sqlboiler

sqlboiler mysql --no-back-referencing
Step 2

Make sure you have followed the prerequisites
Generate schema, converts and resolvers

go run convert_plugin.go

See example of convert_plugin.go

Features

  • schema.graphql based on sqlboiler structs
  • converts between sqlboiler and gqlgen
  • connections / edges / filtering / ordering / sorting
  • three-way-merge schema re-generate
  • converts between input models and sqlboiler
  • understands the difference between empty and null in update input
  • sqlboiler preloads from graphql context
  • foreign keys and relations
  • resolvers based on queries/mutations in schema
  • one-to-one relationships inside input types.
  • batch update/delete generation in resolvers.
  • enum support (only in graphql schema right now).
  • public errors in resolvers + logging via zerolog.
  • overriding convert functions
  • custom scope resolvers e.g userId, organizationId
Relay
Roadmap
  • Support overriding resolvers
  • Support gqlgen multiple .graphql files
  • Support multiple resolvers (per schema)
  • Adding automatic database migrations and integration with web-ridge/dbifier
  • Crud of adding/removing relationships from many-to-many on edges
  • Support more relationships inside input types
  • Generate tests
  • Run automatic tests in Github CI/CD in https://github.com/web-ridge/gqlgen-sqlboiler-examples
  • Batch create generation in resolvers (based on code from web-ridge/contact-tracing)

Examples

Checkout our examples to see the generated schema.grapql, converts and resolvers.
web-ridge/gqlgen-sqlboiler-examples

Output example
func PostToGraphQL(m *models.Post) *graphql_models.Post {
	if m == nil {
		return nil
	}
	r := &graphql_models.Post{
		ID:      PostIDToGraphQL(m.ID),
		Content: m.Content,
	}
	if boilergql.UintIsFilled(m.UserID) {
		if m.R != nil && m.R.User != nil {
			r.User = UserToGraphQL(m.R.User)
		} else {
			r.User = UserWithUintID(m.UserID)
		}
	}
	if m.R != nil && m.R.Comments != nil {
		r.Comments = CommentsToGraphQL(m.R.Comments)
	}
	if m.R != nil && m.R.Images != nil {
		r.Images = ImagesToGraphQL(m.R.Images)
	}
	if m.R != nil && m.R.Likes != nil {
		r.Likes = LikesToGraphQL(m.R.Likes)
	}
	return r
}

Prerequisites

sqlboiler.yml
mysql:
  dbname: dbname
  host: localhost
  port: 8889
  user: root
  pass: root
  sslmode: "false"
  blacklist:
    - notifications
    - jobs
    - password_resets
    - migrations
mysqldump:
  column-statistics: 0
gqlgen.yml
schema:
  - schema.graphql
exec:
  filename: graphql_models/generated.go
  package: graphql_models
model:
  filename: graphql_models/genereated_models.go
  package: graphql_models
resolver:
  filename: resolver.go
  type: Resolver
models:
  ConnectionBackwardPagination:
    model: github.com/web-ridge/utils-go/boilergql/v3.ConnectionBackwardPagination
  ConnectionForwardPagination:
    model: github.com/web-ridge/utils-go/boilergql/v3.ConnectionForwardPagination
  ConnectionPagination:
    model: github.com/web-ridge/utils-go/boilergql/v3.ConnectionPagination
  SortDirection:
    model: github.com/web-ridge/utils-go/boilergql/v3.SortDirection
convert_plugin.go

Put something like the code below in file convert_plugin.go

// +build ignore

package main

import (
	"fmt"
	"os"

	"github.com/99designs/gqlgen/api"
	"github.com/99designs/gqlgen/codegen/config"
	gbgen "github.com/web-ridge/gqlgen-sqlboiler/v3"
)

func main() {
	output := gbgen.Config{
		Directory:   "helpers", // supports root or sub directories
		PackageName: "helpers",
	}
	backend := gbgen.Config{
		Directory:   "models",
		PackageName: "models",
	}
	frontend := gbgen.Config{
		Directory:   "graphql_models",
		PackageName: "graphql_models",
	}

	if err := gbgen.SchemaWrite(gbgen.SchemaConfig{
		BoilerModelDirectory: backend,
		// Directives:           []string{"IsAuthenticated"},
		// GenerateBatchCreate:  false, // Not implemented yet
		GenerateMutations:    true,
		GenerateBatchDelete:  true,
		GenerateBatchUpdate:  true,
	}, "schema.graphql", gbgen.SchemaGenerateConfig{
		MergeSchema: false, // uses three way merge to keep your customization
	}); err != nil {
		fmt.Println("error while trying to generate schema.graphql")
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}

	cfg, err := config.LoadConfigFromDefaultLocations()
	if err != nil {
		fmt.Fprintln(os.Stderr, "failed to load config", err.Error())
		os.Exit(2)
	}

	if err = api.Generate(cfg,
		api.AddPlugin(gbgen.NewConvertPlugin(
			output,   // directory where convert.go, convert_input.go and preload.go should live
			backend,  // directory where sqlboiler files are put
			frontend, // directory where gqlgen models live
			gbgen.ConvertPluginConfig{
				DatabaseDriver: gbgen.MySQL, // or gbgen.PostgreSQL,
			},
		)),
		api.AddPlugin(gbgen.NewResolverPlugin(
			output,
			backend,
			frontend,
			gbgen.ResolverPluginConfig{
                   // See example for AuthorizationScopes here: https://github.com/web-ridge/gqlgen-sqlboiler-examples/blob/main/social-network/convert_plugin.go#L66
                },
		)),
	); err != nil {
		fmt.Println("error while trying generate resolver and converts")
		fmt.Fprintln(os.Stderr, err.Error())
		os.Exit(3)
	}
}

Overriding converts

Put a file in your helpers/ directory e.g. convert_override_user.go

package helpers

import (
	"github.com/../app/backend/graphql_models"
	"github.com/../app/backend/models"
)

// use same name as in one of the generated files to override
func UserCreateInputToBoiler(
	m *graphql_models.UserCreateInput,
) *models.User {
	if m == nil {
		return nil
	}

	originalConvert := originalUserCreateInputToBoiler(m)
	// e.g. bcrypt password
	return originalConvert
}

If you re-generate the original convert will get changed to originalUserCreateInputToBoiler which you can still use in your overridden convert.

Help us

We're the most happy with your time investments and/or pull request to improve this plugin. Feedback is also highly appreciated.

If you don't have time or knowledge to contribute and we did save you a lot of time, please consider a donation so we can invest more time in this library.

paypal

Documentation

Index

Constants

This section is empty.

Variables

View Source
var InputTypes = []string{"Create", "Update", "Delete"} //nolint:gochecknoglobals

Functions

func GetBoilerModels

func GetBoilerModels(dir string) ([]*BoilerModel, []*BoilerEnum)

parseModelsAndFieldsFromBoiler since these are like User.ID, User.Organization and we want them grouped by modelName and their belonging fields.

func IsPlural

func IsPlural(s string) bool

func IsSingular

func IsSingular(s string) bool

func NewConvertPlugin

func NewConvertPlugin(output, backend, frontend Config, pluginConfig ConvertPluginConfig) plugin.Plugin

func NewResolverPlugin

func NewResolverPlugin(output, backend, frontend Config, resolverPluginConfig ResolverPluginConfig) plugin.Plugin

func Plural

func Plural(s string) string

TaskBlockedBy -> TaskBlockedBies Person -> Persons Person -> People

func SchemaGet

func SchemaGet(
	config SchemaConfig,
) string

func SchemaWrite

func SchemaWrite(config SchemaConfig, outputFile string, generateOptions SchemaGenerateConfig) error

func Singular

func Singular(s string) string

TaskBlockedBies -> TaskBlockedBy People -> Person

Types

type AuthorizationScope

type AuthorizationScope struct {
	ImportPath        string
	ImportAlias       string
	ScopeResolverName string
	BoilerColumnName  string
	AddHook           func(model *BoilerModel, resolver *Resolver, templateKey string) bool
}

type BoilerEnum

type BoilerEnum struct {
	Name          string
	ModelName     string
	ModelFieldKey string
	Values        []*BoilerEnumValue
}

type BoilerEnumValue

type BoilerEnumValue struct {
	Name string
}

type BoilerField

type BoilerField struct {
	Name             string
	PluralName       string
	Type             string
	IsForeignKey     bool
	IsRequired       bool
	IsArray          bool
	IsEnum           bool
	IsRelation       bool
	Enum             BoilerEnum
	RelationshipName string
	Relationship     *BoilerModel
}

type BoilerModel

type BoilerModel struct {
	Name               string
	TableName          string
	PluralName         string
	Fields             []*BoilerField
	Enums              []*BoilerEnum
	HasPrimaryStringID bool
	HasDeletedAt       bool
}

func FindBoilerModel

func FindBoilerModel(models []*BoilerModel, modelName string) *BoilerModel

type BoilerType

type BoilerType struct {
	Name string
	Type string
}

type ColumnSetting

type ColumnSetting struct {
	Name                  string
	RelationshipModelName string
	IDAvailable           bool
}

type Config

type Config struct {
	Directory   string
	PackageName string
}

type ConvertConfig

type ConvertConfig struct {
	IsCustom         bool
	ToBoiler         string
	ToGraphQL        string
	GraphTypeAsText  string
	BoilerTypeAsText string
}

type ConvertPlugin

type ConvertPlugin struct {
	Output       Config
	Backend      Config
	Frontend     Config
	PluginConfig ConvertPluginConfig
	// contains filtered or unexported fields
}

func (*ConvertPlugin) MutateConfig

func (m *ConvertPlugin) MutateConfig(originalCfg *config.Config) error

func (*ConvertPlugin) Name

func (m *ConvertPlugin) Name() string

type ConvertPluginConfig

type ConvertPluginConfig struct {
	DatabaseDriver DatabaseDriver
}

type DatabaseDriver

type DatabaseDriver string

DatabaseDriver defines which data syntax to use for some of the converts

const (
	// MySQL is the default
	MySQL DatabaseDriver = "mysql"
	// PostgreSQL is the default
	PostgreSQL DatabaseDriver = "postgres"
)

type Enum

type Enum struct {
	Description   string
	Name          string
	PluralName    string
	Values        []*EnumValue
	HasBoilerEnum bool
	BoilerEnum    *BoilerEnum
}

type EnumValue

type EnumValue struct {
	Description     string
	Name            string
	NameLower       string
	BoilerEnumValue *BoilerEnumValue
}

type Field

type Field struct {
	Name               string
	JSONName           string
	PluralName         string
	Type               string
	TypeWithoutPointer string
	IsNumberID         bool
	IsPrimaryNumberID  bool
	IsPrimaryStringID  bool
	IsPrimaryID        bool
	IsRequired         bool
	IsPlural           bool
	ConvertConfig      ConvertConfig
	Enum               *Enum
	// relation stuff
	IsRelation                 bool
	IsRelationAndNotForeignKey bool
	IsObject                   bool
	// boiler relation stuff is inside this field
	BoilerField BoilerField
	// graphql relation ship can be found here
	Relationship *Model
	IsOr         bool
	IsAnd        bool

	// Some stuff
	Description  string
	OriginalType types.Type
}

type File

type File struct {
	// These are separated because the type definition of the resolver object may live in a different file from the
	// resolver method implementations, for example when extending a type in a different graphql schema file
	Objects         []*codegen.Object
	Resolvers       []*Resolver
	Imports         []Import
	RemainingSource string
}

type Import

type Import struct {
	Alias      string
	ImportPath string
}

type Interface

type Interface struct {
	Description string
	Name        string
}

type Model

type Model struct {
	Name               string
	PluralName         string
	BoilerModel        *BoilerModel
	PrimaryKeyType     string
	Fields             []*Field
	IsNormal           bool
	IsInput            bool
	IsCreateInput      bool
	IsUpdateInput      bool
	IsNormalInput      bool
	IsPayload          bool
	IsConnection       bool
	IsEdge             bool
	IsOrdering         bool
	IsWhere            bool
	IsFilter           bool
	IsPreloadable      bool
	PreloadArray       []Preload
	HasDeletedAt       bool
	HasPrimaryStringID bool
	// other stuff
	Description string
	PureFields  []*ast.FieldDefinition
	Implements  []string
}

func GetModelsWithInformation

func GetModelsWithInformation(
	backend Config,
	enums []*Enum,
	cfg *config.Config,
	boilerModels []*BoilerModel,
	ignoreTypePrefixes []string) []*Model

type ModelBuild

type ModelBuild struct {
	Backend      Config
	Frontend     Config
	PluginConfig ConvertPluginConfig
	PackageName  string
	Interfaces   []*Interface
	Models       []*Model
	Enums        []*Enum
	Scalars      []string
}

func (ModelBuild) Imports

func (t ModelBuild) Imports() []Import

type ParentType

type ParentType string
const (
	ParentTypeNormal      ParentType = "Normal"
	ParentTypeWhere       ParentType = "Where"
	ParentTypeCreate      ParentType = "Create"
	ParentTypeUpdate      ParentType = "Update"
	ParentTypeBatchUpdate ParentType = "BatchUpdate"
	ParentTypeBatchCreate ParentType = "BatchCreate"
)

type Preload

type Preload struct {
	Key           string
	ColumnSetting ColumnSetting
}

type Resolver

type Resolver struct {
	Object *codegen.Object
	Field  *codegen.Field

	Implementation            string
	IsSingle                  bool
	IsList                    bool
	IsListForward             bool
	IsListBackward            bool
	IsCreate                  bool
	IsUpdate                  bool
	IsDelete                  bool
	IsBatchCreate             bool
	IsBatchUpdate             bool
	IsBatchDelete             bool
	ResolveOrganizationID     bool // TODO: something more pluggable
	ResolveUserOrganizationID bool // TODO: something more pluggable
	ResolveUserID             bool // TODO: something more pluggable
	Model                     Model
	InputModel                Model
	BoilerWhiteList           string
	PublicErrorKey            string
	PublicErrorMessage        string
	SoftDeleteSuffix          string
}

type ResolverBuild

type ResolverBuild struct {
	*File
	HasRoot             bool
	PackageName         string
	ResolverType        string
	Models              []*Model
	AuthorizationScopes []*AuthorizationScope
	TryHook             func(string) bool
}

func (*ResolverBuild) ShortResolverDeclaration

func (rb *ResolverBuild) ShortResolverDeclaration(r *Resolver) string

type ResolverPlugin

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

func (*ResolverPlugin) GenerateCode

func (m *ResolverPlugin) GenerateCode(data *codegen.Data) error

func (*ResolverPlugin) Name

func (m *ResolverPlugin) Name() string

type ResolverPluginConfig

type ResolverPluginConfig struct {
	EnableSoftDeletes   bool
	AuthorizationScopes []*AuthorizationScope
}

type SchemaConfig

type SchemaConfig struct {
	BoilerModelDirectory Config
	Directives           []string
	SkipInputFields      []string
	GenerateBatchCreate  bool
	GenerateMutations    bool
	GenerateBatchDelete  bool
	GenerateBatchUpdate  bool
	HookShouldAddModel   func(model SchemaModel) bool
	HookShouldAddField   func(model SchemaModel, field SchemaField) bool
	HookChangeField      func(model *SchemaModel, field *SchemaField)
	HookChangeFields     func(model *SchemaModel, fields []*SchemaField, parenType ParentType) []*SchemaField
	HookChangeModel      func(model *SchemaModel)
}

type SchemaField

type SchemaField struct {
	Name                 string
	Type                 string // String, ID, Integer
	InputWhereType       string
	InputCreateType      string
	InputUpdateType      string
	InputBatchUpdateType string
	InputBatchCreateType string
	BoilerField          *BoilerField
	SkipInput            bool
	SkipWhere            bool
	SkipCreate           bool
	SkipUpdate           bool
	SkipBatchUpdate      bool
	SkipBatchCreate      bool
	InputDirectives      []string
	Directives           []string
}

func NewSchemaField

func NewSchemaField(name string, typ string, boilerField *BoilerField) *SchemaField

func (*SchemaField) SetInputTypeForAllInputs

func (s *SchemaField) SetInputTypeForAllInputs(v string)

func (*SchemaField) SetSkipForAllInputs

func (s *SchemaField) SetSkipForAllInputs(v bool)

type SchemaGenerateConfig

type SchemaGenerateConfig struct {
	MergeSchema bool
}

type SchemaModel

type SchemaModel struct {
	Name   string
	Fields []*SchemaField
}

type SimpleWriter

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

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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