Skip to content

Upgrading To v1.16 From v1.15

Exciting New Features 🎉

Enhancements 🚀

Breaking Changes 🛠

Upgrade Guide

As Golang v1.22 is no longer maintained, the Golang version of Goravel supports has been upgraded from 1.22 to 1.23.

goravel/example project from v1.15 to v1.16 PR can be used as an upgrade reference: goravel/example#68.

1. Update Dependencies

shell
go get github.com/goravel/framework@latest

// If using gin
go get github.com/goravel/gin@latest

// If using fiber
go get github.com/goravel/fiber@latest

// If using redis
go get github.com/goravel/redis@latest

// If using S3
go get github.com/goravel/s3@latest

// If using Oss
go get github.com/goravel/oss@latest

// If using Cos
go get github.com/goravel/cos@latest

// If using Minio
go get github.com/goravel/minio@latest

go mod tidy

2. Modify Database Configuration

Delete the connections in the database configuration file config/database.go that you don't need, and install the database driver you need.

For example, Postgres:

  1. Install the driver package
shell
go get github.com/goravel/postgres
  1. Add postgres.ServiceProvider to the config/app.go file
go
// config/app.go
import "github.com/goravel/postgres"

"providers": []foundation.ServiceProvider{
  ...
  &postgres.ServiceProvider{},
},
  1. Modify the configuration in the config/database.go file
diff
// config/database.go
import (
	"github.com/goravel/framework/contracts/database/driver"
	postgresfacades "github.com/goravel/postgres/facades"
)

"connections": map[string]any{
  "postgres": map[string]any{
    ...
++    "via": func() (driver.Driver, error) {
++      return postgresfacades.Postgres("postgres")
++    },
  },
},

3. Modify auth.go configuration file

diff
// config/auth.go

// Supported: "jwt"
"guards": map[string]any{
  "user": map[string]any{
    "driver":   "jwt",
++    "provider": "user",
  },
},

++ // Supported: "orm"
++ "providers": map[string]any{
++   "user": map[string]any{
++     "driver": "orm",
++   },
++ },

4. Modify session.go configuration file

diff
// config/session.go

-- "driver": config.Env("SESSION_DRIVER", "file"),
++ "default": config.Env("SESSION_DRIVER", "file"),
++ "drivers": map[string]any{
++   "file": map[string]any{
++     "driver": "file",
++   },
++ },

5. Modify queue.go configuration file

  • If you are using the Redis driver of the Queue module, you need to modify accordingly: Optimize Queue module configuration.

  • If you are not using it or want to use the database driver in the future, you need to modify the configuration in the config/queue.go file:

diff
// config/queue.go

-- // Drivers: "sync", "redis"
++ // Drivers: "sync", "database", "custom"
"connections": map[string]any{
  "sync": map[string]any{
    "driver": "sync",
  },
--  "redis": map[string]any{
--    "driver":     "redis",
--    "connection": "default",
--    "queue":      config.Env("REDIS_QUEUE", "default"),
--  },
++  "database": map[string]any{
++    "driver":     "database",
++    "connection": "postgres",
++    "queue":      "default",
++    "concurrent": 1,
++  },
},

++ "failed": map[string]any{
++  "database": config.Env("DB_CONNECTION", "postgres"),
++  "table":    "failed_jobs",
++ },

Add the migration file required for the database driver: database/migrations/20210101000002_create_jobs_table.go, and register the migration file in the database/kernel.go file.

6. If you need to use HTTP Client

Add the HTTP Client configuration item in the config/http.go file:

diff
// config/http.go
++ "client": map[string]any{
++   "base_url":                config.GetString("HTTP_CLIENT_BASE_URL"),
++   "timeout":                 config.GetDuration("HTTP_CLIENT_TIMEOUT"),
++   "max_idle_conns":          config.GetInt("HTTP_CLIENT_MAX_IDLE_CONNS"),
++   "max_idle_conns_per_host": config.GetInt("HTTP_CLIENT_MAX_IDLE_CONNS_PER_HOST"),
++   "max_conns_per_host":      config.GetInt("HTTP_CLIENT_MAX_CONN_PER_HOST"),
++   "idle_conn_timeout":       config.GetDuration("HTTP_CLIENT_IDLE_CONN_TIMEOUT"),
++ },

7. If using goravel/redis driver

Need to modify the configuration in the config/cache.go file:

diff
// config/cache.go
"redis": map[string]any{
  "driver":     "custom",
  "connection": "default",
  "via": func() (cache.Driver, error) {
--  return redisfacades.Redis("redis"), nil
++  return redisfacades.Cache("redis")
  },
},

8. If using carbon.DateTime etc. methods

