gnext

package module
v0.7.0 Latest Latest
Warning

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

Go to latest
Published: Dec 5, 2022 License: MIT Imports: 15 Imported by: 0

README

gNext Web Framework

Go Report Card GoDoc Release

gNext is a Golang API-focused framework extending Gin. Offers the API structuring, automates validation and generates documentation. It's compatible with the existing Gin handlers and Gin middlewares. Designed to simplify and boost development of JSON APIs. You can leave generic and boring stuff to gNext and purely focus on the business logic of your code.

Contents

Installation

You can download gNext and install it in your project by running:

$ go get -u github.com/meteran/gnext

Quick start

This tutorial assumes, that you already have Golang installation and basic knowledge about how to build and run Go programs. If this is your first hit with Go, and you feel you have no idea what is happening here, please read how to get started with Go.

Ok, so let's create a project:

mkdir gnext-example
cd gnext-example
go mod init example.com/gnext
go get github.com/meteran/gnext

Create a file example.go and fill it up with the following code:

package main

import "github.com/meteran/gnext"

func main() {
	r := gnext.Router()

	r.GET("/example", func() string {
		return "Hello World!"
	})

	_ = r.Run()
}

Run it:

go run example

Now you can visit this link in your browser: http://localhost:8080/example

Yes, yes... of course it works, but that's boring... Let's open this page: http://localhost:8080/docs

Whoa, that was amazing, ...but not very useful.

Let's try some real example. With request and response. We can modify our handler to use structures:

package main

import "github.com/meteran/gnext"

func main() {
	r := gnext.Router()

	r.POST("/example", handler)
	_ = r.Run()
}

type MyRequest struct {
	Id   int    `json:"id" binding:"required"`
	Name string `json:"name"`
}

type MyResponse struct {
	Result string `json:"result"`
}

func handler(req *MyRequest) *MyResponse {
	return &MyResponse{Result: req.Name}
}

Restart the server and visit the docs page. You can see that request and response of POST /example endpoint are documented. That's the real power!

The POST request without required id now fails with the validation error:

curl --request POST http://localhost:8080/example --data '{"name": "some name"}'

gives output:

{
  "message": "validation error",
  "details": [
    "field validation for 'id' failed on the 'required' tag with value ''"
  ],
  "success": false
}

the valid request:

curl --request POST http://localhost:8080/example --data '{"name": "some name", "id": 4}'

gives us the expected response:

{
  "result": "some name"
}

Congratulations! Now you are prepared for the fast forwarding development of your great API.

Note: all following sections will base on the program from this overview.

Url parameters

Okay, in the previous section we saw quick use, let's get to specific things 😎

First, we'll start with the parameters in the url.

Using them the standard way is unpleasant, we have to write a piece of code in the handler method just to be able to use it knowing the type safely - not cool.

But... Gnext will do it for us 🥳!

Let's see, I'll add a new endpoint with parameter and add handler method to it:

func main() {
    r := gnext.Router()

    r.POST("/example", handler)
    r.GET("/shops/:name/", getShop)
    _ = r.Run()
}

func getShop(paramName string) *MyResponse {
    return &MyResponse{Result: paramName}
}

Ok, now restart the server and use a new endpoint:

curl -X 'GET' \
  'http://localhost:8080/shops/myownshop/' \
  -H 'accept: application/json'

the response will look like this:

{
  "result": "myownshop"
}

Cool, yeah? Let's take a look at http://localhost:8080/docs, but ... don't be surprised when

you will see a documented endpoint /shops/{name}/ ready to use straight from the Swagger interface 👏

Note: adding new parameters as arguments to the handler methods, keep the order in accordance with the parameters in the url.

Query parameters

Okay, let's move on to a topic with a similar problem as in the previous section - Query parameters.

Exactly the same problem as with url parameters, to use them we have to add a piece of code, but why not use the magic of gNext 🎩?

Let's add some query parameter to our new shop list endpoint/shops/:

func main() {
    r := gnext.Router()

    r.POST("/example", handler)
    r.GET("/shops/", getShopsList)
    r.GET("/shops/:name/", getShop)
    _ = r.Run()
}

type ShopQuery struct {
  gnext.Query
  Search       string    `form:"search"`
}

