README

Goyave Logo

Version Build Status Coverage Status Go Report

License Awesome Discord

An Elegant Golang Web Framework

Goyave is a progressive and accessible web application framework focused on REST APIs, aimed at making backend development easy and enjoyable. It has a philosophy of cleanliness and conciseness to make programs more elegant, easier to maintain and more focused. Goyave is an opinionated framework, helping your applications keeping a uniform architecture and limit redundancy. With Goyave, expect a full package with minimum setup.

  • Clean Code: Goyave has an expressive, elegant syntax, a robust structure and conventions. Minimalist calls and reduced redundancy are among the Goyave's core principles.
  • Fast Development: Develop faster and concentrate on the business logic of your application thanks to the many helpers and built-in functions.
  • Powerful functionalities: Goyave is accessible, yet powerful. The framework includes routing, request parsing, validation, localization, testing, authentication, and more!
  • Reliability: Error reporting is made easy thanks to advanced error handling and panic recovery. The framework is deeply tested.

Table of contents

Learning Goyave

The Goyave framework has an extensive documentation covering in-depth subjects and teaching you how to run a project using Goyave from setup to deployment.

Read the documentation

pkg.go.dev

Example project

Getting started

Requirements

  • Go 1.13+
  • Go modules

Install using the template project

You can bootstrap your project using the Goyave template project. This project has a complete directory structure already set up for you.

Linux / MacOS
$ curl https://goyave.dev/install.sh | bash -s github.com/username/projectname
Windows (Powershell)
> & ([scriptblock]::Create((curl "https://goyave.dev/install.ps1").Content)) -moduleName github.com/username/projectname

Run go run . in your project's directory to start the server, then try to request the hello route.

$ curl http://localhost:8080/hello
Hi!

There is also an echo route, with basic validation of the request body.

$ curl -H "Content-Type: application/json" -X POST -d '{"text":"abc 123"}' http://localhost:8080/echo
abc 123

Features tour

This section's goal is to give a brief look at the main features of the framework. It doesn't describe everything the framework has to offer, so don't consider this documentation. If you want a complete reference and documentation, head to pkg.go.dev and the official documentation.

Hello world from scratch

The example below shows a basic Hello world application using Goyave.

import "goyave.dev/goyave/v3"

func registerRoutes(router *goyave.Router) {
    router.Get("/hello", func(response *goyave.Response, request *goyave.Request) {
        response.String(http.StatusOK, "Hello world!")
    })
}

func main() {
    if err := goyave.Start(registerRoutes); err != nil {
        os.Exit(err.(*goyave.Error).ExitCode)
    }
}

Configuration

To configure your application, use the config.json file at your project's root. If you are using the template project, copy config.example.json to config.json. The following code is an example of configuration for a local development environment:

{
    "app": {
        "name": "goyave_template",
        "environment": "localhost",
        "debug": true,
        "defaultLanguage": "en-US"
    },
    "server": {
        "host": "127.0.0.1",
        "maintenance": false,
        "protocol": "http",
        "domain": "",
        "port": 8080,
        "httpsPort": 8081,
        "timeout": 10,
        "maxUploadSize": 10
    },
    "database": {
        "connection": "mysql",
        "host": "127.0.0.1",
        "port": 3306,
        "name": "goyave",
        "username": "root",
        "password": "root",
        "options": "charset=utf8mb4&collation=utf8mb4_general_ci&parseTime=true&loc=Local",
        "maxOpenConnections": 20,
        "maxIdleConnections": 20,
        "maxLifetime": 300,
        "autoMigrate": false
    }
}

If this config file misses some config entries, the default values will be used.

All entries are validated. That means that the application will not start if you provided an invalid value in your config (for example if the specified port is not a number). That also means that a goroutine trying to change a config entry with the incorrect type will panic.
Entries can be registered with a default value, their type and authorized values from any package.

Getting a value:

config.GetString("app.name") // "goyave"
config.GetBool("app.debug") // true
config.GetInt("server.port") // 80
config.Has("app.name") // true

Setting a value:

config.Set("app.name", "my awesome app")

Using environment variables:

{
  "database": {
    "host": "${DB_HOST}"
  }
}

Learn more about configuration in the documentation.

Routing

Routing is an essential part of any Goyave application. Routes definition is the action of associating a URI, sometimes having parameters, with a handler which will process the request and respond to it. Separating and naming routes clearly is important to make your API or website clear and expressive.

Routes are defined in routes registrer functions. The main route registrer is passed to goyave.Start() and is executed automatically with a newly created root-level router.

func Register(router *goyave.Router) {
    // Register your routes here

    // With closure, not recommended
    router.Get("/hello", func(response *goyave.Response, r *goyave.Request) {
        response.String(http.StatusOK, "Hi!")
    })

    router.Get("/hello", myHandlerFunction)
    router.Post("/user", user.Register).Validate(user.RegisterRequest)
    router.Route("PUT|PATCH", "/user", user.Update).Validate(user.UpdateRequest)
    router.Route("POST", "/product", product.Store).Validate(product.StoreRequest).Middleware(middleware.Trim)
}

URIs can have parameters, defined using the format {name} or {name:pattern}. If a regular expression pattern is not defined, the matched variable will be anything until the next slash.

Example:

router.Get("/product/{key}", product.Show)
router.Get("/product/{id:[0-9]+}", product.ShowById)
router.Get("/category/{category}/{id:[0-9]+}", category.Show)

Route parameters can be retrieved as a map[string]string in handlers using the request's Params attribute.

func myHandlerFunction(response *goyave.Response, request *goyave.Request) {
    category := request.Params["category"]
    id, _ := strconv.Atoi(request.Params["id"])
    //...
}

Learn more about routing in the documentation.

Controller

Controllers are files containing a collection of Handlers related to a specific feature. Each feature should have its own package. For example, if you have a controller handling user registration, user profiles, etc, you should create a http/controller/user package. Creating a package for each feature has the advantage of cleaning up route definitions a lot and helps keeping a clean structure for your project.

A Handler is a func(*goyave.Response, *goyave.Request). The first parameter lets you write a response, and the second contains all the information extracted from the raw incoming request.