Need to modify accordingly: Optimize Carbon methods

9. If using session custom driver

Need to modify accordingly: Optimize Session custom driver

10. If using methods of facades.Config()

Need to modify accordingly: Modify Config module methods default value type

11. If Using SQL Migrations

Need to modify accordingly: Remove SQL Migration

12. If Using Orm

Need to modify accordingly: Optimize Orm methods

13. If Using Path Method

The path method returns the absolute path, if you are using it, you need to check if you need to modify: Optimize Path method

14. If Using ctx.Request().InputMap Method

Need to modify accordingly: Optimize Request method

15. If Using console.Confirm Method

Need to modify accordingly: Optimize console Confirm method

16. If Using Testing.Request method

Need to modify accordingly: Optimize Testing.Request method

Feature Introduction

Add facades.DB()

Add the facades.DB() module, which is convenient for performing native database operations, faster than ORM.

View Document

Add facades.Http()

Add the facades.Http() module, which is convenient for performing HTTP operations.

View Document

Reconstruct facades.Queue()

  • Before v1.15, Queue only supported sync and Redis drivers, starting from v1.16, it supports database and custom driver.

  • Add jobs and failed_jobs table, used to store tasks and failed tasks.

  • Add queue:retry command, used to retry failed tasks.

  • Add queue:failed command, used to view failed tasks.

  • Support setting the number of retries when starting the queue task.

View Document

facades.Auth() supports custom driver

Before v1.15, Auth only supported JWT driver, starting from v1.16, it supports Session and custom driver, please view document.

make:* commands support automatic registration

The filesmake:migration and make:seeder, etc. commands generated files will be automatically registered to the framework after generation, no longer need to manually register like before.

Goravel Installer supports selecting driver

When installing a new project using Goravel Installer, you can select different drivers, such as: you can choose to install goravel/gin or goravel/fiber, etc.

Split Database Driver

Before multiple database drivers were integrated in the framework, which increased the software package size. Starting from v1.16, the database driver will be a separate package, so you only need to install the database driver you need.

Database TypeDriver Package
Postgresgithub.com/goravel/postgres
MySQLgithub.com/goravel/mysql
Sqlservergithub.com/goravel/sqlserver
SQLitegithub.com/goravel/sqlite

Orm adds features

  • Support querying JSON fields, view document.

  • Support updating JSON fields, view document.

  • Model supports setting the GlobalScope method, which restricts the scope of the query, update, and delete operations:

go
import "github.com/goravel/framework/contracts/orm"

type User struct {
  orm.Model
  Name string
}

func (r *User) GlobalScopes() []func(orm.Query) orm.Query {
  return []func(orm.Query) orm.Query{
    func(query orm.Query) orm.Query {
      return query.Where("name", "goravel")
    },
  }
}

Support creating Model based on data table

make:model command adds --table option, used to create Model based on data table:

shell
./artisan make:model --table=users User

// If the Model already exists, you can use the -f option to force overwrite
./artisan make:model --table=users -f User

Support runtime dynamic SQL statement

Add the EnableQueryLog method, which is used to enable query log:

go
ctx := db.EnableQueryLog(ctx)

facades.Orm().WithContext(ctx).Query()
facades.DB().WithContext(ctx).Table()

queryLogs := db.GetQueryLog(ctx)
ctx := db.DisableQueryLog(ctx)

Migration adds features

  • Add the Change method to modify the table structure(Apply to Postgres, MySQL, Sqlserver):
go
table.String("name").Change()
  • Add the RenameColumn method, for renaming the table column:
go
table.RenameColumn("old_name", "new_name")
  • Add the Comment method, for adding a comment to the table (Apply to Postgres, MySQL):
go
table.Comment("user table")
  • Add the First method, for setting the field as the first field of the table (Apply to MySQL):
go
table.String("name").First()
  • Add the After method, for setting the field as the last field of the table (Apply to MySQL):
go
table.String("name").After("id")
  • Add the Morphs, NullableMorphs, NumericMorphs, UuidMorphs, UlidMorphs methods, used to create polymorphic association fields:
go
table.Morphs("morphable")
// morphable_type, morphable_id
  • Add the Uuid, Ulid methods, used to create UUID and ULID fields:
go
table.Uuid("uuid")
table.Ulid("ulid")
  • Add the GeneratedAs and Always methods, used to create generated columns (only PostgreSQL):
go
table.String("name").GeneratedAs().Always()

Grpc adds Shutdown method

Add the Shutdown method to gracefully shut down Grpc, View Document.

Artisan adds disable print colors option

Some commands print colors by default, such as the list command. However, in some terminals or logs, the color values may be garbled. You can use the --no-ansi option to disable the print colors:

shell
go run . artisan list --no-ansi

Encrypt and decrypt env file

You may want to add the production environment env file to version control, but you don't want to expose sensitive information, you can encrypt and decrypt the env file:

View Document

Send email supports setting Header

Add the Headers method, which is used to set the Header of the email.

go
facades.Mail().Headers(map[string]string{"X-Mailer": "Goravel"})

Installer supports self upgrade

goravel/installer adds upgrade command, which is used to upgrade the installer itself:

shell
goravel upgrade
goravel upgrade v1.1.1

Localization supports embed loading

When using embed loading, the language files will be compiled into the binary file and no longer need to be deployed.

View Document

Schedule adds features

  • Add the EverySecond method, etc., please view document.

  • Add the schedule:run command, which is used to manually run tasks.

  • Add the schedule:list method, which is used to view all tasks.

Request adds features

  • Before v1.15, the following Request binding will report an error, starting from v1.16, it supports binding multiple files:
go
type StoreMediaRequest struct {
	Files []*multipart.FileHeader `form:"files" json:"files"`
}
  • Add the OriginPath method, which is used to get the original route path:
go
ctx.Request().OriginPath()
  • Add the Info method, which is used to get the route information:
go
info := ctx.Request().Info()
  • Add the Name method, which is used to get the route name:
go
name := ctx.Request().Name()
  • When the http.request_timeout configuration item is set to 0, the request will not timeout.

Route adds features

  • Add the GetRoutes method, which is used to get all routes:
go
routes := facades.Route().GetRoutes()
  • Add the Name method, which is used to set the route name:
go
facades.Route().Get("users", usersController.Index).Name("users.index")

Then you can use the Info method to get the route information:

go
route := facades.Route().Info("users.index")
  • Add the route:list command, which is used to view all routes:
shell
./artisan route:list

Testing supports creating Cache image

Add the Cache method, which is used to create Cache image:

go
cache, err := facades.Testing().Docker().Cache()

Different JWT Guard supports different configurations

Before v1.15, the ttl and secret configuration items of the JWT Guard were global configurations, starting from v1.16, you can set different ttl and secret for different JWT Guards, View Document.

ServiceProvider no longer depends on order

ServiceProvider adds an optional method Relationship() binding.Relationship, used to declare the dependency relationship of the current ServicerProvider, the ServiceProvider that sets this method will not depend on the registration order, and the ServiceProvider that does not set it will be registered last, for example:

go
type ServiceProvider struct {
}

func (r *ServiceProvider) Relationship() binding.Relationship {
	return binding.Relationship{
		Bindings: []string{
			BindingSession,
		},
		Dependencies: []string{
			binding.Config,
		},
		ProvideFor: []string{
			binding.Cache,
		},
	}
}

func (r *ServiceProvider) Register(app foundation.Application) {}

func (r *ServiceProvider) Boot(app foundation.Application) {}

Remove SQL Migration

SQL migrations have been completely removed, please use Go language migrations. Migration steps:

  1. Remove the migrations.driver key in config/database.go;
go
// config/database.go
"migrations": map[string]any{
--  "driver": "sql",
  "table":  "migrations",
},
  1. Convert the SQL migration files in the database/migrations directory to Go language migration files:
go
// Up Run the migrations.
func (r *M20241207095921CreateUsersTable) Up() error {
    return facades.Schema().Sql({Original SQL})
}

// Down Reverse the migrations.
func (r *M20241207095921CreateUsersTable) Down() error {
    return facades.Schema().Sql({Original SQL})
}
  1. Register the migration files in the database/kernel.go file;

Optimize Config module methods default value type

Before the GetStringGetIntGetBool method of the Config module had a default value type of any, starting from v1.16, the default value type is stringintbool, to ensure type safety.

diff
-- GetString(path string, defaultValue ...any) string
++ GetString(path string, defaultValue ...string) string

-- GetInt(path string, defaultValue ...any) int
++ GetInt(path string, defaultValue ...int) int

-- GetBool(path string, defaultValue ...any) bool
++ GetBool(path string, defaultValue ...bool) bool

Optimize Orm methods

  • Optimize the return value of the Count, Exists method:
diff
-- Count(count *int64) error
++ Count() (int64, error)

-- Distinct(args ...any) Query
++ Distinct(columns ...string) Query

-- Exists(exists *bool) error
++ Exists() (bool, error)

-- Select(query any, args ...any) Query
++ Select(columns ...string) Query

-- Sum(column string, dest any) error
++ Sum(column string) (int64, error)
  • The Where method supports closures:
go
facades.Orm().Query().Where(func(query orm.Query) orm.Query {
  return query.Where("height", 180).Where("age", 18)
}).FindOrFail(&user, 100)
// SELECT * FROM users WHERE id = 100 AND (height = 180 AND age = 18);