func getShopsList(q *ShopQuery) *MyResponse {
    return &MyResponse{Result: q.Search}
}

Ok, now restart the server and use a new endpoint:

curl -X 'GET' \
  'http://localhost:8080/shops/?search=wantedshop' \
  -H 'accept: application/json'

the response will look like this:

{
  "result": "wantedshop"
}

As before, in the documentation we find a new endpoint ready to be used by the interface 👷‍♀️

Request payload

Response payload

Status codes

In this section we will show you returning statuses with gNext 🙌.

It's simple, just add gnext.Status to the returned handler parameters

Example:

func getShopsList(q *ShopQuery)(*MyResponse, gnext.Status) {
    return nil, http.StatusNotFound
}

Ok, now restart the server and use endpoint:

curl -X 'GET' \
  'http://localhost:8080/shops/?search=wantedshop' \
  -H 'accept: application/json'

And the response status we will be 404 🦾

Request headers

It happens that we want to do something with the request Headers - nothing difficult.

Look, I will add the headers structure and use it in the handler:

type MyHeaders struct {
  gnext.Headers
  ContentType string `header:"Content-Type,default=application/json"`
}

func getShopsList(q *ShopQuery, h *MyHeaders) (*MyResponse, gnext.Status){
    return &MyResponse{Result: h.ContentType}, http.StatusOK
}

Ok, now restart the server and use endpoint:

curl -X 'GET' \
  'http://localhost:8080/shops/' \
  -H 'accept: application/json'

the response will look like this:

{
  "result": "application/json"
}

It's all simple isn't it? Of course you can enter headers in the Swagger interface 🫡

Response headers

Request context

It may happen that we need to get directly to the request context, just add *gin.Context to the arguments of the handler method.

Let's look at an example:

func getShopsList(c *gin.Context, q *ShopQuery, h *MyHeaders) (*MyResponse, gnext.Status){
    return &MyResponse{Result: c.Request.Method}, http.StatusOK
}

Ok, now restart the server and use endpoint:

curl -X 'GET' \
  'http://localhost:8080/shops/' \
  -H 'accept: application/json'

the response will look like this:

{
  "result": "GET"
}

Endpoint groups

As in the standard GinGonic and many frameworks, we support Endpoint Groups.

We use them not only for structuring, we can also add gnext middleware or gnext error handler to them.

Let's look at a simple example to create a group:

func main() {
  r := gnext.Router()
  
  r.POST("/example", handler)
  r.Group("/shops").
    GET("/", getShopsList).
    GET("/:name/", getShop)
  _ = r.Run()
}

Okay, now we can restart the server using the previously created endpoints in exactly the same way.

Note: using middleware and error handler for the group will be presented in their individual documentation sections.

Middleware

Error handler

Router options

Benchmarks

Advanced documentation

Authors

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func DefaultErrorHandler added in v0.2.0

func DefaultErrorHandler(err error) (status Status, response *DefaultErrorResponse)

Types

type Body

type Body struct{}

func (Body) BodyDocs

func (m Body) BodyDocs()

type BodyInterface

type BodyInterface interface {
	BodyDocs()
}

type DefaultErrorResponse added in v0.2.0

type DefaultErrorResponse struct {
	ErrorResponse `default_status:"500" status_codes:"4XX,5XX"`
	Message       string   `json:"message"`
	Details       []string `json:"details"`
	Success       bool     `json:"success"`
}

type ErrorInterface

type ErrorInterface interface {
	ErrorDocs()
}

type ErrorResponse added in v0.2.0

type ErrorResponse struct{}

func (ErrorResponse) ErrorDocs added in v0.2.0

func (m ErrorResponse) ErrorDocs()

type HandlerPanicked added in v0.2.0

type HandlerPanicked struct {
	Value      interface{}
	StackTrace []byte
}

func (*HandlerPanicked) Error added in v0.2.0

func (e *HandlerPanicked) Error() string

type HandlerWrapper

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

func WrapHandler

func WrapHandler(
	method string,
	path string,
	middlewares middlewares,
	documentation *docs.Docs,
	handler interface{},
	errorHandlers errorHandlers,
	doc ...*docs.Endpoint,
) *HandlerWrapper

type Headers