Handlers receive a goyave.Response and a goyave.Request as parameters.
goyave.Request can give you a lot of information about the incoming request, such as its headers, cookies, or body. Learn more here.
goyave.Response implements http.ResponseWriter and is used to write a response. If you didn't write anything before the request lifecycle ends, 204 No Content is automatically written. Learn everything about reponses here.

Let's take a very simple CRUD as an example for a controller definition: http/controller/product/product.go:

func Index(response *goyave.Response, request *goyave.Request) {
    products := []model.Product{}
    result := database.Conn().Find(&products)
    if response.HandleDatabaseError(result) {
        response.JSON(http.StatusOK, products)
    }
}

func Show(response *goyave.Response, request *goyave.Request) {
    product := model.Product{}
    result := database.Conn().First(&product, request.Params["id"])
    if response.HandleDatabaseError(result) {
        response.JSON(http.StatusOK, product)
    }
}

func Store(response *goyave.Response, request *goyave.Request) {
    product := model.Product{
        Name:  request.String("name"),
        Price: request.Numeric("price"),
    }
    if err := database.Conn().Create(&product).Error; err != nil {
        response.Error(err)
    } else {
        response.JSON(http.StatusCreated, map[string]uint{"id": product.ID})
    }
}

func Update(response *goyave.Response, request *goyave.Request) {
    product := model.Product{}
    db := database.Conn()
    result := db.Select("id").First(&product, request.Params["id"])
    if response.HandleDatabaseError(result) {
        if err := db.Model(&product).Update("name", request.String("name")).Error; err != nil {
            response.Error(err)
        }
    }
}

func Destroy(response *goyave.Response, request *goyave.Request) {
    product := model.Product{}
    db := database.Conn()
    result := db.Select("id").First(&product, request.Params["id"])
    if response.HandleDatabaseError(result) {
        if err := db.Delete(&product).Error; err != nil {
            response.Error(err)
        }
    }
}

Learn more about controllers in the documentation.

Middleware

Middleware are handlers executed before the controller handler. They are a convenient way to filter, intercept or alter HTTP requests entering your application. For example, middleware can be used to authenticate users. If the user is not authenticated, a message is sent to the user even before the controller handler is reached. However, if the user is authenticated, the middleware will pass to the next handler. Middleware can also be used to sanitize user inputs, by trimming strings for example, to log all requests into a log file, to automatically add headers to all your responses, etc.

func MyCustomMiddleware(next goyave.Handler) goyave.Handler {
    return func(response *goyave.Response, request *goyave.Request) {
        // Do something
        next(response, request) // Pass to the next handler
    }
}

To assign a middleware to a router, use the router.Middleware() function. Many middleware can be assigned at once. The assignment order is important as middleware will be executed in order.

router.Middleware(middleware.MyCustomMiddleware)

Learn more about middleware in the documentation.

Validation

Goyave provides a powerful, yet easy way to validate all incoming data, no matter its type or its format, thanks to a large number of validation rules.

Incoming requests are validated using rules set, which associate rules with each expected field in the request.

Validation rules can alter the raw data. That means that when you validate a field to be number, if the validation passes, you are ensured that the data you'll be using in your controller handler is a float64. Or if you're validating an IP, you get a net.IP object.

Validation is automatic. You just have to define a rules set and assign it to a route. When the validation doesn't pass, the request is stopped and the validation errors messages are sent as a response.

Rule sets are defined in the same package as the controller, typically in a separate file named request.go. Rule sets are named after the name of the controller handler they will be used with, and end with Request. For example, a rule set for the Store handler will be named StoreRequest. If a rule set can be used for multiple handlers, consider using a name suited for all of them. The rules for a store operation are often the same for update operations, so instead of duplicating the set, create one unique set called UpsertRequest.

Example: (http/controller/product/request.go)

var (
    StoreRequest validation.RuleSet = validation.RuleSet{
        "name":  {"required", "string", "between:3,50"},
        "price": {"required", "numeric", "min:0.01"},
        "image": {"nullable", "file", "image", "max:2048", "count:1"},
    }

    // ...
)

Once your rules sets are defined, you need to assign them to your routes using the Validate() method.

router.Post("/product", product.Store).Validate(product.StoreRequest)

Learn more about validation in the documentation.

Database

Most web applications use a database. In this section, we are going to see how Goyave applications can query a database, using the awesome Gorm ORM.

Database connections are managed by the framework and are long-lived. When the server shuts down, the database connections are closed automatically. So you don't have to worry about creating, closing or refreshing database connections in your application.

Very few code is required to get started with databases. There are some configuration options that you need to change though:

  • database.connection
  • database.host
  • database.port
  • database.name
  • database.username
  • database.password
  • database.options
  • database.maxOpenConnection
  • database.maxIdleConnection
  • database.maxLifetime
user := model.User{}
db := database.Conn()
db.First(&user)

fmt.Println(user)

Models are usually just normal Golang structs, basic Go types, or pointers of them. sql.Scanner and driver.Valuer interfaces are also supported.

func init() {
    database.RegisterModel(&User{})
}

type User struct {
    gorm.Model
    Name         string
    Age          sql.NullInt64
    Birthday     *time.Time
    Email        string  `gorm:"type:varchar(100);uniqueIndex"`
    Role         string  `gorm:"size:255"` // set field size to 255
    MemberNumber *string `gorm:"unique;not null"` // set member number to unique and not null
    Num          int     `gorm:"autoIncrement"` // set num to auto incrementable
    Address      string  `gorm:"index:addr"` // create index with name `addr` for address
    IgnoreMe     int     `gorm:"-"` // ignore this field
}

Learn more about using databases in the documentation.

Localization

The Goyave framework provides a convenient way to support multiple languages within your application. Out of the box, Goyave only provides the en-US language.

Language files are stored in the resources/lang directory.

.
└── resources
    └── lang
        └── en-US (language name)
            ├── fields.json (optional)
            ├── locale.json (optional)
            └── rules.json (optional)

The fields.json file contains the field names translations and their rule-specific messages. Translating field names helps making more expressive messages instead of showing the technical field name to the user. Rule-specific messages let you override a validation rule message for a specific field.

Example:

{
    "email": {
        "name": "email address",
        "rules": {
            "required": "You must provide an :field."
        }
    }
}

The locale.json file contains all language lines that are not related to validation. This is the place where you should write the language lines for your user interface or for the messages returned by your controllers.

Example:

