A lightweight and powerful Go web framework built on top of Gin, designed for building scalable web applications with MongoDB integration and AWS Lambda support.
- Go 1.21 or later
- MongoDB (for local development)
- AWS SAM CLI (for deployment)
- AWS credentials configured
- Install the Ginboot CLI tool:
go install github.com/klass-lk/ginboot-cli@latest
- Create a new project:
# Create a new project
ginboot new myproject
# Navigate to project directory
cd myproject
# Initialize dependencies
go mod tidy
- Run locally:
go run main.go
Your API will be available at http://localhost:8080/api/v1
To deploy your application to AWS Lambda:
# Build the project for AWS Lambda
ginboot build
# Deploy to AWS
ginboot deploy
On first deployment, you'll be prompted for:
- Stack name (defaults to project name)
- AWS Region
- S3 bucket configuration
These settings will be saved in ginboot-app.yml
for future deployments.
- Database Operations: Built-in support for MongoDB through a generic repository interface, enabling common CRUD operations with minimal code.
- API Request Handling: Simplified API request and authentication context extraction.
- Error Handling: Easily define and manage business errors.
- Password Encoding: Inbuilt password hashing and matching utility for secure authentication.
- CORS Configuration: Flexible CORS setup with both default and custom configurations.
To install GinBoot, add it to your project:
go get github.com/klass-lk/ginboot
GinBoot provides a generic MongoDB repository that simplifies database operations. Here's how to use it:
- Define your document struct:
type User struct {
ID string `bson:"_id" ginboot:"_id"`
Name string `bson:"name"`
}
- Create a repository:
// Create a repository instance
repo := ginboot.NewMongoRepository[User](db, "users")
// Or wrap it in your own repository struct for additional methods
type UserRepository struct {
*ginboot.MongoRepository[User]
}
func NewUserRepository(db *mongo.Database) *UserRepository {
return &UserRepository{
MongoRepository: ginboot.NewMongoRepository[User](db, "users"),
}
}
- Use the repository:
// Create
user := User{ID: "1", Name: "John"}
err := repo.SaveOrUpdate(user)
// Read
user, err := repo.FindById("1")
// Update
user.Name = "John Doe"
err = repo.Update(user)
// Delete
err = repo.Delete("1")
// Find with filters
users, err := repo.FindByFilters(map[string]interface{}{
"name": "John",
})
// Paginated query
response, err := repo.FindAllPaginated(PageRequest{
Page: 1,
Size: 10,
})
The repository provides a comprehensive set of methods for database operations:
- Basic CRUD operations
- Batch operations (SaveAll, FindAllById)
- Filtering and querying
- Pagination support
- Count and existence checks
GinBoot provides a custom Context wrapper around Gin's context that simplifies request handling and authentication. The context provides these key utilities:
// Get authentication context
authContext, err := ctx.GetAuthContext()
// Parse and validate request body
var request YourRequestType
err := ctx.GetRequest(&request)
// Get paginated request parameters
pageRequest := ctx.GetPageRequest()
Here are examples of different handler patterns supported by GinBoot:
// Pattern 1: Context Handler
// Use when you need direct access to context utilities
func (c *Controller) ListApiKeys(ctx *ginboot.Context) (*ApiKeyList, error) {
// Get auth context
authContext, err := ctx.GetAuthContext()
if err != nil {
return nil, err
}
// Get pagination parameters
pageRequest := ctx.GetPageRequest()
return c.service.ListApiKeys(authContext, pageRequest)
}
// Pattern 2: Request Model Handler
// Use when you only need the request body
func (c *Controller) CreateApiKey(request models.CreateApiKeyRequest) (*ApiKey, error) {
// Request is automatically parsed and validated
return c.service.CreateApiKey(request)
}
// Pattern 3: No Input Handler
// Use for simple endpoints that don't need request data
func (c *Controller) GetApiKeyStats() (*ApiKeyStats, error) {
return c.service.GetApiKeyStats()
}
// Register routes
func (c *Controller) Register(group *ginboot.ControllerGroup) {
group.GET("/api-keys", c.ListApiKeys)
group.POST("/api-keys", c.CreateApiKey)
group.GET("/api-keys/stats", c.GetApiKeyStats)
}
The framework will automatically:
- Handle request parsing and validation
- Manage authentication context
- Process pagination parameters
- Convert responses to JSON
- Handle errors appropriately
Define and manage business errors with GinBoot's ApiError type, which allows custom error codes and messages.
var (
TokenExpired = ginboot.ApiError{"TOKEN_EXPIRED", "Token expired"}
SomethingWentWrong = ginboot.ApiError{"SOMETHING_WENT_WRONG", "Something went wrong"}
UserNotFound = ginboot.ApiError{"USER_NOT_FOUND", "User %s not found"}
ConfigAlreadyExists = ginboot.ApiError{"CONFIG_ALREADY_EXISTS", "Config %s already exists"}
ConfigNotFound = ginboot.ApiError{"CONFIG_NOT_FOUND", "Config %s not found"}
TeamNotFound = ginboot.ApiError{"TEAM_NOT_FOUND", "Team %s not found"}
)
type PasswordEncoder interface {
GetPasswordHash(password string) (string, error)
IsMatching(hash, password string) bool
}
GinBoot provides flexible CORS configuration options through the Server struct. You can use either default settings or customize them according to your needs.
For quick setup with sensible defaults:
server := ginboot.New()
server.DefaultCORS() // Allows all origins with common methods and headers
The default configuration:
- Allows all origins
- Allows common methods: GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS
- Allows common headers: Origin, Content-Length, Content-Type, Authorization
- Sets preflight cache to 12 hours
For more control over CORS settings:
server := ginboot.New()
server.CustomCORS(
[]string{"http://localhost:3000", "https://yourdomain.com"}, // Allowed origins
[]string{"GET", "POST", "PUT", "DELETE", "OPTIONS"}, // Allowed methods
[]string{"Origin", "Content-Type", "Authorization", "Accept"}, // Allowed headers
24*time.Hour, // Preflight cache duration
)
You can customize:
- Origins: Specify which domains can access your API
- Methods: Control which HTTP methods are allowed
- Headers: Define which headers can be included in requests
- Max Age: Set how long browsers should cache preflight results
For complete control over CORS settings:
import "github.com/gin-contrib/cors"
server := ginboot.New()
config := cors.Config{
AllowOrigins: []string{"http://localhost:3000"},
AllowMethods: []string{"GET", "POST"},
AllowHeaders: []string{"Origin"},
ExposeHeaders: []string{"Content-Length"},
AllowCredentials: true,
MaxAge: 12 * time.Hour,
}
server.WithCORS(&config)
This gives you access to all CORS configuration options provided by gin-contrib/cors.
GinBoot provides a flexible and intuitive routing system that follows Gin's style while adding powerful controller-based routing capabilities.
You can set a base path for all routes in your application:
server := ginboot.New()
server.SetBasePath("/api/v1") // All routes will be prefixed with /api/v1
Controllers implement the Controller
interface which requires a Register
method:
type Controller interface {
Register(group *ControllerGroup)
}
Example controller implementation:
type UserController struct {
userService *service.UserService
}
func (c *UserController) Register(group *ginboot.ControllerGroup) {
// Public routes
group.GET("", c.ListUsers)
group.GET("/:id", c.GetUser)
// Protected routes
protected := group.Group("", middleware.Auth())
{
protected.POST("", c.CreateUser)
protected.PUT("/:id", c.UpdateUser)
protected.DELETE("/:id", c.DeleteUser)
}
}
Register controllers with their base paths:
// Initialize controllers
userController := NewUserController(userService)
postController := NewPostController(postService)
// Register with paths
server.RegisterController("/users", userController) // -> /api/v1/users
server.RegisterController("/posts", postController) // -> /api/v1/posts
Create route groups with shared middleware:
// Create a group with middleware
adminGroup := server.Group("/admin", middleware.Auth(), middleware.AdminOnly())
{
adminGroup.GET("/stats", adminController.GetStats)
adminGroup.POST("/settings", adminController.UpdateSettings)
}
// Nested groups
apiGroup := server.Group("/api")
v1Group := apiGroup.Group("/v1")
{
v1Group.GET("/health", healthCheck)
}
GinBoot supports all standard HTTP methods:
group.GET("", handler) // GET request
group.POST("", handler) // POST request
group.PUT("", handler) // PUT request
group.DELETE("", handler) // DELETE request
group.PATCH("", handler) // PATCH request
group.OPTIONS("", handler) // OPTIONS request
group.HEAD("", handler) // HEAD request
Add middleware at different levels:
// Server-wide middleware
server.Use(middleware.Logger())
// Group middleware
group := server.Group("/admin", middleware.Auth())
// Route-specific middleware
group.GET("/users", middleware.Cache(), controller.ListUsers)
Use Gin's path parameter syntax:
group.GET("/:id", controller.GetUser) // /users/123
group.GET("/:type/*path", controller.GetFile) // /files/image/avatar.png
func main() {
server := ginboot.New()
// Set base path for all routes
server.SetBasePath("/api/v1")
// Global middleware
server.Use(middleware.Logger())
// Initialize controllers
userController := NewUserController(userService)
postController := NewPostController(postService)
// Register controllers
server.RegisterController("/users", userController)
server.RegisterController("/posts", postController)
// Create admin group
adminGroup := server.Group("/admin", middleware.Auth(), middleware.AdminOnly())
adminController := NewAdminController(adminService)
adminController.Register(adminGroup)
// Start server
server.Start(8080)
}
This setup creates a clean, maintainable API structure with routes like:
- GET /api/v1/users
- POST /api/v1/posts
- GET /api/v1/admin/stats
The routing system combines the simplicity of Gin's routing with the power of controller-based organization, making it easy to structure and maintain your API endpoints.
GinBoot supports multiple handler function signatures for flexibility. You can write your controller methods in any of the following formats:
func (c *Controller) HandleRequest(ctx *ginboot.Context) (Response, error) {
// Access auth context, request body, and other utilities through ctx
authContext, err := ctx.GetAuthContext()
if err != nil {
return nil, err
}
return response, nil
}
func (c *Controller) CreateItem(request models.CreateItemRequest) (Response, error) {
// Request is automatically parsed and validated
// Auth context can be accessed through middleware if needed
return response, nil
}
func (c *Controller) GetStatus() (Response, error) {
// Simple handlers with no input parameters
return response, nil
}
type UserController struct {
service *UserService
}
// Context handler example
func (c *UserController) GetUser(ctx *ginboot.Context) (*User, error) {
authContext, err := ctx.GetAuthContext()
if err != nil {
return nil, err
}
return c.service.GetUser(authContext.UserID)
}
// Request model handler example
func (c *UserController) CreateUser(request models.CreateUserRequest) (*User, error) {
return c.service.CreateUser(request)
}
// No input handler example
func (c *UserController) GetStats() (*Stats, error) {
return c.service.GetStats()
}
func (c *UserController) Register(group *ginboot.ControllerGroup) {
group.GET("/user", c.GetUser)
group.POST("/user", c.CreateUser)
group.GET("/stats", c.GetStats)
}
All handlers must return two values:
- A response value (can be any type)
- An error value
The framework will automatically:
- Parse and validate request bodies
- Handle errors appropriately
- Convert responses to JSON
- Manage HTTP status codes based on errors
GinBoot provides a flexible server configuration that supports both HTTP and AWS Lambda runtimes.
// Create a new server
server := ginboot.New()
// Add your routes
userController := &UserController{}
server.RegisterControllers(userController)
// Start the server on port 8080
err := server.Start(8080)
if err != nil {
log.Fatal(err)
}
// Set LAMBDA_RUNTIME=true environment variable for Lambda mode
server := ginboot.New()
server.RegisterControllers(userController)
server.Start(0) // port is ignored in Lambda mode
GinBoot provides a clean way to organize your routes using controllers.
type UserController struct {
userService *UserService
}
func (c *UserController) Routes() []ginboot.Route {
return []ginboot.Route{
{
Method: "GET",
Path: "/users",
Handler: c.ListUsers,
},
{
Method: "POST",
Path: "/users",
Handler: c.CreateUser,
Middleware: []gin.HandlerFunc{
validateUserMiddleware, // Route-specific middleware
},
},
}
}
server := ginboot.New()
// API group with authentication
apiGroup := ginboot.RouterGroup{
Path: "/api/v1",
Middleware: []gin.HandlerFunc{authMiddleware},
Controllers: []ginboot.Controller{
&UserController{},
&ProductController{},
},
}
// Admin group with additional middleware
adminGroup := ginboot.RouterGroup{
Path: "/admin",
Middleware: []gin.HandlerFunc{authMiddleware, adminMiddleware},
Controllers: []ginboot.Controller{
&AdminController{},
},
}
server.RegisterGroups(apiGroup, adminGroup)
GinBoot provides a generic repository interface for SQL databases.
// Create SQL configuration
config := ginboot.NewSQLConfig().
WithDriver("postgres").
WithDSN("host=localhost port=5432 user=myuser password=mypass dbname=mydb sslmode=disable")
// Connect to database
db, err := config.Connect()
if err != nil {
log.Fatal(err)
}
type User struct {
ID string `db:"id"`
Name string `db:"name"`
Email string `db:"email"`
CreatedAt time.Time `db:"created_at"`
}
type UserRepository struct {
*ginboot.SQLRepository[User]
}
// Create a new repository
userRepo := ginboot.NewSQLRepository[User](db, "users")
// Use the repository
user := User{
Name: "John Doe",
Email: "[email protected]",
}
// Basic CRUD operations
err = userRepo.Save(&user)
users, err := userRepo.FindAll()
user, err := userRepo.FindById("123")
err = userRepo.Delete("123")
// Custom queries
users, err := userRepo.FindByField("email", "[email protected]")
count, err := userRepo.CountByField("name", "John")
GinBoot provides DynamoDB support with a similar interface to other databases.
// Create DynamoDB configuration
config := ginboot.NewDynamoConfig().
WithRegion("us-west-2").
WithCredentials(aws.NewCredentials("access_key", "secret_key"))
// Connect to DynamoDB
db, err := config.Connect()
if err != nil {
log.Fatal(err)
}
type Product struct {
ID string `dynamodbav:"id"`
Name string `dynamodbav:"name"`
Price float64 `dynamodbav:"price"`
CategoryID string `dynamodbav:"category_id"`
}
type ProductRepository struct {
*ginboot.DynamoRepository[Product]
}
// Create a new repository
productRepo := ginboot.NewDynamoRepository[Product](db, "products")
// Use the repository
product := Product{
Name: "Awesome Product",
Price: 99.99,
CategoryID: "electronics",
}
// Basic CRUD operations
err = productRepo.Save(&product)
products, err := productRepo.FindAll()
product, err := productRepo.FindById("123")
err = productRepo.Delete("123")
// Query operations
products, err := productRepo.Query("category_id", "electronics")
count, err := productRepo.CountByField("category_id", "electronics")
// Batch operations
err = productRepo.BatchSave([]Product{product1, product2})
products, err := productRepo.BatchGet([]string{"id1", "id2"})
Contributions are welcome! Please read our contributing guidelines for more details.
This project is licensed under the MIT License. See the LICENSE file for details.