Optimize Path methods

  • Add the Resource method
go
path.Resource("views/welcome.tmpl")
  • The path method returns the absolute path

Before the path method returned the relative path of the root directory, for example, path.Config("app.go") == config/app.go. Starting from v1.16, the path method returns the absolute path, for example, path.Config("app.go") == /Users/goravel/workspace/goravel/config/app.go.

Optimize Request methods

  1. Modify the default value type of the InputMap method:
diff
-- ctx.Request().InputMap(key string, defaultValue ...map[string]string) map[string]string
++ ctx.Request().InputMap(key string, defaultValue ...map[string]any) map[string]any
  1. Add the InputMapArray method, which is used to get the array type of Map:
go
ctx.Request().InputMapArray(key string, defaultValue ...[]map[string]any) []map[string]any
  1. Add the Files method, which is used to get multiple files:
go
ctx.Request().Files(name string) ([]filesystem.File, error)

Optimize console Confirm methods

Modify the return value of the Confirm method, simplifying the judgment logic:

diff
-- Confirm(question string, option ...ConfirmOption) (bool, error)
++ Confirm(question string, option ...ConfirmOption) bool

Optimize Session custom driver

Before, you needed to register the driver through the facades.Session().Extend method. Starting from v1.16, you only need to add it to the config/session.go configuration file:

diff
// app/providers/app_service_provider.go
-- facades.Session().Extend("custom", func() session.Driver {
--   return &CustomDriver{}
-- })

// config/session.go
"drivers": map[string]any{
  "custom": map[string]any{
    "driver": "custom",
    "via": func() (session.Driver, error) {
      return &CustomDriver{}, nil
    },
  }
},

View Document

Optimize Testing.Request methods

Optimize the parameters of the WithCookies and WithCookie methods:

diff
-- WithCookies(cookies map[string]string) Request
++ WithCookies(cookies []*http.Cookie) Request

-- WithCookie(key, value string) Request
++ WithCookie(cookie *http.Cookie) Request

Optimize Carbon methods

Previously, the carbon.NewDateTime, etc. methods return a struct instance, starting from v1.16, it returns a pointer, if you define the carbon.DateTime type, etc., you need to modify it to *carbon.DateTime etc. pointer type.

Optimize str.Snake methods

The str.Snake method previously converted userID to user_i_d, starting from v1.16, it will be converted to user_id.

goravel/cloudinary is no longer maintained

goravel/cloudinary is no longer maintained, please use goravel/s3 or other drivers instead.

Optimize Queue module configuration

Before v1.15, the Redis driver of the Queue module was implemented using RichardKnop/machinery. Starting from v1.16, the Redis driver is officially supported in goravel/redis.

You can still use the Machinery driver in v1.16, but it will be removed in v1.17, it is recommended to use the Redis driver. The following provides two upgrade methods:

Continue using Machinery driver

Modify the configuration in the config/queue.go file:

diff
// config/queue.go
"connections": map[string]any{
...
  "redis": map[string]any{
--    "driver":     "redis",
++    "driver":     "machinery",
    "connection": "default",
    "queue":      config.Env("REDIS_QUEUE", "default"),
  },
},

Use Redis queue driver

Modify the configuration in the config/queue.go file:

diff
// config/queue.go
import (
  "github.com/goravel/framework/contracts/queue"
  redisfacades "github.com/goravel/redis/facades"
)

"connections": map[string]any{
...
  "redis": map[string]any{
--  "driver":     "redis",
++  "driver":     "custom",
    "connection": "default",
    "queue":      config.Env("REDIS_QUEUE", "default"),
++  "via": func() (queue.Driver, error) {
++    return redisfacades.Queue("redis") // The ` + "`redis`" + ` value is the key of ` + "`connections`" + `
++  },
  },
},

The new Redis queue driver and the original machinery queue driver tasks are stored in the same Redis, but use different queue names. Even if you switch to the new Redis queue driver, the framework will continue to read the original Machinery queue driver tasks until all tasks in Machinery are completed. At the same time, the new queue tasks are no longer written to the Machinery queue, but written to the new Redis queue, and consumed by the new Redis queue driver. To achieve seamless switching between two drivers.

You can check the data of the two drivers in Redis to see if they are correctly written and consumed:

shell
redis-cli

# Get all keys in Redis
keys

# View Machinery queue driver data (replace {} with the value)
LRANGE {app_name}_queues:{queue_name} 0 -1

# View Redis queue driver data (replace {} with the value)
LRANGE {app_name}_queues:{connection}_{queue} 0 -1

Released under the MIT License