{
    "product.created": "The product have been created with success.",
    "product.deleted": "The product have been deleted with success."
}

The rules.json file contains the validation rules messages. These messages can have placeholders, which will be automatically replaced by the validator with dynamic values. If you write custom validation rules, their messages shall be written in this file.

Example:

{
    "integer": "The :field must be an integer.",
    "starts_with": "The :field must start with one of the following values: :values.",
    "same": "The :field and the :other must match."
}

When an incoming request enters your application, the core language middleware checks if the Accept-Language header is set, and set the goyave.Request's Lang attribute accordingly. Localization is handled automatically by the validator.

func ControllerHandler(response *goyave.Response, request *goyave.Request) {
    response.String(http.StatusOK, lang.Get(request.Lang, "my-custom-message"))
}

Learn more about localization in the documentation.

Testing

Goyave provides an API to ease the unit and functional testing of your application. This API is an extension of testify. goyave.TestSuite inherits from testify's suite.Suite, and sets up the environment for you. That means:

  • GOYAVE_ENV environment variable is set to test and restored to its original value when the suite is done.
  • All tests are run using your project's root as working directory. This directory is determined by the presence of a go.mod file.
  • Config and language files are loaded before the tests start. As the environment is set to test, you need a config.test.json in the root directory of your project.

This setup is done by the function goyave.RunTest, so you shouldn't run your test suites using testify's suite.Run() function.

The following example is a functional test and would be located in the test package.

import (
    "github.com/username/projectname/http/route"
    "goyave.dev/goyave/v3"
)

type CustomTestSuite struct {
    goyave.TestSuite
}

func (suite *CustomTestSuite) TestHello() {
    suite.RunServer(route.Register, func() {
        resp, err := suite.Get("/hello", nil)
        suite.Nil(err)
        suite.NotNil(resp)
        if resp != nil {
            defer resp.Body.Close()
            suite.Equal(200, resp.StatusCode)
            suite.Equal("Hi!", string(suite.GetBody(resp)))
        }
    })
}

func TestCustomSuite(t *testing.T) {
    goyave.RunTest(t, new(CustomTestSuite))
}

When writing functional tests, you can retrieve the response body easily using suite.GetBody(response).

resp, err := suite.Get("/get", nil)
suite.Nil(err)
if err == nil {
    defer resp.Body.Close()
    suite.Equal("response content", string(suite.GetBody(resp)))
}

URL-encoded requests:

headers := map[string]string{"Content-Type": "application/x-www-form-urlencoded; param=value"}
resp, err := suite.Post("/product", headers, strings.NewReader("field=value"))
suite.Nil(err)
if err == nil {
    defer resp.Body.Close()
    suite.Equal("response content", string(suite.GetBody(resp)))
}

JSON requests:

headers := map[string]string{"Content-Type": "application/json"}
body, _ := json.Marshal(map[string]interface{}{"name": "Pizza", "price": 12.5})
resp, err := suite.Post("/product", headers, bytes.NewReader(body))
suite.Nil(err)
if err == nil {
    defer resp.Body.Close()
    suite.Equal("response content", string(suite.GetBody(resp)))
}

Testing JSON response:

suite.RunServer(route.Register, func() {
    resp, err := suite.Get("/product", nil)
    suite.Nil(err)
    if err == nil {
        defer resp.Body.Close()
        json := map[string]interface{}{}
        err := suite.GetJSONBody(resp, &json)
        suite.Nil(err)
        if err == nil { // You should always check parsing error before continuing.
            suite.Equal("value", json["field"])
            suite.Equal(float64(42), json["number"])
        }
    }
})

The testing API has many more features such as record generators, factories, database helpers, a middleware tester, support for multipart and file uploads...

Learn more about testing in the documentation.

Status handlers

Status handlers are regular handlers executed during the finalization step of the request's lifecycle if the response body is empty but a status code has been set. Status handler are mainly used to implement a custom behavior for user or server errors (400 and 500 status codes).

The following file http/controller/status/status.go is an example of custom 404 error handling:

package status

import "goyave.dev/goyave/v3"

func NotFound(response *goyave.Response, request *goyave.Request) {
    if err := response.RenderHTML(response.GetStatus(), "errors/404.html", nil); err != nil {
        response.Error(err)
    }
}

Status handlers are registered in the router.

// Use "status.NotFound" for empty responses having status 404 or 405.
router.StatusHandler(status.NotFound, 404)

Learn more about status handlers in the documentation.

CORS

Goyave provides a built-in CORS module. CORS options are set on routers. If the passed options are not nil, the CORS core middleware is automatically added.

router.CORS(cors.Default())

CORS options should be defined before middleware and route definition. All of this router's sub-routers inherit CORS options by default. If you want to remove the options from a sub-router, or use different ones, simply create another cors.Options object and assign it.

cors.Default() can be used as a starting point for custom configuration.

options := cors.Default()
options.AllowedOrigins = []string{"https://google.com", "https://images.google.com"}
router.CORS(options)

Learn more about CORS in the documentation.

Authentication

Goyave provides a convenient and expandable way of handling authentication in your application. Authentication can be enabled when registering your routes:

import "goyave.dev/goyave/v3/auth"

//...

authenticator := auth.Middleware(&model.User{}, &auth.BasicAuthenticator{})
router.Middleware(authenticator)

Authentication is handled by a simple middleware calling an Authenticator. This middleware also needs a model, which will be used to fetch user information on a successful login.

Authenticators use their model's struct fields tags to know which field to use for username and password. To make your model compatible with authentication, you must add the auth:"username" and auth:"password" tags:

type User struct {
    gorm.Model
    Email    string `gorm:"type:char(100);uniqueIndex" auth:"username"`
    Name     string `gorm:"type:char(100)"`
    Password string `gorm:"type:char(60)" auth:"password"`
}

When a user is successfully authenticated on a protected route, its information is available in the controller handler, through the request User field.

func Hello(response *goyave.Response, request *goyave.Request) {
    user := request.User.(*model.User)
    response.String(http.StatusOK, "Hello " + user.Name)
}

Learn more about authentication in the documentation.

Rate limiting

Rate limiting is a crucial part of public API development. If you want to protect your data from being crawled, protect yourself from DDOS attacks, or provide different tiers of access to your API, you can do it using Goyave's built-in rate limiting middleware.

