A Go toolkit for building HTTP services with Gin. Provides configuration management, database access (MySQL/PostgreSQL), JWT authentication, structured logging, middleware, and standardized API responses out of the box.
Looking for a working example? Check out cetus-demo — a complete REST API with user registration, JWT auth, and CRUD, built step by step.
- Configuration - Environment-based config with
.envfiles, singleton pattern - Database - MySQL & PostgreSQL via GORM, auto driver selection
- JWT Authentication - RSA-signed tokens with Redis-backed session management
- Logging - Structured logging with Zap (JSON/console, file/stdout)
- Middleware - Request ID tracking, rate limiting
- API Responses - Standardized JSON response helpers for Gin
- Utilities - Bcrypt hashing, ID obfuscation (Optimus), RSA crypto, i18n, file operations
go get github.com/JackDPro/cetusCopy the example and modify values for your environment:
cp .env.example .envKey environment variables:
APP_NAME=my-appAPP_ENV=devLOG_CONSOLE_OUT=trueLOG_FILE_OUT=falseLOG_LEVEL=debugLOG_FORMAT=jsonDB_TYPE=mysqlDB_HOST=127.0.0.1DB_PORT=3306DB_DATABASE=mydbDB_USERNAME=rootDB_PASSWORD=passwordREDIS_HOST=127.0.0.1REDIS_PORT=6379REDIS_DATABASE=0REDIS_PASSWORD=passwordSERVER_HTTP_PORT=80SERVER_GRPC_PORT=50051See .env.example for all available options.
package main
import (
"fmt""github.com/JackDPro/cetus/config""github.com/JackDPro/cetus/controller""github.com/JackDPro/cetus/middleware""github.com/JackDPro/cetus/provider""github.com/gin-contrib/cors""github.com/gin-gonic/gin"
)
funcmain() {
router:=gin.New()
router.Use(gin.Recovery())
// CORScorsConf:=cors.DefaultConfig()
corsConf.AllowAllOrigins=truecorsConf.AllowHeaders= []string{"Authorization", "Accept-Language"}
router.Use(cors.New(corsConf))
// Request ID middlewarerouter.Use(middleware.RequestId())
// Health check endpointprobeCtr:=controller.NewProbeController()
router.GET("/probe", probeCtr.Show)
// Your routes here// router.GET("/users", userController.Index)conf:=config.GetApiConfig()
addr:=fmt.Sprintf("0.0.0.0:%d", conf.HttpPort)
provider.GetLogger().Info("server starting", "address", addr)
iferr:=router.Run(addr); err!=nil {
panic(err)
}
}All configuration is loaded from environment variables (via .env file). Each config struct uses the singleton pattern.
import"github.com/JackDPro/cetus/config"appConf:=config.GetAppConfig() // App name, env, data root, public URLdbConf:=config.GetDatabaseConfig() // DB type, host, port, credentialsapiConf:=config.GetApiConfig() // HTTP and gRPC portsauthConf:=config.GetAuthConf() // JWT cert/key paths, expirationredisConf:=config.GetRedisConfig() // Redis connection infologConf:=config.GetLogConfig() // Log level, format, output targetshashConf:=config.GetHashIdConfig() // Optimus ID hashing paramsnatsConf:=config.GetNatsConf() // NATS messaging configUse APP_ENV to control behavior:
ifconfig.GetAppConfig().Env=="prod" {
gin.SetMode(gin.ReleaseMode)
}Thread-safe singleton providers for common infrastructure.
Structured logging powered by Zap. Supports console/JSON format, file/stdout output.
import"github.com/JackDPro/cetus/provider"logger:=provider.GetLogger()
logger.Info("server started")
logger.Infow("user created", "userId", 123)
logger.Errorw("request failed", "error", err)GORM wrapper with automatic MySQL/PostgreSQL driver selection based on DB_TYPE.
import"github.com/JackDPro/cetus/provider"db:=provider.GetOrm().Db// Use standard GORM operationsvarusers []Userdb.Where("active = ?", true).Find(&users)
// Auto-migratedb.AutoMigrate(&User{}, &Post{})import"github.com/JackDPro/cetus/provider"rdb:=provider.GetRedisClient()
rdb.Set(ctx, "key", "value", time.Hour)
val, err:=rdb.Get(ctx, "key").Result()import"github.com/JackDPro/cetus/provider"// Hash a passwordhashed, err:=provider.HashMake("my-password")
// Verifyerr=provider.HashCheck("my-password", hashed)
// Check if re-hash is neededifprovider.HashNeedRefresh(hashed) {
// re-hash with current cost
}Reversible integer ID encoding to hide sequential database IDs in APIs.
import"github.com/JackDPro/cetus/provider"encoded:=provider.Hash().Encode(42) // e.g. 1580030173decoded:=provider.Hash().Decode(1580030173) // 42Requires OPTIMUS_PRIME, OPTIMUS_INVERSE, OPTIMUS_RANDOM in .env.
How to get these values:
- Visit http://primes.utm.edu/lists/small/millions/, download any
.txtfile, open it and randomly pick a prime number less than2,147,483,647— this will be yourOPTIMUS_PRIME. - Use the generator in cetus-demo to calculate the other two values:
go run storage/optimus_gen.go 104393867
# Output:# OPTIMUS_PRIME=104393867# OPTIMUS_INVERSE=1990279033# OPTIMUS_RANDOM=1333095938Important: Once deployed to production, never change these values — all existing encoded IDs will become invalid.
import"github.com/JackDPro/cetus/provider"// Load from filersaKey, err:=provider.NewRsaByKeyFile("path/to/private.pem")
// Sign datasignature, err:=rsaKey.Signature(data)
// Verify signatureerr=rsaKey.VerifySignature(data, signature)import"github.com/JackDPro/cetus/provider"provider.RandomString(16) // "aB3kF9mNpQ2xR7wL"provider.RandomInt(6) // "483921"provider.StringInSlice("a", []string{"a", "b"}) // trueimport"github.com/JackDPro/cetus/provider"provider.CopyFile("src.txt", "dst.txt")
provider.CopyDir("src_dir", "dst_dir") // recursive copyimport"github.com/JackDPro/cetus/provider"t:=provider.GetTranslate()
t.SetLanguage("zh")
msg:=t.Tr("hello_world")
// Or use the global shorthandmsg:=provider.Tr("hello_world")Embed BaseModel in your models to get serialization helpers:
import"github.com/JackDPro/cetus/model"typeUserstruct {
model.BaseModelIduint64`json:"id" gorm:"primaryKey"`Nicknamestring`json:"nickname"`Emailstring`json:"email"`
}
// Implement IModel interfacefunc (u*User) ToMap() (map[string]interface{}, error) {
returnu.BaseModel.ToMap(u)
}BaseModel provides:
ToMap(model)- Convert struct to map usingjsontagsToJson(model)- Convert struct to JSON stringInclude(model, relations, db)- Preload GORM relations from a comma-separated string
Any model implementing IModel can be used with the response helpers:
typeIModelinterface {
ToMap() (map[string]interface{}, error)
}// API responses are wrapped in DataWrappertypeDataWrapperstruct {
Datainterface{} `json:"data"`Metainterface{} `json:"meta,omitempty"`
}
// Error responsetypeErrorstruct {
Codeint`json:"code"`Messagestring`json:"message"`Detailstring`json:"detail,omitempty"`
}
// Pagination metadatatypePaginationstruct {
Countint`json:"count"`CurrentPageint`json:"current_page"`PerPageint`json:"per_page"`Totalint`json:"total"`TotalPagesint`json:"total_pages"`
}Standardized HTTP response functions for Gin handlers:
import"github.com/JackDPro/cetus/controller"func (ctr*UserController) Store(c*gin.Context) {
// Validate inputiferr:=c.ShouldBindJSON(&req); err!=nil {
controller.ResponseUnprocessable(c, 1, "invalid params", err)
return
}
// Create resourceuser, err:=createUser(req)
iferr!=nil {
controller.ResponseInternalError(c, 2, "create failed", err)
return
}
// Return created resourcecontroller.ResponseCreated(c, user.Id)
}
func (ctr*UserController) Show(c*gin.Context) {
user, err:=findUser(id)
iferr!=nil {
controller.ResponseNotFound(c, "user not found")
return
}
controller.ResponseItem(c, user) // user must implement IModel
}
func (ctr*UserController) Index(c*gin.Context) {
users, meta:=listUsers(page, perPage)
controller.ResponseCollection(c, users, meta)
}Available response helpers:
| Function | HTTP Status | Use Case |
|---|---|---|
ResponseSuccess(c) | 200 | Action succeeded |
ResponseItem(c, item) | 200 | Return single resource |
ResponseCollection(c, items, meta) | 200 | Return list with optional pagination |
ResponseCreated(c, id) | 201 | Resource created (sets Location header) |
ResponseAccepted(c) | 202 | Async action accepted |
ResponseBadRequest(c, code, msg) | 400 | Invalid request |
ResponseUnauthorized(c) | 401 | Authentication required |
ResponseForbidden(c) | 403 | Permission denied |
ResponseNotFound(c, msg) | 404 | Resource not found |
ResponseUnprocessable(c, code, msg, err) | 422 | Validation failed |
ResponseInternalError(c, code, msg, err) | 500 | Server error (auto-logged) |
RSA-signed JWT tokens with Redis-backed session storage. Supports access tokens and refresh tokens.
Prerequisites:
- RSA key pair (PKCS#8 private key + PEM public certificate)
- Redis server
- JWT config in
.env
JWT_EXPIRES_IN=72JWT_REDIS_PREFIX=authJWT_CERT_PATH=storage/jwt.crtJWT_KEY_PATH=storage/jwt.keyJWT_ISSUE=example.comGenerate RSA keys:
The private key must be in PKCS#8 DER format, the public key in PEM format:
# 1. Generate RSA private key (PKCS#1 PEM)
openssl genrsa -out jwt1.pem 2048
# 2. Convert to PKCS#8 DER format (required by cetus)
openssl pkcs8 -topk8 -inform PEM -outform DER \
-in jwt1.pem -out storage/jwt.key -nocrypt
# 3. Extract public key (PEM)
openssl rsa -in jwt1.pem -pubout -out storage/jwt.crt
# 4. Clean up
rm jwt1.pemUsage:
import"github.com/JackDPro/cetus/jwt"guard, err:=jwt.GetJwtGuard()
// Create access token + refresh tokenaccessToken, err:=guard.CreateToken(userId, true) // true = revoke old tokens// accessToken.AccessToken - JWT string// accessToken.RefreshToken - Refresh JWT string// accessToken.Type - "bearer"// accessToken.ExpiresIn - Expiration in seconds// Create access token only (e.g. for API keys)accessToken, err:=guard.CreateAccessToken(userId)
// Validate tokenvalidToken, err:=guard.Attempt(tokenString)
// validToken.UserId - Authenticated user ID// validToken.Type - "token", "refresh", or "access_key"// Revoke token (logout)err=guard.DeleteCredential(tokenString)Adds a unique request ID to every request. Checks incoming headers first (X-Request-Id, HTTP_X_REQUEST_ID, HTTP_REQUEST_ID), generates a UUID if none found.
import"github.com/JackDPro/cetus/middleware"router.Use(middleware.RequestId())Powered by tollbooth.
import (
"github.com/JackDPro/cetus/middleware""github.com/didip/tollbooth/v7/limiter"
)
lmt:=limiter.New(nil).SetMax(100) // 100 requests per secondrouter.Use(middleware.LimitRate(lmt))Use BaseModel.Include() to dynamically preload relations from query parameters:
// GET /users?include=posts,commentsfunc (ctr*UserController) Index(c*gin.Context) {
includeStr:=c.Query("include")
varusers []Userdb:=provider.GetOrm().Dbdb= (&model.BaseModel{}).Include(&User{}, includeStr, db)
db.Find(&users)
controller.ResponseCollection(c, users, nil)
}A complete REST API setup:
package main
import (
"fmt""github.com/JackDPro/cetus/config""github.com/JackDPro/cetus/controller""github.com/JackDPro/cetus/middleware""github.com/JackDPro/cetus/provider""github.com/gin-contrib/cors""github.com/gin-gonic/gin"
)
funcmain() {
appConf:=config.GetAppConfig()
ifappConf.Env=="prod" {
gin.SetMode(gin.ReleaseMode)
}
router:=gin.New()
router.Use(gin.Recovery())
// CORScorsConf:=cors.DefaultConfig()
corsConf.AllowAllOrigins=truecorsConf.AllowHeaders= []string{"Authorization", "Accept-Language"}
router.Use(cors.New(corsConf))
// Middlewarerouter.Use(middleware.RequestId())
// Public routesprobeCtr:=controller.NewProbeController()
router.GET("/probe", probeCtr.Show)
router.POST("/auth/login", authLogin)
router.POST("/users", createUser)
// Protected routes (add your auth middleware)authorized:=router.Group("/")
// authorized.Use(yourAuthMiddleware())authorized.GET("/users/me", getMe)
authorized.POST("/auth/logout", logout)
// StartapiConf:=config.GetApiConfig()
addr:=fmt.Sprintf("0.0.0.0:%d", apiConf.HttpPort)
provider.GetLogger().Info("server starting", "address", addr)
iferr:=router.Run(addr); err!=nil {
panic(err)
}
}Use bcrypt hashing in GORM model hooks for automatic password encryption:
import (
"github.com/JackDPro/cetus/model""github.com/JackDPro/cetus/provider""gorm.io/gorm"
)
typeUserstruct {
model.BaseModelIduint64`json:"id" gorm:"primaryKey"`Emailstring`json:"email"`Passwordstring`json:"password,omitempty"`
}
func (u*User) BeforeSave(tx*gorm.DB) (errerror) {
ifu.Password!="" {
u.Password, err=provider.HashMake(u.Password)
}
return
}
func (u*User) ToMap() (map[string]interface{}, error) {
returnu.BaseModel.ToMap(u)
}| Variable | Description | Default/Example |
|---|---|---|
APP_NAME | Application name | my-app |
APP_ENV | Environment (dev/prod) | dev |
APP_DATA_ROOT | Data storage root path | /usr/app |
APP_PUBLIC_RES_URL | Public static resource URL | http://example.com/static |
LOG_CONSOLE_OUT | Output logs to console | true |
LOG_FILE_OUT | Output logs to file | false |
LOG_FILE_PATH | Log file path | /var/log/app.log |
LOG_LEVEL | Log level (debug/info/warn/error) | debug |
LOG_FORMAT | Log format (json/console) | json |
DB_TYPE | Database type (mysql/postgres) | mysql |
DB_HOST | Database host | 127.0.0.1 |
DB_PORT | Database port | 3306 |
DB_DATABASE | Database name | mydb |
DB_USERNAME | Database username | root |
DB_PASSWORD | Database password | - |
DB_SSLMODE | PostgreSQL SSL mode | disable |
DB_MIGRATE_SELF_ONLY | Limit migration scope | false |
JWT_EXPIRES_IN | Token expiration (hours) | 72 |
JWT_REDIS_PREFIX | Redis key prefix for tokens | auth |
JWT_CERT_PATH | RSA public key path | storage/jwt.crt |
JWT_KEY_PATH | RSA private key path | storage/jwt.key |
JWT_ISSUE | JWT issuer claim | example.com |
OPTIMUS_PRIME | Optimus prime number | - |
OPTIMUS_INVERSE | Optimus inverse | - |
OPTIMUS_RANDOM | Optimus random seed | - |
REDIS_HOST | Redis host | 127.0.0.1 |
REDIS_PORT | Redis port | 6379 |
REDIS_DATABASE | Redis database index | 0 |
REDIS_PASSWORD | Redis password | - |
NATS_HOST | NATS server host | 127.0.0.1 |
NATS_USERNAME | NATS username | - |
NATS_PASSWORD | NATS password | - |
SERVER_HTTP_PORT | HTTP server port | 80 |
SERVER_GRPC_PORT | gRPC server port | 50051 |
See cetus-demo for a complete working example with user registration, JWT authentication, and CRUD operations.
MIT