🍐 Elegant Golang REST API Framework

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)

Owner
Goyave
🍐 Elegant Golang REST API Framework
Goyave
Comments
  • CLI Utility

    CLI Utility

    Proposal

    Create a CLI Utility called goyave-cli or gyv to make Goyave development easier. The utility would be an interactive, a bit like gh

    • Creation tools:
      • Create a project
        • The utility would ask a series of questions
        • Name of the project, name of the go module
        • Goyave version (latest by default)
        • Database used (changes the config options)
      • Create controllers
        • Resource controllers (full CRUD template, then fill the blanks)
        • Resource controller creation can take a model as reference to automatically create a request and the handlers
      • Create middleware
      • Create request (validation, may be created alongside resource controllers)
      • Create model (+ its resource controller)
    • Database commands:
      • Run seeders
      • Run migrations (may open to a more advanced migration system)
      • Clear / recreate database
    • Miscellaneous:
      • Detailed routes list
      • Run tests (maybe not needed, as it would duplicate go test)
      • Generate OpenAPI specification #42
      • Open documentation page (goyave.dev)

    Possible drawbacks

    None.

    Additional information

    This issue is a feature proposal and is meant to be discussed. If you have any other idea for this CLI Utility, feel free to suggest it! It is also a good candidate if you want to contribute to the project. This should probably be developed as a side-project.

  • v3 Discussion

    v3 Discussion

    v2.10.1 is expected to be the last release before v3, unless critical issues have to be addressed while developing this new major version.

    v3 will contain a number of breaking changes. To avoid moving to a new major version too often, required breaking changes are pushed back as much as possible and grouped into a single release.

    For the moment, v3 will contain the following changes:

    • Revamped configuration.
      • Maybe using another configuration format such as toml instead of json
      • Improving the way configuration is organized, by letting the possibility of grouping items together (for example grouping all database config into a single category) instead of accumulating a lot of entries at the root-level.
      • Ease plugin development by adding a possibility to add entries to the default config in an init() function
      • Make configuration checked at compile-time by using structures instead of maps.
    • Validation
      • Improve performance by parsing rule sets at startup time instead of during each request.
      • Add the ability to define validation in a more complex way, which will be more verbose, but more flexible and checked at compile-time. This would solve the comma escaping problem in rules parameters.
    • Route parameter converters ( #93 ) may require a breaking change.
    • ~Remove native handlers. I understand that it would make it harder to plug well known go http modules to Goyave and isolate it a bit from the rest of the environment, but this change would be beneficial in many ways:~
      • ~Performance improvements: no need to duplicate the request body~
      • ~Consistency: plugged modules may not work well with Goyave and there is no way to guarantee it. They probably don't follow the same design choices and principles, which can make programs inconsistent and sometimes unreliable.~
    • Convention changes
      • Remove the request package and move rule sets to a requests.go file inside the controller packages. This change would make route definition cleaner and easier, and the directory structure lighter. The requests package naming was inconvenient too.
    • More flexibility for databases. For example, the ability to add another dialect will be added.

    Before the development of v3 starts, please let me know if you think of any breaking or major change that you think would be good for the framework, and feel free to discuss the points above.

  • add rate limit middleware

    add rate limit middleware

    Description

    This pull requests adds a middleware for rate limiting functionality as proposed in Rate limiting #105.

    The current implementation can:

    • Work with authenticated users and add variable limits depending on a user plan
    • Be configured to count rates in relation to a route. This can be done by creating multiple middlewares with different configurations.

    Example

    
    // configure the middleware
    ratelimiterMiddleware := New(func(request *goyave.Request) LimiterConfig {
        return LimiterConfig {
    	RequestQuota:  2,
    	QuotaDuration: 10 * time.Minute,
        }
    })
    
    // apply to a route
    router.Get("/", hello.SayHi).Middleware(ratelimiterMiddleware)
    

    Possible drawbacks

    Please note that this is initial implementation, and I am still working to make it compliant to IETF spec. The headers are not yet implemented.

    Related issue(s)

    List all related issues here:

    Final Thoughts

    As it is my first time contributing a feature, I would love to get your valuable feedback and opinion. Also please feel free to provide better naming suggestions :-)

  • Benchmark framwork

    Benchmark framwork

    Hi all. Before using the framework I usually care about its performance. Have you tried this framework benchmark? You can refer here about benchmark: https://github.com/smallnest/go-web-framework-benchmark

  • OpenAPI generator

    OpenAPI generator

    Proposal

    Develop a module able to generate OpenAPI specification for Goyave applications by reading the main route registrer and validation rule sets.

    This feature could be included in the CLI Utility ( #39 )

    Possible drawbacks

    None.

    Additional information

    This issue is a feature proposal and is meant to be discussed. It is also a good candidate if you want to contribute to the project. This should probably be developed as a side-project.

  • Support JWT custom claims

    Support JWT custom claims

    Proposal

    I propose to support custom claims in the generated JWT token.

    The user fields to be included as claims could be specified by adding auth:"jwtInclude" (to follow current auth prefix).

    This would allow people putting custom information in the token, e.g. when thinking about username, some additional name fields or permissions (I saw you're planning support in the future).

    Possible drawbacks

    By default - none, if someone uses too many fields then the token will be heavy, but that's up to user of the framework.

    Additional information

    I think I can handle this contribution if you agree.

  • A real demo for benchmarks projects and to improve learning

    A real demo for benchmarks projects and to improve learning

    Proposal

    Your work on this project is amazing. I think it's the most accurate docs ever seen for a Go framework.

    But I think it would also be nice to create a "real demo" repo both to learn even better the "best practices" for proper use and to use as a benchmark in the following projects:

    • https://github.com/smallnest/go-web-framework-benchmark and
    • https://www.techempower.com/benchmarks

    And it's better for the author himself to use his framework for those benchmarks rather than someone who doesn't understand 100% how to best use it.

    What do you think about it?

    Thanks again for the wonderful work!

  • Rate limiting

    Rate limiting

    Proposal

    To make rate limiting easier, a middleware could be developed. This middleware would use a new config entry making it possible to adjust the rate easily. The real goal is to make a basic rate limiter middleware, which would cover most cases and be easy to use, mostly to protect from attacks.

    The middleware will have to correctly set the following headers, following this IETF spec:

    • RateLimit-Limit: the rate limit ceiling
    • RateLimit-Remaining: the number of requests left
    • RateLimit-Reset: the number of seconds until the quota resets

    Note: the IETF spec doesn't use the X- header prefix, unlike popular APIs such as Github's. It would be worth making some research to know if it would be better to add the X- prefix or keep it as defined in the IETF spec.

    In case of cancelled request because of limiting, the middleware would be stopping (don't call next()) and would respond with the HTTP status 429 Too Many Requests, in accordance with RFC 6585. The default status handler can be kept and developers will be able to define the behavior of their choice when a request is rate limited.

    Limiting will be done using an in-memory storage directly implemented in the middleware. A simple map and a struct containing the user's information (IP, time frame, remaining requests) are probably more than enough. The storage needs to be thread-safe, as requests can be served in parallel.

    The official go package golang.org/x/time/rate could be used, but it would need to be extended a little bit to support multiple clients. We want to limit IPs that keep requesting the server, but not the other legitimate users.

    I would like to start with a simple rate limiter, as mentioned above, but a more advanced rate limiter could be developed next to it later on. There is no design or clear requirements specification for it yet. For example, this advanced rate limiter could:

    • work with authenticated users and add variable limits depending on a user plan
    • be configured to count rates in relation to a route
    • use an external storage driver instead of the in-memory one explained earlier (support the database for example)
    • check the X-Forwarded-For and X-Real-IP headers if the application is running behind a reverse proxy
    • and more?

    Either way, this middleware has to be compliant with previously mentioned IETF spec.

    Possible drawbacks

    Rate limiting can have a slight impact of performance, and especially on memory usage if using in-memory storage. However, it is a mandatory feature for public APIs and well worth the upfront cost.

    Additional information

    This issue is a feature proposal and is meant to be discussed. It is also a good candidate if you want to contribute to the project.

  • Adding extra data to request

    Adding extra data to request

    Description

    Make a clear and detailed description of your changes, with the added benefits.

    Adding an extra data to Request that would allow middleware to process data that is not part of the request's body Example a new middleware that could wrap extra information to teh Request and later be processed.

    Possible drawbacks

    None

    Related issue(s)

    List all related issues here:

    • closes #117

    Additional information

  • Social Media and Outreach Activities

    Social Media and Outreach Activities

    Description

    Goyave is a very powerful framework but it is not reaching many gophers because you have not been leveraging social media and other platforms to reach developers.

    Possible drawbacks

    Reach more Gophers and more contributors

    Additional information

    I am a beginner in Golang and would love to contribute to this framework by promoting it to gophers.

  • Can't read json raw request

    Can't read json raw request

    Description

    Version: v2.2.0 Environment: Mac Catalina, go1.13.4 Reproduces: always

    Hi,

    I tried to handle raw json request with Request.Data["key"], but got nil, first I sent this as my request on postman body raw json:

    {
        "name": "John Doe",
        "tags": ["tag1", "tag2"]
    }
    

    Then I log print out the result, got this:

    map[]
    

    And I also tried the Request.Integer(), Request.Bool() and some of them got error like this:

    #Case Request.Integer()
    Field \"test\" is not an integer
    
    #Case Request.Bool()
    Field \"test\" is not a bool
    

    And this is log on terminal:

    2020/01/02 12:16:49 Server is running on port: 8090
    2020/01/02 12:16:54 Field "test" is not an integer
    2020/01/02 12:16:54 Field "test" is not an integer
    goroutine 35 [running]: runtime/debug.Stack(0x10e47ec, 0xc0000b2000, 0x2)
        /usr/local/Cellar/go/1.13.4/libexec/src/runtime/debug/stack.go:24 +0x9d
    runtime/debug.PrintStack()
        /usr/local/Cellar/go/1.13.4/libexec/src/runtime/debug/stack.go:16 +0x22
    github.com/System-Glitch/goyave/v2.(*Response).Error(0xc00000e0a0, 0x148e4a0, 0xc000020190, 0xc0000c3748, 0x100dbc6)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/response.go:118 +0x96
    github.com/System-Glitch/goyave/v2.recoveryMiddleware.func1.1(0xc00000e0a0)
        
     /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/middleware.go:25 +0x4b
    panic(0x148e4a0, 0xc000020190)
        /usr/local/Cellar/go/1.13.4/libexec/src/runtime/panic.go:679 +0x1b2
    log.Panicf(0x1549f5c, 0x1c, 0xc0000c3810, 0x1, 0x1)
        /usr/local/Cellar/go/1.13.4/libexec/src/log/log.go:345 +0xc0
    github.com/System-Glitch/goyave/v2.(*Request).Integer(...)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/request.go:127
    karmapala-backend/http/controller/user.Register(0xc00000e0a0, 0xc0002960a0)
        /Users/ridwankustanto/go/src/gitlab.com/ridwankustanto/karmapala-backend/http/controller/user/user.go:24 +0x137
    github.com/System-Glitch/goyave/v2.validateRequestMiddleware.func1(0xc00000e0a0, 0xc0002960a0)
        
    
    /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/middleware.go:106 +0xa5
    karmapala-backend/http/middleware.EnableCORS.func1(0xc00000e0a0, 0xc0002960a0)
        /Users/ridwankustanto/go/src/gitlab.com/ridwankustanto/karmapala-backend/http/middleware/cors.go:26 +0x580
    github.com/System-Glitch/goyave/v2.languageMiddleware.func1(0xc00000e0a0, 0xc0002960a0)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/middleware.go:139 +0xa9
     github.com/System-Glitch/goyave/v2.parseRequestMiddleware.func1(0xc00000e0a0, 0xc0002960a0)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/middleware.go:56 +0x118
    github.com/System-Glitch/goyave/v2.recoveryMiddleware.func1(0xc00000e0a0, 0xc0002960a0)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/middleware.go:29 +0x77
    github.com/System-Glitch/goyave/v2.(*Router).requestHandler(0xc0002545a0, 0x15f2220, 0xc0002c6000, 0xc0002a6200, 0x1561998, 0x0)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/router.go:120 +0x147
    github.com/System-Glitch/goyave/v2.(*Router).Route.func1(0x15f2220, 0xc0002c6000, 0xc0002a6200)
        /Users/ridwankustanto/go/pkg/mod/github.com/!system-!glitch/goyave/[email protected]/router.go:55 +0x5a
    net/http.HandlerFunc.ServeHTTP(0xc0002547e0, 0x15f2220, 0xc0002c6000, 0xc0002a6200)
        /usr/local/Cellar/go/1.13.4/libexec/src/net/http/server.go:2007 +0x44
    github.com/gorilla/mux.(*Router).ServeHTTP(0xc0000d8600, 0x15f2220, 0xc0002c6000, 0xc0002c0000)
        /Users/ridwankustanto/go/pkg/mod/github.com/gorilla/[email protected]/mux.go:212 +0xe2
    net/http.serverHandler.ServeHTTP(0xc000248380, 0x15f2220, 0xc0002c6000, 0xc0002c0000)
        /usr/local/Cellar/go/1.13.4/libexec/src/net/http/server.go:2802 +0xa4
    net/http.(*conn).serve(0xc0002b0000, 0x15f36e0, 0xc0002bc000)
        /usr/local/Cellar/go/1.13.4/libexec/src/net/http/server.go:1890 +0x875
    created by net/http.(*Server).Serve
        /usr/local/Cellar/go/1.13.4/libexec/src/net/http/server.go:2927 +0x38e
    

    Expected result:
    Got value on a specific key.

    Anyway, happy new year everyone!! 🎉🎉🎉

  • Instances

    Instances

    Proposal

    Instead of using globals for the server, the config, the database connection, etc, we could use structures. That way, every component that is currently more or less a "singleton" could have multiple properly separated instances.

    Using config as an example, the config.Load() function would return a *config.Config, a structure that would implement Get(), Set(), etc.

    This would require changes to fundamentals of the framework, such as the Handlers. Instead of receiving *goyave.Request and *goyave.Response, they would receive *goyave.Context, containing the request, the response, but also the config, a DB connection, etc.

    Benefits

    • Easier and parallel testing.
    • More flexible API thanks to using a context for handlers.
    • Multiple servers could run within the same app (not recommended though. Goyave was originally made thinking you would/should only run one REST service from your app).
    • Less mutex locks because some global values such as the database connection would be passed by the context.
    • Cleaner overall than using globals everywhere.

    Drawbacks

    • This would be a huge breaking change and would require a lot of work to upgrade from v3.
    • Some more code would be required in all components. From config.Get() to ctx.Config.Get() for example.
    • Some features would be less accessible from outside handlers, middleware, etc.
    • Will probably require a bit more code in the main function (need to specify config for example).

    Questions

    • Should the language package be rewritten using the same logic? This wouldn't have many benefits compared to the rest.
    • Are all these changes worth it? The main concrete advantage would be parallel testing, but at the cost of some ease of writing the application code.
    • Feel free to discuss and express your opinion.
  • Validation

    Validation "Required if"

    Proposal

    Add a "required_if" validation rule that would take another field as first parameter and check if its value is exactly the value of the second parameter (converted to proper type). If that is true, then the field under validation is required.

    "credit_card_number": {"required_if:payment_type,cc"}
    

    Possible drawbacks

    None.

  • Embedded static resources

    Embedded static resources

    Proposal

    Go's 1.16 new embed feature is great to bundle static resources into the binary, making it easier to distribute it. Currently, the frameworks relies on a non-interfaced filesystem, which makes it a bit harder to use embedded resources. Only the configuration supports it. I would like to provide a solution for developers wanting to embed their files.

    • Create a new handler similar to router.Static(), but taking io/fs.FS as a parameter.
    • Add the ability to use an io/fs.FS to load language files. (This may require a bit of refactoring into the framework so it's possible to load a language file by file)
    • Provide an abstracted version of the helper/filesystem package so it works with io/fs too.
    • Allow Request to use generic file system.

    On top of adding the ability to embed files, this would also let developers use virtual filesystems and fetch files from other sources more easily. However, the io/fs packge is still a bit young in my opinion, and lacks interfaces for writable FS. I wouldn't want to create a temporary non-standard interface that will probably not be compatible with a potential future standard one. So for now, this issue will stay on standby until more development on the io/fs package.

    ~~Because the embed feature is only available in go 1.16+ and that the io.FS interface is required for what I would like to achieve here, we can't build this into the framework without breaking compatibility with prior versions. I don't want to stop supporting older versions (yet), so we could create an external library. Later on, we could consider merging this library into the framework.~~ With v4, I dropped go versions < 1.16.

    Possible drawbacks

    None.

    Additional information

    This issue is a feature proposal and is meant to be discussed. The design is not set yet neither. It is also a good candidate if you want to contribute to the project.

  • JSON Schema Validator

    JSON Schema Validator

    Other than your input validator, consider the JSON Schema validator (jsonschema.org). Although similar, it has the advantage of write-once, run anywhere. The same validation schema could run on the browser before bothering the server. There would be no challenge keeping the two in synchronization: just use the same validator file.

  • Permission system (guards)

    Permission system (guards)

    Proposal

    Guards are an extension of the authentication system using user-defined fields from the authenticator's user model to allow or deny specific actions to an authenticated user.

    For example, a Goyave application will define a guard for forums moderation. A moderator is allowed to modify the posts of other users, but regular users aren't. A guard update-others will be implemented, using the IsModerator field from the user model and the route parameter to check if the user is a moderator or if its own post.

    Guards could be used in two ways:

    • As function calls, so they can be used anywhere such as in the middle of a controller handler
    • In middleware to protect routes directly without cluttering controller handlers.

    This system could support OAuth in some way too.

    The implementation is not defined yet, so feel free to discuss and suggest one.

    Possible drawbacks

    None.

    Additional information

    This issue is a feature proposal and is meant to be discussed. It is also a good candidate if you want to contribute to the project.

REST Layer, Go (golang) REST API framework
REST Layer, Go (golang) REST API framework

REST Layer REST APIs made easy. REST Layer is an API framework heavily inspired by the excellent Python Eve. It helps you create a comprehensive, cust

Dec 16, 2022
Example Golang API backend rest implementation mini project Point Of Sale using Gin Framework and Gorm ORM Database.

Example Golang API backend rest implementation mini project Point Of Sale using Gin Framework and Gorm ORM Database.

Dec 23, 2022
REST api using fiber framework written in golang and using firebase ecosystem to authentication, storage and firestore as a db and use clean architecture as base
REST api using fiber framework written in golang and using firebase ecosystem to authentication, storage and firestore as a db and use clean architecture as base

Backend API Example FiberGo Framework Docs : https://github.com/gofiber Info This application using firebase ecosystem Firebase Auth Cloud Storage Fir

May 31, 2022
REST API boilerplate built with go and clean architecture - Echo Framework

GO Boilerplate Prerequisite Install go-migrate for running migration https://github.com/golang-migrate/migrate App requires 2 database (postgreSQL an

Jan 2, 2023
REST API with Echo Framework from Go
REST API with Echo Framework from Go

REST API with Echo Framework from Go

Nov 16, 2021
REST API made using native Golang libraries. This API resembles the basic working of Instagram.
REST API made using native Golang libraries. This API resembles the basic working of Instagram.

Golang RESTful API for Instagram A Go based REST API built using native libraries. The API has been thoroughly worked through with Postman. Routes inc

Mar 16, 2022
laravel for golang,goal,fullstack framework,api framework
laravel for golang,goal,fullstack framework,api framework

laravel for golang,goal,fullstack framework,api framework

Feb 24, 2022
A REST framework for quickly writing resource based services in Golang.

What is Resoursea? A high productivity web framework for quickly writing resource based services fully implementing the REST architectural style. This

Sep 27, 2022
A small and evil REST framework for Go

go-rest A small and evil REST framework for Go Reflection, Go structs, and JSON marshalling FTW! go get github.com/ungerik/go-rest import "github.com/

Dec 6, 2022
Yet Another REST Framework

YARF: Yet Another REST Framework YARF is a fast micro-framework designed to build REST APIs and web services in a fast and simple way. Designed after

Sep 27, 2022
A rest api with the crud methods made it in golang
A rest api with the crud methods made it in golang

go-API-REST A rest api made it in golang that connects to a mongodb database This API is compatible with the Angular frontend from https://github.com/

Apr 26, 2022
A Simple REST API Build Using Golang

gorestapi A Simple REST API Build Using Golang About gorestapi: a simple music restapi that retrives info about the author, album name, price of it ge

Nov 21, 2021
API REST with Golang. Not directly mine.

REST API: API REST with HTTP Methods like: GET & GET ONE PUT POST DELETE To build a compiled server: CompiledDaemon To develop on a compiled server:

Dec 15, 2021
API Rest em Golang e MongoDB com autenticação com JWT afim de testes e simulaçoes

goRestApi API Rest em Golang e MongoDB com autenticação com JWT afim de testes e simulaçoes Como construir um layout de projeto extensível Autenticaçã

Nov 8, 2021
HTTP Rest API in Golang

Database configuration (for MacOS) Useful article - click to read Install postgres Run cd /Library/PostgreSQL/13/bin Run sudo -u postgres ./createdb r

Dec 14, 2021
Simple REST-API implementation using Golang with several packages (Echo, GORM) and Docker

Simple REST-API Boilerplate This is a simple implementation of REST-API using Golang and several packages (Echo and GORM). By default, I use PostgreSQ

Sep 13, 2022
Kubewrap - kubewrap is an kubernetes command line utility with a focus to explore the kubernetes REST API's leveraging the go libraries available along with golang and cobra packages.

Kubewrap kubewrap is an kubernetes cli wrapper with a focus to explore the kubernetes REST API's leveraging the go libraries available along with gola

Nov 20, 2022
Opinionated Go starter with gin for REST API, logrus for logging, viper for config with added graceful shutdown

go-gin-starter An opinionated starter for Go Backend projects using: gin-gonic/gin as the REST framework logrus for logging viper for configs Docker f

Dec 2, 2022