This middleware uses either a client's IP or an authenticated client's ID (or any other way of identifying a client you may need) and maps a quota, a quota duration and a request count to it. If a client exceeds the request quota in the given quota duration, this middleware will block and return HTTP 429 Too Many Requests.

The middleware will always add the following headers to the response:

  • RateLimit-Limit: containing the requests quota in the time window
  • RateLimit-Remaining: containing the remaining requests quota in the current window
  • RateLimit-Reset: containing the time remaining in the current window, specified in seconds
import "goyave.dev/goyave/v3/middleware/ratelimiter"

ratelimiterMiddleware := ratelimiter.New(func(request *goyave.Request) ratelimiter.Config {
    return ratelimiter.Config {
        RequestQuota:  100,
        QuotaDuration: time.Minute,
        // 100 requests per minute allowed
        // Client IP will be used as identifier
    }
})

router.Middleware(ratelimiterMiddleware)

Learn more about rate limiting in the documentation.

Websocket

Goyave is using gorilla/websocket and adds a layer of abstraction to it to make it easier to use. You don't have to write the connection upgrading logic nor the close handshake. Just like regular HTTP handlers, websocket handlers benefit from reliable error handling and panic recovery.

Here is an example of an "echo" feature:

upgrader := websocket.Upgrader{}
router.Get("/websocket", upgrader.Handler(func(c *websocket.Conn, request *goyave.Request) error {
    for {
        mt, message, err := c.ReadMessage()
        if err != nil {
            return err
        }
        goyave.Logger.Printf("recv: %s", message)
        err = c.WriteMessage(mt, message)
        if err != nil {
            return fmt.Errorf("write: %w", err)
        }
    }
}))

Learn more about websockets in the documentation.

Contributing

Thank you for considering contributing to the Goyave framework! You can find the contribution guide in the documentation.

I have many ideas for the future of Goyave. I would be infinitely grateful to whoever want to support me and let me continue working on Goyave and making it better and better.

You can support me on Github Sponsor or Patreon.

❤ Sponsor me!

I'm very grateful to my patrons, sponsors and donators:

  • Ben Hyrman
  • Massimiliano Bertinetti
  • ethicnology

Contributors

A big "Thank you" to the Goyave contributors:

License

The Goyave framework is MIT Licensed. Copyright © 2019 Jérémy LAMBERT (SystemGlitch)

Documentation

Index

Constants

View Source
const (
	// ExitInvalidConfig the exit code returned when the config
	// validation doesn't pass.
	ExitInvalidConfig = 3

	// ExitNetworkError the exit code returned when an error
	// occurs when opening the network listener
	ExitNetworkError = 4

	// ExitHTTPError the exit code returned when an error
	// occurs in the HTTP server (port already in use for example)
	ExitHTTPError = 5
)

Variables

View Source
var (

	// Logger the logger for default output
	// Writes to stdout by default.
	Logger *log.Logger = log.New(os.Stdout, "", log.LstdFlags)

	// AccessLogger the logger for access. This logger
	// is used by the logging middleware.
	// Writes to stdout by default.
	AccessLogger *log.Logger = log.New(os.Stdout, "", 0)

	// ErrLogger the logger in which errors and stacktraces are written.
	// Writes to stderr by default.
	ErrLogger *log.Logger = log.New(os.Stderr, "", log.LstdFlags)
)
View Source
var (
	// ErrNotHijackable returned by response.Hijack() if the underlying
	// http.ResponseWriter doesn't implement http.Hijacker. This can
	// happen with HTTP/2 connections.
	ErrNotHijackable = errors.New("Underlying http.ResponseWriter doesn't implement http.Hijacker")
)

Functions

func BaseURL

func BaseURL() string

BaseURL returns the base URL of your application.

func ClearShutdownHooks

func ClearShutdownHooks()

ClearShutdownHooks removes all shutdown hooks.

func ClearStartupHooks

func ClearStartupHooks()

ClearStartupHooks removes all startup hooks.

func DisableMaintenance

func DisableMaintenance()

DisableMaintenance replace the main server handler with the original router.

func EnableMaintenance

func EnableMaintenance()

EnableMaintenance replace the main server handler with the "Service Unavailable" handler.

func ErrorStatusHandler

func ErrorStatusHandler(response *Response, request *Request)

ErrorStatusHandler a generic status handler for non-success codes. Writes the corresponding status message to the response.

func IsMaintenanceEnabled

func IsMaintenanceEnabled() bool

IsMaintenanceEnabled return true if the server is currently in maintenance mode.

func IsReady

func IsReady() bool

IsReady returns true if the server has finished initializing and is ready to serve incoming requests.

func PanicStatusHandler

func PanicStatusHandler(response *Response, request *Request)

PanicStatusHandler for the HTTP 500 error. If debugging is enabled, writes the error details to the response and print stacktrace in the console. If debugging is not enabled, writes `{"error": "Internal Server Error"}` to the response.

func RegisterShutdownHook

func RegisterShutdownHook(hook func())

RegisterShutdownHook to execute some code after the server stopped. Shutdown hooks are executed before goyave.Start() returns.

func RegisterStartupHook

func RegisterStartupHook(hook func())

RegisterStartupHook to execute some code once the server is ready and running.

func RunTest

func RunTest(t *testing.T, suite ITestSuite) bool

RunTest run a test suite with prior initialization of a test environment. The GOYAVE_ENV environment variable is automatically set to "test" and restored to its original value at the end of the test run. All tests are run using your project's root as working directory. This directory is determined by the presence of a "go.mod" file.

func Start

func Start(routeRegistrer func(*Router)) error

Start starts the web server. The routeRegistrer parameter is a function aimed at registering all your routes and middleware.

import (
    "goyave.dev/goyave/v3"
    "github.com/username/projectname/route"
)

func main() {
    if err := goyave.Start(route.Register); err != nil {
        os.Exit(err.(*goyave.Error).ExitCode)
    }
}

Errors returned can be safely type-asserted to "*goyave.Error". Panics if the server is already running.

func Stop

func Stop()

Stop gracefully shuts down the server without interrupting any active connections.

Make sure the program doesn't exit and waits instead for Stop to return.

Stop does not attempt to close nor wait for hijacked connections such as WebSockets. The caller of Stop should separately notify such long-lived connections of shutdown and wait for them to close, if desired.