type Headers http.Header

func (Headers) HeadersDocs

func (m Headers) HeadersDocs()

type HeadersInterface

type HeadersInterface interface {
	HeadersDocs()
}

type IRouter

type IRouter interface {
	IRoutes
	RawRouter() gin.IRouter
	Group(string, ...*docs.Endpoint) IRouter
	OnError(handler interface{}) IRoutes
}

type IRoutes

type IRoutes interface {
	Use(Middleware) IRoutes

	Handle(string, string, interface{}, ...*docs.Endpoint) IRoutes
	Any(string, interface{}, ...*docs.Endpoint) IRoutes
	GET(string, interface{}, ...*docs.Endpoint) IRoutes
	POST(string, interface{}, ...*docs.Endpoint) IRoutes
	DELETE(string, interface{}, ...*docs.Endpoint) IRoutes
	PATCH(string, interface{}, ...*docs.Endpoint) IRoutes
	PUT(string, interface{}, ...*docs.Endpoint) IRoutes
	OPTIONS(string, interface{}, ...*docs.Endpoint) IRoutes
	HEAD(string, interface{}, ...*docs.Endpoint) IRoutes
}

type Middleware

type Middleware struct {
	Before interface{}
	After  interface{}
}

type MiddlewareFactory

type MiddlewareFactory func() Middleware

type NotFound

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

type Query

type Query struct{}

func (Query) QueryDocs

func (m Query) QueryDocs()

type QueryInterface

type QueryInterface interface {
	QueryDocs()
}

type Response

type Response struct{}

func (Response) ResponseDocs

func (m Response) ResponseDocs()

type ResponseInterface

type ResponseInterface interface {
	ResponseDocs()
}

type RootRouter added in v0.2.0

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

RootRouter is a main struct of the module. All other operations are made using this router.

func Router

func Router(docsOptions ...*docs.Options) *RootRouter

Router is a RootRouter constructor. It gets one optional parameter *docs.Options. If passed, all non-empty fields from this struct will be used to initialize the documentation.

func (*RootRouter) Any added in v0.2.0

func (g *RootRouter) Any(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) DELETE added in v0.2.0

func (g *RootRouter) DELETE(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) Engine added in v0.2.0

func (r *RootRouter) Engine() *gin.Engine

Engine returns the raw Gin engine. It can be used to add Gin-native handlers or middlewares.

Note: handlers and middlewares attached directly to the raw engine bypasses the gNext core. Because of that they won't be included in the docs nor validation mechanism.

func (*RootRouter) GET added in v0.2.0

func (g *RootRouter) GET(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) Group added in v0.2.0

func (g *RootRouter) Group(prefix string, _ ...*docs.Endpoint) IRouter

func (*RootRouter) HEAD added in v0.2.0

func (g *RootRouter) HEAD(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) Handle added in v0.2.0

func (g *RootRouter) Handle(method string, path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) OPTIONS added in v0.2.0

func (g *RootRouter) OPTIONS(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) OnError added in v0.2.0

func (g *RootRouter) OnError(errorHandler interface{}) IRoutes

func (*RootRouter) PATCH added in v0.2.0

func (g *RootRouter) PATCH(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) POST added in v0.2.0

func (g *RootRouter) POST(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) PUT added in v0.2.0

func (g *RootRouter) PUT(path string, handler interface{}, doc ...*docs.Endpoint) IRoutes

func (*RootRouter) RawRouter added in v0.2.0

func (g *RootRouter) RawRouter() gin.IRouter

func (*RootRouter) Run added in v0.2.0

func (r *RootRouter) Run(address ...string) error

Run starts the http server. It takes optional address parameters. The number of parameters is meaningful:

  • 0 - defaults to ":8080".
  • 1 - means the given address is either a full address in form 'host:port` or, if doesn't contain ':', a port.
  • 2 - first parameter is a host, the latter one is a port.
  • 3+ - invalid address.

func (*RootRouter) ServeHTTP added in v0.7.0

func (r *RootRouter) ServeHTTP(response http.ResponseWriter, req *http.Request)

func (*RootRouter) Use added in v0.2.0

func (g *RootRouter) Use(middleware Middleware) IRoutes

type Status

type Status int

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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