A production-ready REST API boilerplate written in Go with Gin Framework, featuring JWT authentication, GORM ORM, comprehensive middleware support, and interactive Swagger documentation.
- π― Modern Go - Built with Go 1.23
- β‘ Gin Framework - Fast HTTP web framework
- ποΈ Multi-Database Support - SQLite, MySQL, PostgreSQL via GORM
- π JWT Authentication - Secure token-based authentication
- π Comprehensive Logging - Application, database, and access logs
- π Advanced Querying - Built-in filtering, search, and pagination
- π‘οΈ Security Middleware - CORS, rate limiting, and more
- π¦ Gzip Compression - Automatic response compression
- π Hot Reload - Development mode with auto-reload
- π Structured Logging - Using logrus
- ποΈ Clean Architecture - Repository pattern with dependency injection
- π§ͺ Fully Testable - Interface-based design for easy mocking
- π Context Propagation - Proper context handling throughout the stack
- β»οΈ Graceful Shutdown - Proper resource cleanup on exit
- π Swagger/OpenAPI - Interactive API documentation with Swagger UI
- Quick Start
- Project Structure
- Configuration
- API Endpoints
- Database
- Middleware
- Logging
- Development
- Docker Support
- QUICK_START.md - Fast setup guide with common commands
- SWAGGER_GUIDE.md - Complete Swagger/OpenAPI documentation guide
- Go 1.23 or higher
- Git
# Clone the repository
git clone https://github.com/yakuter/ugin.git
cd ugin
# Download dependencies
go mod download
# Build the application
make build
# Run the application
./bin/uginOr use the Makefile for a simpler workflow:
# Build and run in one command
make run
# Or run directly without building (development mode)
make run-devThe server will start at http://127.0.0.1:8081
π Access Swagger UI: http://127.0.0.1:8081/swagger/index.html
ugin/
βββ cmd/ # Application entry points
β βββ ugin/
β βββ main.go # Main entry point (simple!)
βββ internal/ # Private application code
β βββ core/ # Application core
β β βββ app.go # Application lifecycle
β β βββ database.go # Database initialization
β β βββ router.go # Router setup
β βββ domain/ # Domain models (entities)
β β βββ post.go
β β βββ user.go
β β βββ auth.go
β βββ repository/ # Data access layer
β β βββ repository.go # Repository interfaces
β β βββ gormrepo/ # GORM implementations
β β βββ post.go
β β βββ user.go
β βββ service/ # Business logic layer
β β βββ interfaces.go # Service interfaces
β β βββ post.go
β β βββ auth.go
β β βββ post_test.go # Example tests
β βββ handler/ # HTTP handlers
β β βββ http/
β β βββ post.go
β β βββ auth.go
β β βββ admin.go
β β βββ middleware.go
β βββ config/ # Configuration management
β βββ config.go
βββ pkg/ # Public reusable packages
β βββ logger/ # Logging utilities
β βββ logger.go
βββ containers/ # Docker configuration
β βββ composes/ # Docker compose files
β βββ images/ # Dockerfiles
βββ bin/ # Compiled binaries (gitignored)
βββ config.yml # Application configuration
βββ Makefile # Build automation
βββ go.mod # Go module definition
This structure follows the Standard Go Project Layout with Clean Architecture principles.
- Core Layer (
internal/core/) - Application lifecycle and wiring - Domain Layer (
internal/domain/) - Pure business entities - Repository Layer (
internal/repository/) - Data access interfaces and implementations - Service Layer (
internal/service/) - Business logic and use cases - Handler Layer (
internal/handler/) - HTTP request/response handling - Infrastructure (
pkg/,internal/config/) - External concerns
Key Principles:
- β Dependency Injection - No global state
- β Interface-based - Easy to mock and test
- β Context Propagation - Proper timeout and cancellation
- β Clean Separation - Each layer has a single responsibility
- β Simple main.go - Entry point is just 15 lines!
Edit config.yml to configure your application:
database:
driver: "sqlite" # Options: sqlite, mysql, postgres
dbname: "ugin"
username: "user" # Not required for SQLite
password: "password" # Not required for SQLite
host: "localhost" # Not required for SQLite
port: "5432" # Not required for SQLite
logmode: true # Enable SQL query logging
server:
port: "8081"
secret: "mySecretKey" # JWT secret key
accessTokenExpireDuration: 1 # Hours
refreshTokenExpireDuration: 1 # Hours
limitCountPerRequest: 1 # Rate limit per requestSQLite (Default - No setup required):
database:
driver: "sqlite"
dbname: "ugin"
logmode: trueMySQL:
database:
driver: "mysql"
dbname: "ugin"
username: "root"
password: "password"
host: "localhost"
port: "3306"PostgreSQL:
database:
driver: "postgres"
dbname: "ugin"
username: "user"
password: "password"
host: "localhost"
port: "5432"
sslmode: "disable"All API endpoints are versioned with /api/v1 prefix.
Swagger UI is available at: http://localhost:8081/swagger/index.html
- π View all endpoints with detailed documentation
- π§ͺ Test API endpoints directly from browser
- π Test authentication with JWT tokens
- π See request/response examples with real data
See SWAGGER_GUIDE.md for detailed usage.
| Method | Endpoint | Description |
|---|---|---|
| POST | /api/v1/auth/signup |
Register a new user |
| POST | /api/v1/auth/signin |
Sign in and get JWT tokens |
| POST | /api/v1/auth/refresh |
Refresh access token |
| POST | /api/v1/auth/check |
Validate token |
| Method | Endpoint | Description |
|---|---|---|
| GET | /api/v1/posts |
Get all posts (supports pagination) |
| GET | /api/v1/posts/:id |
Get a single post by ID |
| POST | /api/v1/posts |
Create a new post |
| PUT | /api/v1/posts/:id |
Update an existing post |
| DELETE | /api/v1/posts/:id |
Delete a post |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /api/v1/postsjwt |
Get all posts | JWT |
| GET | /api/v1/postsjwt/:id |
Get a single post | JWT |
| POST | /api/v1/postsjwt |
Create a new post | JWT |
| PUT | /api/v1/postsjwt/:id |
Update a post | JWT |
| DELETE | /api/v1/postsjwt/:id |
Delete a post | JWT |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /admin/dashboard |
Admin dashboard | Basic Auth |
Default credentials: username1:password1, username2:password2, username3:password3
All list endpoints support advanced querying:
GET /posts/?Limit=10&Offset=0&Sort=ID&Order=DESC&Search=keyword
| Parameter | Description | Example |
|---|---|---|
Limit |
Number of records to return | Limit=25 |
Offset |
Number of records to skip | Offset=0 |
Sort |
Field to sort by | Sort=ID |
Order |
Sort order (ASC/DESC) | Order=DESC |
Search |
Search keyword | Search=hello |
curl -X POST http://localhost:8081/api/v1/posts \
-H "Content-Type: application/json" \
-d '{
"name": "Hello World",
"description": "This is a sample post",
"tags": [
{
"name": "golang",
"description": "Go programming language"
},
{
"name": "api",
"description": "REST API"
}
]
}'curl "http://localhost:8081/api/v1/posts?Limit=10&Offset=0&Sort=id&Order=DESC"curl -X POST http://localhost:8081/api/v1/auth/signup \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"master_password": "password123"
}'curl -X POST http://localhost:8081/api/v1/auth/signin \
-H "Content-Type: application/json" \
-d '{
"email": "user@example.com",
"master_password": "password123"
}'Response:
{
"access_token": "eyJhbGc...",
"refresh_token": "eyJhbGc...",
"transmission_key": "...",
"access_token_expires_at": "2025-11-01T10:00:00Z",
"refresh_token_expires_at": "2025-11-02T10:00:00Z"
}curl http://localhost:8081/api/v1/postsjwt \
-H "Authorization: Bearer YOUR_ACCESS_TOKEN"curl -X POST http://localhost:8081/api/v1/auth/refresh \
-H "Content-Type: application/json" \
-d '{
"refresh_token": "YOUR_REFRESH_TOKEN"
}'UGin includes example domain models demonstrating relationships:
Post Model (internal/domain/post.go):
type Post struct {
ID uint `json:"id" gorm:"primarykey"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"index"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
Description string `json:"description" gorm:"type:text"`
Tags []Tag `json:"tags,omitempty" gorm:"foreignKey:PostID"`
}Tag Model (internal/domain/post.go):
type Tag struct {
ID uint `json:"id" gorm:"primarykey"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"index"`
PostID uint `json:"post_id" gorm:"index;not null"`
Name string `json:"name" gorm:"type:varchar(255);not null"`
Description string `json:"description" gorm:"type:text"`
}User Model (internal/domain/user.go):
type User struct {
ID uint `json:"id" gorm:"primarykey"`
CreatedAt time.Time `json:"created_at"`
UpdatedAt time.Time `json:"updated_at"`
DeletedAt *time.Time `json:"deleted_at,omitempty" gorm:"index"`
Email string `json:"email" gorm:"uniqueIndex;not null"`
MasterPassword string `json:"-" gorm:"not null"` // Never exposed in JSON
}The application uses the Repository pattern for data access:
// Repository interface (internal/repository/repository.go)
type PostRepository interface {
GetByID(ctx context.Context, id string) (*domain.Post, error)
List(ctx context.Context, filter ListFilter) ([]*domain.Post, *ListResult, error)
Create(ctx context.Context, post *domain.Post) error
Update(ctx context.Context, post *domain.Post) error
Delete(ctx context.Context, id string) error
}
// GORM implementation (internal/repository/gormrepo/post.go)
type postRepository struct {
db *gorm.DB
}Migrations run automatically on application startup in cmd/ugin/main.go:
func autoMigrate(db *gorm.DB) error {
return db.AutoMigrate(
&domain.Post{},
&domain.Tag{},
&domain.User{},
)
}- Logger - Request logging (Gin)
- Recovery - Panic recovery (Gin)
- CORS - Cross-Origin Resource Sharing
- Gzip - Response compression
- Security - Security headers
- Rate Limiting - Request rate limiting (per IP)
- JWT Auth - Token validation
The JWT middleware is applied to protected routes:
// In main.go
postsJWT := v1.Group("/postsjwt")
postsJWT.Use(httpHandler.JWTAuth(authService))
{
postsJWT.GET("", postHandler.List)
// ... other protected routes
}Protected endpoints require an Authorization header:
Authorization: Bearer YOUR_ACCESS_TOKEN
Add custom middleware in internal/handler/http/middleware.go:
func CustomMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
// Your logic here
c.Next()
}
}Then register it in cmd/ugin/main.go:
router.Use(httpHandler.CustomMiddleware())The new architecture makes testing easy with dependency injection and interfaces.
# Run all tests
make test
# Run tests with coverage
make test-coverage
# Run specific package tests
go test -v ./internal/service/...Here's how to test a service with mocked dependencies (internal/service/post_test.go):
func TestPostService_GetByID(t *testing.T) {
// Create mock repository
mockRepo := &mockPostRepository{
getByIDFunc: func(ctx context.Context, id string) (*domain.Post, error) {
return &domain.Post{
ID: 1,
Name: "Test Post",
Description: "Test Description",
}, nil
},
}
// Create service with mock
svc := service.NewPostService(mockRepo, &mockLogger{})
// Test
post, err := svc.GetByID(context.Background(), "1")
if err != nil {
t.Errorf("unexpected error: %v", err)
}
if post.Name != "Test Post" {
t.Errorf("expected 'Test Post', got '%s'", post.Name)
}
}β
Easy to Mock - All dependencies are interfaces
β
Isolated Tests - No global state to manage
β
Fast Tests - No database required for service tests
β
Reliable - Tests don't affect each other
UGin provides three types of logs:
General application events and errors:
INFO 2025-10-31T10:05:53+03:00 Server is starting at 127.0.0.1:8081
ERROR 2025-10-31T10:06:15+03:00 Failed to connect to database
SQL queries and database operations:
2025/10/31 10:05:53 /Users/user/ugin/pkg/database/database.go:80
[0.017ms] [rows:-] SELECT count(*) FROM sqlite_master WHERE type='table'
HTTP request logs:
[GIN] 2025/10/31 - 10:05:53 | 200 | 9.255625ms | 127.0.0.1 | GET "/posts/"
Configure log verbosity using the GIN_MODE environment variable:
# Development mode (verbose)
export GIN_MODE=debug
# Test mode
export GIN_MODE=test
# Production mode (minimal logging)
export GIN_MODE=releaseView all available commands:
make help# Set debug mode
export GIN_MODE=debug
# Run directly (no build step)
make run-dev
# Or run with build
make run# Build for development
make build
# Build for production (optimized, smaller binary)
make build-prod
# The binary will be created in ./bin/ugin# Run all tests
make test
# Run tests with coverage report
make test-coverage
# This generates coverage.html that you can open in a browser# Format code
make fmt
# Run go vet
make vet
# Run linter (requires golangci-lint)
make lint
# Run all checks (format + vet + test)
make check# Download dependencies
make deps
# Update dependencies to latest versions
make deps-update# Remove binaries and log files
make clean# Generate Swagger docs
make swagger
# Generate docs and run
make run-swagger
# View documentation
# Open http://localhost:8081/swagger/index.htmlNote: You need to install swag CLI first:
go install github.com/swaggo/swag/cmd/swag@latestSee SWAGGER_GUIDE.md for detailed Swagger usage.
make build-imageWith MySQL:
# Start application with MySQL
make run-app-mysql
# Stop MySQL containers
make clean-app-mysqlWith PostgreSQL:
# Start application with PostgreSQL
make run-app-postgres
# Stop PostgreSQL containers
make clean-app-postgres# Build image
docker build -t ugin:latest -f containers/images/Dockerfile .
# Run container
docker run -p 8081:8081 -v $(pwd)/config.yml:/app/config.yml ugin:latestCore dependencies:
- gin-gonic/gin - HTTP web framework
- gorm.io/gorm - ORM library
- spf13/viper - Configuration management
- golang-jwt/jwt - JWT implementation
- sirupsen/logrus - Structured logging
- didip/tollbooth - Rate limiting
- swaggo/swag - Swagger documentation
See go.mod for the complete list.
Contributions are welcome! Please feel free to submit a Pull Request.
- Fork the repository
- Create your feature branch (
git checkout -b feature/AmazingFeature) - Commit your changes (
git commit -m 'Add some AmazingFeature') - Push to the branch (
git push origin feature/AmazingFeature) - Open a Pull Request
This project is licensed under the MIT License - see the LICENSE file for details.
- Gin - Amazing HTTP web framework
- GORM - Fantastic ORM library
- Viper - Complete configuration solution
If you have any questions or need help, please open an issue on GitHub.
Made with β€οΈ using Go