func ValidationStatusHandler

func ValidationStatusHandler(response *Response, request *Request)

ValidationStatusHandler for HTTP 400 and HTTP 422 errors. Writes the validation errors to the response.

Types

type Error

type Error struct {
	Err      error
	ExitCode int
}

Error wrapper for errors directely related to the server itself. Contains an exit code and the original error.

func (*Error) Error

func (e *Error) Error() string

type Handler

type Handler func(*Response, *Request)

Handler is a controller or middleware function

func NativeHandler

func NativeHandler(handler http.Handler) Handler

NativeHandler is an adapter function for "http.Handler". With this adapter, you can plug non-Goyave handlers to your application.

Just remember that the body contains the raw data, which haven't been validated nor converted. This means that native handlers are not guaranteed to work and cannot modify the request data. Request properties, such as headers, can still be modified. Prefer implementing a Goyave handler.

This feature is a compatibility layer with the rest of the Golang web ecosystem. Prefer using Goyave handlers if possible.

type ITestSuite

type ITestSuite interface {
	RunServer(func(*Router), func())
	Timeout() time.Duration
	SetTimeout(time.Duration)
	Middleware(Middleware, *Request, Handler) *http.Response

	Get(string, map[string]string) (*http.Response, error)
	Post(string, map[string]string, io.Reader) (*http.Response, error)
	Put(string, map[string]string, io.Reader) (*http.Response, error)
	Patch(string, map[string]string, io.Reader) (*http.Response, error)
	Delete(string, map[string]string, io.Reader) (*http.Response, error)
	Request(string, string, map[string]string, io.Reader) (*http.Response, error)

	T() *testing.T
	SetT(*testing.T)

	GetBody(*http.Response) []byte
	GetJSONBody(*http.Response, interface{}) error
	CreateTestFiles(paths ...string) []filesystem.File
	WriteFile(*multipart.Writer, string, string, string)
	WriteField(*multipart.Writer, string, string)
	CreateTestRequest(*http.Request) *Request
	CreateTestResponse(http.ResponseWriter) *Response
	// contains filtered or unexported methods
}

ITestSuite is an extension of testify's Suite for Goyave-specific testing.

type Middleware

type Middleware func(Handler) Handler

Middleware function generating middleware handler function.

Request data is available to middleware, but bear in mind that it had not been validated yet. That means that you can modify or filter data. (Trim strings for example)

func NativeMiddleware

func NativeMiddleware(middleware NativeMiddlewareFunc) Middleware

NativeMiddleware is an adapter function for standard library middleware.

Native middleware work like native handlers. See "NativeHandler" for more details.

type NativeMiddlewareFunc

type NativeMiddlewareFunc func(http.Handler) http.Handler

NativeMiddlewareFunc is a function which receives an http.Handler and returns another http.Handler.

type PreWriter

type PreWriter interface {
	PreWrite(b []byte)
}

PreWriter is a writter that needs to alter the response headers or status before they are written. If implemented, PreWrite will be called right before the Write operation.

type Request

type Request struct {
	Rules  *validation.Rules
	Params map[string]string
	Data   map[string]interface{}
	Extra  map[string]interface{}
	User   interface{}
	Lang   string
	// contains filtered or unexported fields
}

Request struct represents an http request. Contains the validated body in the Data attribute if the route was defined with a request generator function

func (*Request) BasicAuth

func (r *Request) BasicAuth() (username, password string, ok bool)

BasicAuth returns the username and password provided in the request's Authorization header, if the request uses HTTP Basic Authentication.

func (*Request) BearerToken

func (r *Request) BearerToken() (string, bool)

BearerToken extract the auth token from the "Authorization" header. Only takes tokens of type "Bearer". Returns empty string if no token found or the header is invalid.

func (*Request) Bool

func (r *Request) Bool(field string) bool

Bool get a bool field from the request data. Panics if the field is not a bool.

func (*Request) CORSOptions

func (r *Request) CORSOptions() *cors.Options

CORSOptions returns the CORS options applied to this request, or nil. The returned object is a copy of the options applied to the router. Therefore, altering the returned object will not alter the router's options.

func (*Request) ContentLength

func (r *Request) ContentLength() int64

ContentLength records the length of the associated content. The value -1 indicates that the length is unknown.

func (*Request) Cookies

func (r *Request) Cookies(name string) []*http.Cookie

Cookies returns the HTTP cookies sent with the request.

func (*Request) Date

func (r *Request) Date(field string) time.Time

Date get a date field from the request data. Panics if the field is not a date.

func (*Request) File

func (r *Request) File(field string) []filesystem.File

File get a file field from the request data. Panics if the field is not numeric.

func (*Request) Has

func (r *Request) Has(field string) bool

Has check if the given field exists in the request data.

func (*Request) Header

func (r *Request) Header() http.Header

Header contains the request header fields either received by the server or to be sent by the client. Header names are case-insensitive.

If the raw request has the following header lines,

Host: example.com
accept-encoding: gzip, deflate
Accept-Language: en-us
fOO: Bar
foo: two

then the header map will look like this:

Header = map[string][]string{
	"Accept-Encoding": {"gzip, deflate"},
	"Accept-Language": {"en-us"},
	"Foo": {"Bar", "two"},
}

func (*Request) IP

func (r *Request) IP(field string) net.IP

IP get an IP field from the request data. Panics if the field is not an IP.

func (*Request) Integer

func (r *Request) Integer(field string) int

Integer get an integer field from the request data. Panics if the field is not an integer.

func (*Request) Method

func (r *Request) Method() string

Method specifies the HTTP method (GET, POST, PUT, etc.).

func (*Request) Numeric

func (r *Request) Numeric(field string) float64

Numeric get a numeric field from the request data. Panics if the field is not numeric.

func (*Request) Object

func (r *Request) Object(field string) map[string]interface{}

Object get an object field from the request data. Panics if the field is not an object.

func (*Request) Protocol

func (r *Request) Protocol() string

Protocol the protocol used by this request, "HTTP/1.1" for example.

func (*Request) Referrer

func (r *Request) Referrer() string

Referrer returns the referring URL, if sent in the request.

func (*Request) RemoteAddress

func (r *Request) RemoteAddress() string

