api

package module
v0.4.9 Latest Latest
Warning

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

Go to latest
Published: Sep 30, 2025 License: MIT Imports: 18 Imported by: 1

README

gin-api

CORS

import (
	"github.com/litsea/gin-api/cors"
)

r := gin.New()

r.Use(cors.New(
	cors.WithAllowOrigin([]string{"https://foo.com", "https://*.foo.com"}),
))
Config
  • Default:
  • Custom: cors.Option
    • WithAllowMethods()
    • WithAllowHeaders()
    • WithAllowOrigin()
    • WithAllowWildcard()
    • WithAllowCredentials()
    • WithMaxAge()

Error Code

import (
	"github.com/litsea/gin-api/errcode"
)

var (
	ErrFooBar       = errcode.New(100001, "ErrFooBar")
	ErrWithHTTPCode = errcode.New(100002, "ErrWithHTTPCode", http.StatusForbidden)
)
  • Default HTTP code is http.StatusInternalServerError
  • The message(msgID) in errcode.Error will be used for translation
  • There are some built-in error codes, see: errcode.go
  • To avoid confusion, code should not be duplicated
Error message translation

Format: msgID: "translated value"

Example:

ErrFooBar: "Foo bar error"
ErrLongMsg: >-
  long long long
  long long error message

HTTP Response

import (
	"github.com/gin-gonic/gin"
	"github.com/litsea/gin-api/errcode"
	"github.com/litsea/gin-api"
)

// Success
api.Success(ctx, data)

// Error
api.Error(ctx, errcode.ErrXXX)

// Validation Error
r := gin.New()
r.GET("/validation/required", func(ctx *gin.Context) {
	req := &struct {
		Name string `binding:"required" form:"name"`
	}{}
	if err := ctx.ShouldBind(&req); err != nil {
		api.VError(ctx, err, req)

		return
	}

	api.Success(ctx, nil)
})

Logger and Error Wrapping

Logger
import (
	"log/slog"
	"os"

	"github.com/gin-gonic/gin"
	"github.com/litsea/gin-api/log"
)

r := gin.New()

l := log.New(
	slog.New(slog.NewTextHandler(os.Stdout, &slog.HandlerOptions{})),
	log.WithRequestIDHeaderKey("X-Request-ID"),
	log.WithRequestHeader(true),
	log.WithRequestBody(true),
	log.WithUserAgent(true),
	log.WithStackTrace(true),
	log.WithExtraAttrs(map[string]any{ ... }),
)
r.Use(log.Middleware(l))

Default config: log.New()

Logging with gin request context:

l.ErrorRequest(ctx, msgErr, map[string]any{
	"status": httpCode,
	"err":    err,
})

See also:

Error Wrapping

Errors returned to the frontend do not need to be logged repeatedly, use fmt.Errorf() to wrap the error and log the details. When the error type of the outer wrapper is errcode.Error, the front end will only receive the translated message of this error code, and the logger will record all the error contexts.

Example:

Router Login()

err := srv.Login(...)
if err != nil {
	api.Error(ctx, err)

	return
}

Service Login()

err := model.LoginCheck(...)
if err != nil {
	return nil, fmt.Errorf("service.Login: %w, username=%s, %w",
		errcode.ErrLoginCheckFailed, username, err)
}

Model LoginCheck()

err := db.Find(...)
if err != nil {
	return nil, fmt.Errorf("model.LoginCheck: %w", err)
}
  • The frontend will only get the translated error message of errcode.ErrLoginCheckFailed
  • The log message can be service.Login: ErrLoginCheckFailed, username=abc, model.LoginCheck: dial tcp 10.0.0.1:3306: connect: connection refused
Panic Recovery
import (
	"github.com/gin-gonic/gin"
	"github.com/litsea/gin-api"
)

r := gin.New()

r.Use(
	api.Recovery(api.HandleRecovery()),
)

Graceful Shutdown

