GoBricks

Enterprise-grade building blocks for Go microservices
GoBricks is an open-source, enterprise-grade Go framework for building robust microservices with modular, reusable components. It provides a complete foundation for developing production-ready applications with built-in support for HTTP servers, AMQP messaging, multi-database connectivity, and clean architecture patterns.
π Features
- ποΈ Modular Architecture: Component-based design with dependency injection
- π HTTP Server: Echo-based server with comprehensive middleware ecosystem
- ποΈ Multi-Database Support: PostgreSQL and Oracle with unified interface
- π¨ AMQP Messaging: RabbitMQ integration with automatic reconnection
- βοΈ Configuration Management: Koanf-based config with multiple sources (YAML, env vars)
- π Built-in Observability: Request tracking, performance metrics, structured logging
- π§ Database Migrations: Flyway integration for schema management
- π§© Plugin System: Easy module registration and lifecycle management
- π Production Ready: Used in enterprise environments
π¦ Installation
go mod init your-service
go get github.com/gaborage/go-bricks@latest
π Quick Start
1. Create Your Application
// cmd/main.go
package main
import (
"log"
"github.com/gaborage/go-bricks/app"
)
func main() {
framework, err := app.New()
if err != nil {
log.Fatal(err)
}
// Register your modules here
if err := framework.Run(); err != nil {
log.Fatal(err)
}
}
2. Create Configuration
# config.yaml
app:
name: "my-service"
version: "v1.0.0"
env: "development"
server:
port: 8080
database:
type: "postgresql"
host: "localhost"
port: 5432
database: "mydb"
username: "postgres"
password: "password"
log:
level: "info"
pretty: true
3. Create a Module
// internal/modules/example/module.go
package example
import (
"github.com/labstack/echo/v4"
"github.com/gaborage/go-bricks/app"
"github.com/gaborage/go-bricks/server"
)
type Module struct {
deps *app.ModuleDeps
}
func (m *Module) Name() string {
return "example"
}
func (m *Module) Init(deps *app.ModuleDeps) error {
m.deps = deps
return nil
}
func (m *Module) RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo) {
server.GET(hr, e, "/hello", m.hello)
}
func (m *Module) Shutdown() error {
return nil
}
// Enhanced handler signature: focuses on business logic
type HelloReq struct {
Name string `query:"name" validate:"required"`
}
type HelloResp struct {
Message string `json:"message"`
}
func (m *Module) hello(req HelloReq, _ server.HandlerContext) (HelloResp, server.IAPIError) {
return HelloResp{Message: "Hello " + req.Name}, nil
}
4. Register and Run
// cmd/main.go (updated)
func main() {
framework, err := app.New()
if err != nil {
log.Fatal(err)
}
// Register modules
framework.RegisterModule(&example.Module{})
if err := framework.Run(); err != nil {
log.Fatal(err)
}
}
ποΈ Architecture
GoBricks follows a modular architecture where each business domain is implemented as a module:
βββββββββββββββββββββββββββββββββββββββββββ
β GoBricks App β
βββββββββββββββββββββββββββββββββββββββββββ€
β Module A β Module B β Module C β
βββββββββββββββββββββββββββββββββββββββββββ€
β HTTP Server β Database β Messaging β
βββββββββββββββββββββββββββββββββββββββββββ€
β Logger β Config β Migrations β Utils β
βββββββββββββββββββββββββββββββββββββββββββ
Module System
Each module implements the Module interface:
type Module interface {
Name() string
Init(deps *ModuleDeps) error
RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo)
Shutdown() error
}
Modules have access to shared dependencies:
type ModuleDeps struct {
DB database.Interface // Database connection
Logger logger.Logger // Structured logger
Messaging messaging.Client // AMQP client
}
### Enhanced Handlers: Result and Envelope
Handlers use a type-safe signature and return either a data type or a Result wrapper to control status codes and headers:
```go
// Create returns 201 Created
func (m *Module) createUser(req CreateReq, _ server.HandlerContext) (server.Result[User], server.IAPIError) {
user := User{ /* ... */ }
return server.Created(user), nil
}
// Update returns 202 Accepted
func (m *Module) updateUser(req UpdateReq, _ server.HandlerContext) (server.Result[User], server.IAPIError) {
updated := User{ /* ... */ }
return server.Accepted(updated), nil
}
// Delete returns 204 No Content (no body)
func (m *Module) deleteUser(req DeleteReq, _ server.HandlerContext) (server.NoContentResult, server.IAPIError) {
return server.NoContent(), nil
}
All responses are wrapped in a standard envelope on success:
{
"data": { /* your payload */ },
"meta": {
"timestamp": "2025-01-12T12:34:56Z",
"traceId": "uuid-or-request-id"
}
}
Errors are returned in a consistent envelope with codes and optional details (details in dev):
{
"error": {
"code": "NOT_FOUND",
"message": "User not found",
"details": { /* dev only */ }
},
"meta": {
"timestamp": "2025-01-12T12:34:56Z",
"traceId": "uuid-or-request-id"
}
}
Migration: echo.Context to Enhanced Handlers
Minimal steps to migrate an endpoint from raw Echo to the enhanced pattern:
- Change Module interface and route registration
Before:
func (m *Module) RegisterRoutes(e *echo.Echo) error {
e.GET("/users/:id", m.getUser)
return nil
}
func (m *Module) getUser(c echo.Context) error {
id := c.Param("id")
// ...parse, validate, fetch
return c.JSON(http.StatusOK, user)
}
After:
type GetUserReq struct {
ID int `param:"id" validate:"min=1"`
}
type UserResp struct { /* fields */ }
func (m *Module) RegisterRoutes(hr *server.HandlerRegistry, e *echo.Echo) {
server.GET(hr, e, "/users/:id", m.getUser)
}
func (m *Module) getUser(req GetUserReq, _ server.HandlerContext) (UserResp, server.IAPIError) {
// ...business logic only
// on missing: return UserResp{}, server.NewNotFoundError("User")
return UserResp{/* ... */}, nil
}
- Use struct tags for binding and validation
param:"id", query:"q", header:"Authorization", and JSON body tags.
- Add
validate:"..." using validator/v10 rules.
- Set custom statuses with Result helpers when needed
- 201:
return server.Created(resp), nil
- 202:
return server.Accepted(resp), nil
- 204:
return server.NoContent(), nil (no body)
Notes
- The framework auto-wraps responses into
{ data, meta } and errors into { error, meta }.
- Existing Echo middleware remains compatible; tracing uses
X-Request-ID (generated if missing).
Common Pitfalls
- Ensure Echoβs validator is set. Using
app.New() wires server.NewValidator() automatically. If you build Echo manually, set e.Validator = server.NewValidator().
- 204 responses must not include a body. Use
server.NoContent().
- To set headers (e.g.,
Location on 201), return a server.Result[T]{ Data: t, Status: http.StatusCreated, Headers: http.Header{"Location": {"/users/123"}} }.
- Binding precedence: JSON body β path params β query params β headers. Later sources overwrite earlier values.
[]string binding: use repeated query params (?names=a&names=b) or comma-separated headers (X-Items: a, b).
- Error details appear only in development; production hides internal details for 5xx.
- Handlers get
server.HandlerContext with optional Echo access for advanced cases; most handlers wonβt need it.
Old β New Mapping (quick reference)
c.Param("id") β ID int \\param:"id"\`` in request struct
c.QueryParam("q") β Q string \\query:"q"\``
- Repeated query values β
Tags []string \\query:"tags"\`` with ?tags=a&tags=b
- Header read β
Auth string \\header:"Authorization"\``
- Manual bind/validate β Framework binds and calls
c.Validate() for you (add validate:"..." tags)
return c.JSON(200, data) β return data, nil
return c.JSON(201, data) β return server.Created(data), nil
return c.NoContent(204) β return server.NoContent(), nil
- Errors:
echo.NewHTTPError(404) β server.NewNotFoundError("Resource"); 400 β server.NewBadRequestError("..."); 409 β server.NewConflictError("...")
## ποΈ Database Support
GoBricks supports multiple databases through a unified interface:
### PostgreSQL
```yaml
database:
type: "postgresql"
host: "localhost"
port: 5432
database: "myapp"
username: "postgres"
password: "password"
ssl_mode: "disable"
Oracle
database:
type: "oracle"
host: "localhost"
port: 1521
service_name: "ORCL"
username: "system"
password: "password"
Oracle Tips
- Reserved identifiers: Oracle reserves many keywords like NUMBER. If your table has a column named number, you must quote it in SQL (e.g., "NUMBER" or "number", matching how it was created). GoBricks' query builder helps with this.
- Placeholders: Oracle uses
:1, :2, ... (or named binds like :id), not $1.
Best practice: avoid reserved identifiers in schema. Prefer descriptive names like account_number instead of number. If you need to rename:
-- Use the exact quoted identifier if it was created quoted
ALTER TABLE accounts RENAME COLUMN "NUMBER" TO account_number;
Example using the query builder to safely insert into a table with a reserved column:
import (
brdb "github.com/gaborage/go-bricks/database"
"github.com/Masterminds/squirrel"
)
qb := brdb.NewQueryBuilder(brdb.Oracle)
// Will quote the reserved column name on Oracle automatically
insert := qb.
InsertWithColumns("accounts", "id", "name", "number", "balance", "created_at").
Values(1, "John", "12345", 100.50, squirrel.Expr(qb.BuildCurrentTimestamp()))
sql, args, _ := insert.ToSql()
// sql: INSERT INTO accounts (id,name,"number",balance,created_at) VALUES (:1,:2,:3,:4,:5)
// args: [1 "John" "12345" 100.5]
π¨ Messaging
AMQP messaging with RabbitMQ:
messaging:
broker_url: "amqp://guest:guest@localhost:5672/"
exchange: "my-service"
routing_key: "events"
virtual_host: "/"
βοΈ Configuration
Configuration management with multiple sources (priority order):
- Environment Variables (highest priority)
- Environment-specific YAML (
config.production.yaml)
- Base YAML (
config.yaml)
- Default Values (lowest priority)
Environment Variables
export APP_NAME="my-service"
export DATABASE_HOST="prod-db.company.com"
export DATABASE_PASSWORD="secure_password"
export LOG_LEVEL="info"
π Observability
Structured Logging
deps.Logger.Info().
Str("user_id", userID).
Int("count", items).
Msg("Processing user items")
Request Tracking
Automatic tracking of:
- HTTP request/response metrics
- Database query performance
- AMQP message statistics
- Request correlation IDs
Health Checks
Built-in endpoints:
/health - Basic health check
/ready - Readiness probe with database connectivity
π Production Features
- Graceful Shutdown: Coordinated shutdown of all components
- Connection Pooling: Optimized database connection management
- Rate Limiting: Built-in request rate limiting
- CORS Support: Configurable cross-origin resource sharing
- Request Validation: Automatic request validation with go-playground/validator
- Security Headers: Essential security headers included
- Recovery Middleware: Panic recovery with logging
π Documentation
π οΈ Examples
See the examples/ directory for complete examples:
π€ Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
π License
This project is licensed under the MIT License - see the LICENSE file for details.
π Acknowledgments
GoBricks was extracted from production enterprise applications and battle-tested in real-world microservice architectures.
Built with β€οΈ for the Go community
π HTTP Client
GoBricks includes a small HTTP client with a fluent builder, request/response interceptors, default headers and basic auth, plus a retry mechanism with exponential backoff and jitter.
- Create a client with defaults:
package main
import (
"context"
brhttp "github.com/gaborage/go-bricks/http"
"github.com/gaborage/go-bricks/logger"
)
func main() {
log := logger.New("info", false)
client := brhttp.NewClient(log)
ctx := context.Background()
resp, err := client.Get(ctx, &brhttp.Request{URL: "https://example.com"})
if err != nil {
log.Error().Err(err).Msg("request failed")
return
}
log.Info().Int("status", resp.StatusCode).Msg("ok")
}
- Configure via builder, including retries and interceptors:
package main
import (
"context"
nethttp "net/http"
"time"
brhttp "github.com/gaborage/go-bricks/http"
"github.com/gaborage/go-bricks/logger"
)
func main() {
log := logger.New("info", false)
client := brhttp.NewBuilder(log).
WithTimeout(5 * time.Second).
WithDefaultHeader("Accept", "application/json").
WithBasicAuth("user", "pass").
WithRequestInterceptor(func(ctx context.Context, r *nethttp.Request) error {
r.Header.Set("X-Correlation-ID", "demo-123")
return nil
}).
WithResponseInterceptor(func(ctx context.Context, r *nethttp.Request, resp *nethttp.Response) error {
if resp.StatusCode >= 500 {
log.Warn().Int("status", resp.StatusCode).Msg("server error")
}
return nil
}).
// Retries: exponential backoff with full jitter
// delay per attempt = baseDelay * 2^attempt, jittered in [0, delay), capped at 30s
WithRetries(3, 200*time.Millisecond).
Build()
ctx := context.Background()
req := &brhttp.Request{URL: "https://example.com/api"}
resp, err := client.Get(ctx, req)
if err != nil {
log.Error().Err(err).Msg("request failed")
return
}
log.Info().Int("status", resp.StatusCode).Msg("ok")
}
Retry/backoff details:
- Retries on: transport/network errors, timeouts, and 5xx responses.
- No retries on 4xx.
- Backoff: exponential (base Γ 2^attempt), full jitter, max 30s.
- Interceptor errors are not retried and are returned immediately.