RemoteAddress allows to record the network address that sent the request, usually for logging.

func (*Request) Request

func (r *Request) Request() *http.Request

Request return the raw http request. Prefer using the "goyave.Request" accessors.

func (*Request) Route

func (r *Request) Route() *Route

Route returns the current route.

func (*Request) String

func (r *Request) String(field string) string

String get a string field from the request data. Panics if the field is not a string.

func (*Request) Timezone

func (r *Request) Timezone(field string) *time.Location

Timezone get a timezone field from the request data. Panics if the field is not a timezone.

func (*Request) ToStruct

func (r *Request) ToStruct(dst interface{}) error

ToStruct map the request data to a struct.

 type UserInsertRequest struct {
	 Username string
	 Email string
 }
 //...
 userInsertRequest := UserInsertRequest{}
 if err := request.ToStruct(&userInsertRequest); err != nil {
  panic(err)
 }

func (*Request) URI

func (r *Request) URI() *url.URL

URI specifies the URI being requested. Use this if you absolutely need the raw query params, url, etc. Otherwise use the provided methods and fields of the "goyave.Request".

func (*Request) URL

func (r *Request) URL(field string) *url.URL

URL get a URL field from the request data. Panics if the field is not a URL.

func (*Request) UUID

func (r *Request) UUID(field string) uuid.UUID

UUID get a UUID field from the request data. Panics if the field is not a UUID.

func (*Request) UserAgent

func (r *Request) UserAgent() string

UserAgent returns the client's User-Agent, if sent in the request.

type Response

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

Response represents a controller response.

func (*Response) Cookie

func (r *Response) Cookie(cookie *http.Cookie)

Cookie add a Set-Cookie header to the response. The provided cookie must have a valid Name. Invalid cookies may be silently dropped.

func (*Response) Download

func (r *Response) Download(file string, fileName string) error

Download write a file as an attachment element. Automatically detects the file MIME type and sets the "Content-Type" header accordingly. If the file doesn't exist, respond with status 404 Not Found. The given path can be relative or absolute.

The "fileName" parameter defines the name the client will see. In other words, it sets the header "Content-Disposition" to "attachment; filename="${fileName}""

If you want the file to be sent as an inline element ("Content-Disposition: inline"), use the "File" function instead.

func (*Response) Error

func (r *Response) Error(err interface{}) error

Error print the error in the console and return it with an error code 500. If debugging is enabled in the config, the error is also written in the response and the stacktrace is printed in the console. If debugging is not enabled, only the status code is set, which means you can still write to the response, or use your error status handler.

func (*Response) File

func (r *Response) File(file string) error

File write a file as an inline element. Automatically detects the file MIME type and sets the "Content-Type" header accordingly. If the file doesn't exist, respond with status 404 Not Found. The given path can be relative or absolute.

If you want the file to be sent as a download ("Content-Disposition: attachment"), use the "Download" function instead.

func (*Response) GetError

func (r *Response) GetError() interface{}

GetError return the value which caused a panic in the request's handling, or nil.

func (*Response) GetStacktrace

func (r *Response) GetStacktrace() string

GetStacktrace return the stacktrace of when the error occurred, or an empty string. The stacktrace is captured by the recovery middleware.

func (*Response) GetStatus

func (r *Response) GetStatus() int

GetStatus return the response code for this request or 0 if not yet set.

func (*Response) HandleDatabaseError

func (r *Response) HandleDatabaseError(db *gorm.DB) bool

HandleDatabaseError takes a database query result and checks if any error has occurred.

Automatically writes HTTP status code 404 Not Found if the error is a "Not found" error. Calls "Response.Error()" if there is another type of error.

Returns true if there is no error.

func (*Response) Header

func (r *Response) Header() http.Header

Header returns the header map that will be sent.

func (*Response) Hijack

func (r *Response) Hijack() (net.Conn, *bufio.ReadWriter, error)

Hijack implements the Hijacker.Hijack method. For more details, check http.Hijacker.

Returns ErrNotHijackable if the underlying http.ResponseWriter doesn't implement http.Hijacker. This can happen with HTTP/2 connections.

Middleware executed after controller handlers, as well as status handlers, keep working as usual after a connection has been hijacked. Callers should properly set the response status to ensure middleware and status handler execute correctly. Usually, callers of the Hijack method set the HTTP status to http.StatusSwitchingProtocols. If no status is set, the regular behavior will be kept and `204 No Content` will be set as the response status.

func (*Response) Hijacked

func (r *Response) Hijacked() bool

Hijacked returns true if the underlying connection has been successfully hijacked via the Hijack method.

func (*Response) IsEmpty

func (r *Response) IsEmpty() bool

IsEmpty return true if nothing has been written to the response body yet.

func (*Response) IsHeaderWritten

func (r *Response) IsHeaderWritten() bool

IsHeaderWritten return true if the response header has been written. Once the response header is written, you cannot change the response status and headers anymore.

func (*Response) JSON

func (r *Response) JSON(responseCode int, data interface{}) error

JSON write json data as a response. Also sets the "Content-Type" header automatically.

func (*Response) PreWrite

func (r *Response) PreWrite(b []byte)

PreWrite writes the response header after calling PreWrite on the child writer if it implements PreWriter.

func (*Response) Redirect

func (r *Response) Redirect(url string)

Redirect send a permanent redirect response

func (*Response) Render

func (r *Response) Render(responseCode int, templatePath string, data interface{}) error

Render a text template with the given data. The template path is relative to the "resources/template" directory.

func (*Response) RenderHTML

func (r *Response) RenderHTML(responseCode int, templatePath string, data interface{}) error

RenderHTML an HTML template with the given data. The template path is relative to the "resources/template" directory.

func (*Response) SetWriter

func (r *Response) SetWriter(writer io.Writer)

SetWriter set the writer used to write the response. This can be used to chain writers, for example to enable gzip compression, or for logging.

The original http.ResponseWriter is always kept.

func (*Response) Status

func (r *Response) Status(status int)

Status set the response status code. Calling this method a second time will have no effect.

func (*Response) String

func (r *Response) String(responseCode int, message string) error

String write a string as a response

func (*Response) TemporaryRedirect

func (r *Response) TemporaryRedirect(url string)

TemporaryRedirect send a temporary redirect response

func (*Response) Write