import (
	"fmt"
	"time"

	"github.com/gin-gonic/gin"
	"github.com/litsea/gin-api/graceful"
	apilog "github.com/litsea/gin-api/log"
	log "github.com/litsea/log-slog"
)

l := apilog.New( ... )
r := gin.New()

g := graceful.New(
	r,
	graceful.WithAddr(
		fmt.Sprintf("%s:%d", "0.0.0.0", 8080),
	),
	graceful.WithReadTimeout(15*time.Second),
	graceful.WithWriteTimeout(15*time.Second),
	graceful.WithLogger(l),
	graceful.WithCleanup(func() {
		log.Info("gracefulRunServer: test cleanup...")
		time.Sleep(5 * time.Second)
	}),
)

g.Run()
// Wait for send event to Sentry when server start failed
time.Sleep(3 * time.Second)

Rate Limit

import (
	"github.com/gin-gonic/gin"
	"github.com/litsea/gin-api/ratelimit"
)

// Max 10 requests in one minute
var ipLimiter = ratelimit.NewLimiter(10, time.Minute)

r := gin.New()

r.GET("/rate-limit", ipLimiter.Middleware(), func(ctx *gin.Context) {
	// ...
})
Rate Limit Response Header
  • X-RateLimit-Limit: Limit requests
  • X-RateLimit-Remaining: Remaining requests
  • X-RateLimit-Reset: Limit reset seconds

Documentation

Index

Constants

This section is empty.

Variables

View Source
var Localize embed.FS

Functions

func CursorPageSuccess

func CursorPageSuccess(ctx *gin.Context, total int64, size int, start, next, items any)

func Error

func Error(ctx *gin.Context, err error)

func HandleHealthCheck added in v0.3.1

func HandleHealthCheck() gin.HandlerFunc

func HandleMethodNotAllowed added in v0.3.1

func HandleMethodNotAllowed() gin.HandlerFunc

func HandleNotFound added in v0.3.1

func HandleNotFound() gin.HandlerFunc

func HandleRecovery added in v0.4.0

func HandleRecovery() gin.RecoveryFunc

func PageSuccess

func PageSuccess(ctx *gin.Context, total int64, size, page int, items any)

func Recovery added in v0.4.0

func Recovery(recovery gin.RecoveryFunc) gin.HandlerFunc

Recovery middleware

func RouteRegisterPprof added in v0.4.2

func RouteRegisterPprof(r *gin.Engine, getTokenFn func() string)

func Success

func Success(ctx *gin.Context, data any)

func VError

func VError(ctx *gin.Context, err error, req any)

VError response validate errors.

Types

type CursorPageResp

type CursorPageResp struct {
	Total int64 `json:"total"`           // Total number of records
	Size  int   `json:"size"`            // Page size
	Start any   `json:"start"`           // Current page cursor start
	Next  any   `json:"next"`            // Next page cursor start
	Items any   `json:"items,omitempty"` // Data list
}

type DetailError

type DetailError struct {
	Code    int    `json:"code,omitempty"`
	Field   string `json:"field,omitempty"`
	Message string `json:"msg"`
	Value   any    `json:"value,omitempty"`
}

type PageResponse

type PageResponse struct {
	Total int64 `json:"total"`           // Total number of records
	Size  int   `json:"size"`            // Page size
	Page  int   `json:"page"`            // Current page number
	Items any   `json:"items,omitempty"` // Data list
}

type Response

type Response struct {
	Code    int           `json:"code"`
	Message string        `json:"msg"`
	Data    any           `json:"data,omitempty"`
	Errors  []DetailError `json:"errors,omitempty"`
	// contains filtered or unexported fields
}

func NewCursorPageResponse

func NewCursorPageResponse(total int64, size int, start, next, items any) *Response

func NewPageResponse

func NewPageResponse(total int64, size, page int, items any) *Response

func NewSuccessResponse

func NewSuccessResponse(data any) *Response

Directories

Path Synopsis

Jump to

Keyboard shortcuts

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