func (r *Response) Write(data []byte) (int, error)

Write writes the data as a response. See http.ResponseWriter.Write

func (*Response) WriteHeader

func (r *Response) WriteHeader(status int)

WriteHeader sends an HTTP response header with the provided status code. Prefer using "Status()" method instead. Calling this method a second time will have no effect.

func (*Response) Writer

func (r *Response) Writer() io.Writer

Writer return the current writer used to write the response. Note that the returned writer is not necessarily a http.ResponseWriter, as it can be replaced using SetWriter.

type Route

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

Route stores information for matching and serving.

func GetRoute

func GetRoute(name string) *Route

GetRoute get a named route. Returns nil if the route doesn't exist.

func (*Route) BuildURI

func (r *Route) BuildURI(parameters ...string) string

BuildURI build a full URI pointing to this route. The returned string doesn't include the protocol and domain. (e.g. "/user/login") Panics if the amount of parameters doesn't match the amount of actual parameters for this route.

func (*Route) BuildURL

func (r *Route) BuildURL(parameters ...string) string

BuildURL build a full URL pointing to this route. Panics if the amount of parameters doesn't match the amount of actual parameters for this route.

func (*Route) GetFullURI

func (r *Route) GetFullURI() string

GetFullURI get the full URI of this route.

Note that this URI may contain route parameters in their définition format. Use the request's URI if you want to see the URI as it was requested by the client.

func (*Route) GetFullURIAndParameters

func (r *Route) GetFullURIAndParameters() (string, []string)

GetFullURIAndParameters get the full uri and parameters for this route and all its parent routers.

func (*Route) GetHandler

func (r *Route) GetHandler() Handler

GetHandler returns the Handler associated with this route.

func (*Route) GetMethods

func (r *Route) GetMethods() []string

GetMethods returns the methods the route matches against.

func (*Route) GetName

func (r *Route) GetName() string

GetName get the name of this route.

func (*Route) GetParameters

func (p *Route) GetParameters() []string

GetParameters returns the URI parameters' names (in order of appearance).

func (*Route) GetURI

func (r *Route) GetURI() string

GetURI get the URI of this route. The returned URI is relative to the parent router of this route, it is NOT the full path to this route.

Note that this URI may contain route parameters in their définition format. Use the request's URI if you want to see the URI as it was requested by the client.

func (*Route) GetValidationRules

func (r *Route) GetValidationRules() *validation.Rules

GetValidationRules returns the validation rules associated with this route.

func (*Route) Middleware

func (r *Route) Middleware(middleware ...Middleware) *Route

Middleware register middleware for this route only.

Returns itself.

func (*Route) Name

func (r *Route) Name(name string) *Route

Name set the name of the route. Panics if a route with the same name already exists. Returns itself.

func (*Route) Validate

func (r *Route) Validate(validationRules validation.Ruler) *Route

Validate adds validation rules to this route. If the user-submitted data doesn't pass validation, the user will receive an error and messages explaining what is wrong.

Returns itself.

type Router

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

Router registers routes to be matched and executes a handler.

func NewRouter

func NewRouter() *Router

NewRouter create a new root-level Router that is pre-configured with core middleware (recovery, parse and language), as well as status handlers for all standard HTTP status codes.

You don't need to manually build your router using this function if you are using `goyave.Start()`. This method can however be useful for external tooling that build routers without starting the HTTP server. Don't forget to call router.ClearRegexCache() when you are done registering routes.

func (*Router) CORS

func (r *Router) CORS(options *cors.Options)

CORS set the CORS options for this route group. If the options are not nil, the CORS middleware is automatically added.

func (*Router) ClearRegexCache

func (r *Router) ClearRegexCache()

ClearRegexCache set internal router's regex cache used for route parameters optimisation to nil so it can be garbage collected. You don't need to call this function if you are using `goyave.Start()`. However, this method SHOULD be called by external tooling that build routers without starting the HTTP server when they are done registering routes and subrouters.

func (*Router) Delete

func (r *Router) Delete(uri string, handler Handler) *Route

Delete registers a new route with the DELETE method.

func (*Router) Get

func (r *Router) Get(uri string, handler Handler) *Route

Get registers a new route with the GET and HEAD methods.

func (*Router) GetParameters

func (p *Router) GetParameters() []string

GetParameters returns the URI parameters' names (in order of appearance).

func (*Router) GetRoute

func (r *Router) GetRoute(name string) *Route

GetRoute get a named route. Returns nil if the route doesn't exist.

func (*Router) GetRoutes

func (r *Router) GetRoutes() []*Route

GetRoutes returns the list of routes belonging to this router.

func (*Router) GetSubrouters

func (r *Router) GetSubrouters() []*Router

GetSubrouters returns the list of subrouters belonging to this router.

func (*Router) Group

func (r *Router) Group() *Router

Group create a new sub-router with an empty prefix.

func (*Router) Middleware

func (r *Router) Middleware(middleware ...Middleware)

Middleware apply one or more middleware to the route group.

func (*Router) Options

func (r *Router) Options(uri string, handler Handler) *Route

Options registers a new route wit the OPTIONS method.

func (*Router) Patch

func (r *Router) Patch(uri string, handler Handler) *Route

Patch registers a new route with the PATCH method.

func (*Router) Post

func (r *Router) Post(uri string, handler Handler) *Route

Post registers a new route with the POST method.

func (*Router) Put

func (r *Router) Put(uri string, handler Handler) *Route

Put registers a new route with the PUT method.

func (*Router) Route

func (r *Router) Route(methods string, uri string, handler Handler) *Route

Route register a new route.

Multiple methods can be passed using a pipe-separated string.

"PUT|PATCH"

The validation rules set is optional. If you don't want your route to be validated, pass "nil".

If the route matches the "GET" method, the "HEAD" method is automatically added to the matcher if it's missing.

If the router has CORS options set, the "OPTIONS" method is automatically added to the matcher if it's missing, so it allows preflight requests.

Returns the generated route.

func (*Router) ServeHTTP

func (r *Router) ServeHTTP(w http.ResponseWriter, req *http.Request)

ServeHTTP dispatches the handler registered in the matched route.

func (*Router) Static

func (r *Router) Static(uri string, directory string, download bool, middleware ...Middleware)

Static serve a directory and its subdirectories of static resources. Set the "download" parameter to true if you want the files to be sent as an attachment instead of an inline element.

If no file is given in the url, or if the given file is a directory, the handler will send the "index.html" file if it exists.

func (*Router) StatusHandler

func (r *Router) StatusHandler(handler Handler, status int, additionalStatuses ...int)

StatusHandler set a handler for responses with an empty body. The handler will be automatically executed if the request's life-cycle reaches its end and nothing has been written in the response body.

Multiple status codes can be given. The handler will be executed if one of them matches.

This method can be used to define custom error handlers for example.

Status handlers are inherited as a copy in sub-routers. Modifying a child's status handler will not modify its parent's.

Codes in the 400 and 500 ranges have a default status handler.

func (*Router) Subrouter

func (r *Router) Subrouter(prefix string) *Router

Subrouter create a new sub-router from this router. Use subrouters to create route groups and to apply middleware to multiple routes. CORS options are also inherited.

type TestSuite

type TestSuite struct {
	testify.Suite
	// contains filtered or unexported fields
}

TestSuite is an extension of testify's Suite for Goyave-specific testing.

func (*TestSuite) ClearDatabase

func (s *TestSuite) ClearDatabase()

ClearDatabase delete all records in all tables. This function only clears the tables of registered models.

func (*TestSuite) ClearDatabaseTables

func (s *TestSuite) ClearDatabaseTables()

ClearDatabaseTables drop all tables. This function only clears the tables of registered models.

func (*TestSuite) CreateTestFiles

func (s *TestSuite) CreateTestFiles(paths ...string) []filesystem.File

CreateTestFiles create a slice of "filesystem.File" from the given paths. Files are passed to a temporary http request and parsed as Multipart form, to reproduce the way files are obtained in real scenarios.

func (*TestSuite) CreateTestRequest

func (s *TestSuite) CreateTestRequest(rawRequest *http.Request) *Request

CreateTestRequest create a "goyave.Request" from the given raw request. This function is aimed at making it easier to unit test Requests.

If passed request is "nil", a default GET request to "/" is used.

rawRequest := httptest.NewRequest("GET", "/test-route", nil)
rawRequest.Header.Set("Content-Type", "application/json")
request := goyave.CreateTestRequest(rawRequest)
request.Lang = "en-US"
request.Data = map[string]interface{}{"field": "value"}

func (*TestSuite) CreateTestResponse

func (s *TestSuite) CreateTestResponse(recorder http.ResponseWriter) *Response

CreateTestResponse create an empty response with the given response writer. This function is aimed at making it easier to unit test Responses.

writer := httptest.NewRecorder()
response := suite.CreateTestResponse(writer)
response.Status(http.StatusNoContent)
result := writer.Result()
fmt.Println(result.StatusCode) // 204

func (*TestSuite) CreateTestResponseWithRequest

func (s *TestSuite) CreateTestResponseWithRequest(recorder http.ResponseWriter, rawRequest *http.Request) *Response

CreateTestResponseWithRequest create an empty response with the given response writer HTTP request. This function is aimed at making it easier to unit test Responses needing the raw request's information, such as redirects.

writer := httptest.NewRecorder()
rawRequest := httptest.NewRequest("POST", "/test-route", strings.NewReader("body"))
response := suite.CreateTestResponseWithRequest(writer, rawRequest)
response.Status(http.StatusNoContent)
result := writer.Result()
fmt.Println(result.StatusCode) // 204

func (*TestSuite) Delete

func (s *TestSuite) Delete(route string, headers map[string]string, body io.Reader) (*http.Response, error)

Delete execute a DELETE request on the given route. Headers and body are optional.

func (*TestSuite) Get

func (s *TestSuite) Get(route string, headers map[string]string) (*http.Response, error)

Get execute a GET request on the given route. Headers are optional.

func (*TestSuite) GetBody

func (s *TestSuite) GetBody(response *http.Response) []byte

GetBody read the whole body of a response. If read failed, test fails and return empty byte slice.

func (*TestSuite) GetJSONBody

func (s *TestSuite) GetJSONBody(response *http.Response, data interface{}) error

GetJSONBody read the whole body of a response and decode it as JSON. If read or decode failed, test fails.

func (*TestSuite) Middleware

func (s *TestSuite) Middleware(middleware Middleware, request *Request, procedure Handler) *http.Response

Middleware executes the given middleware and returns the HTTP response. Core middleware (recovery, parsing and language) is not executed.

func (*TestSuite) Patch

func (s *TestSuite) Patch(route string, headers map[string]string, body io.Reader) (*http.Response, error)

Patch execute a PATCH request on the given route. Headers and body are optional.

func (*TestSuite) Post

func (s *TestSuite) Post(route string, headers map[string]string, body io.Reader) (*http.Response, error)

Post execute a POST request on the given route. Headers and body are optional.

func (*TestSuite) Put

func (s *TestSuite) Put(route string, headers map[string]string, body io.Reader) (*http.Response, error)

Put execute a PUT request on the given route. Headers and body are optional.

func (*TestSuite) Request

func (s *TestSuite) Request(method, route string, headers map[string]string, body io.Reader) (*http.Response, error)

Request execute a request on the given route. Headers and body are optional.

func (*TestSuite) RunServer

func (s *TestSuite) RunServer(routeRegistrer func(*Router), procedure func())

RunServer start the application and run the given functional test procedure.

This function is the equivalent of "goyave.Start()". The test fails if the suite's timeout is exceeded. The server automatically shuts down when the function ends. This function is synchronized, that means that the server is properly stopped when the function returns.

func (*TestSuite) SetTimeout

func (s *TestSuite) SetTimeout(timeout time.Duration)

SetTimeout set the timeout for test failure when using RunServer or requests.

func (*TestSuite) Timeout

func (s *TestSuite) Timeout() time.Duration

Timeout get the timeout for test failure when using RunServer or requests.

func (*TestSuite) WriteField

func (s *TestSuite) WriteField(writer *multipart.Writer, fieldName, value string)

WriteField create and write a new multipart form field. The test fails if the field couldn't be written.

func (*TestSuite) WriteFile

func (s *TestSuite) WriteFile(writer *multipart.Writer, path, fieldName, fileName string)

WriteFile write a file to the given writer. This function is handy for file upload testing. The test fails if an error occurred.