Forge is a comprehensive Go utility library for building production-ready backend applications on Google Cloud Platform. It provides battle-tested abstractions for databases, logging, storage, event handling, authorization, and more.
go get github.com/axen-software/forge- Go 1.21+
- GCP Project (for cloud services)
| Module | Description |
|---|---|
db/sql |
PostgreSQL connection with pgxpool |
db/nosql |
MongoDB client wrapper |
logger |
Zap-based structured logging |
glog |
GCP Cloud Logging integration |
bigquery |
GCP BigQuery client |
storage |
GCS file storage |
firebase |
Firebase Admin SDK |
ctasks |
GCP Cloud Tasks |
event |
CloudEvents-based event bus |
casbin |
RBAC/ABAC authorization |
setting |
Environment configuration |
validator |
Struct validation |
import (
"github.com/axen-software/forge/db"
"github.com/axen-software/forge/db/sql"
)
func main() {
// Create configuration
cfg := &db.Config{
Type: db.SQL,
Driver: sql.Postgres,
Host: "localhost",
Port: "5432",
User: "postgres",
Password: "password",
Database: "mydb",
SSLMode: "disable",
Debug: true,
}
// Or use DSN directly
cfg := &db.Config{
Type: db.SQL,
Driver: sql.Postgres,
DSN: "postgres://user:pass@localhost:5432/mydb?sslmode=disable",
}
// Create connection
database, err := sql.New(cfg)
if err != nil {
log.Fatal(err)
}
defer database.Close()
// Use with standard database/sql
rows, err := database.DB().Query("SELECT * FROM users")
// Or use the pgxpool directly for better performance
pool := database.Pool()
// Check if debug mode for Ent ORM
if database.IsDebug() {
client = client.Debug()
}
}import (
"github.com/axen-software/forge/db"
"github.com/axen-software/forge/db/nosql"
)
func main() {
cfg := &db.Config{
Type: db.NoSQL,
Host: "localhost",
Port: "27017",
User: "admin",
Password: "password",
Database: "mydb",
}
// Or use DSN
cfg := &db.Config{
Type: db.NoSQL,
DSN: "mongodb://admin:password@localhost:27017/mydb",
}
database, err := nosql.New(cfg)
if err != nil {
log.Fatal(err)
}
defer database.Close(ctx)
// Get default database
db := database.GetDefaultDatabase()
// Query collection
collection := db.Collection("users")
cursor, err := collection.Find(ctx, bson.M{})
}Zap-based structured logging with environment-aware formatting.
import (
"github.com/axen-software/forge/logger"
zaplogger "github.com/axen-software/forge/logger/zap"
"github.com/axen-software/forge/setting"
)
func main() {
cfg := &logger.Config{
LogLevel: "info", // debug, info, warn, error
CallerEnabled: true,
}
log := zaplogger.NewZapLogger(cfg, setting.Production)
log.WithName("my-service")
// Basic logging
log.Info("Server started")
log.Infof("Listening on port %d", 8080)
log.Error("Something went wrong")
// Structured logging with fields
log.Infow("User logged in", logger.Fields{
"user_id": "123",
"ip": "192.168.1.1",
"duration": "50ms",
})
// Error logging
log.Err("Database error", err)
// Flush before shutdown
log.Sync()
}import "github.com/axen-software/forge/logger/l"
// Use anywhere without initialization
l.Info("Quick log message")
l.Errorf("Error: %v", err)Direct integration with GCP Cloud Logging for production environments.
import "github.com/axen-software/forge/glog"
func main() {
logger, err := glog.New(ctx, &glog.Config{
ProjectID: "my-gcp-project",
LogName: "my-service",
DefaultLabels: map[string]string{
"env": "production",
"version": "1.0.0",
},
})
if err != nil {
log.Fatal(err)
}
defer logger.Close()
// Simple logging
logger.LogInfo(ctx, "Application started")
logger.LogWarning(ctx, "High memory usage detected")
logger.LogError(ctx, "Failed to process request")
// With custom labels
logger.LogInfo(ctx, "User action", map[string]string{
"user_id": "123",
"action": "login",
})
// Structured payload
logger.Log(ctx, glog.Entry{
Severity: glog.Info,
Payload: map[string]any{
"event": "order_created",
"orderId": "ORD-123",
"amount": 99.99,
},
})
// With distributed tracing
logger.LogWithTrace(ctx, glog.Entry{
Severity: glog.Info,
Payload: "Processing request",
}, traceID, spanID)
// HTTP request logging
logger.Log(ctx, glog.Entry{
Severity: glog.Info,
Payload: "Request completed",
HTTPRequest: &glog.HTTPRequest{
Request: req,
Status: 200,
Latency: 50 * time.Millisecond,
RemoteIP: "192.168.1.1",
},
})
}import (
"github.com/axen-software/forge/bigquery"
bq "cloud.google.com/go/bigquery"
)
func main() {
client, err := bigquery.New(ctx, &bigquery.Config{
ProjectID: "my-gcp-project",
DefaultDataset: "analytics",
Location: "US",
QueryTimeout: 60 * time.Second,
})
if err != nil {
log.Fatal(err)
}
defer client.Close()
// Execute query
result, err := client.Query(ctx, `
SELECT user_id, COUNT(*) as event_count
FROM events
GROUP BY user_id
LIMIT 100
`)
for _, row := range result.Rows {
fmt.Printf("User: %v, Events: %v\n", row["user_id"], row["event_count"])
}
// Parameterized query (prevents SQL injection)
result, err := client.QueryWithParams(ctx,
"SELECT * FROM users WHERE email = @email",
[]bq.QueryParameter{
{Name: "email", Value: "user@example.com"},
},
)
// Dry run for cost estimation
dryRun, err := client.DryRun(ctx, "SELECT * FROM large_table")
fmt.Printf("Query will process: %d bytes\n", dryRun.TotalBytesProcessed)
// Streaming insert
type Event struct {
UserID string `bigquery:"user_id"`
EventType string `bigquery:"event_type"`
Timestamp time.Time `bigquery:"timestamp"`
}
event := Event{
UserID: "user-123",
EventType: "page_view",
Timestamp: time.Now(),
}
err = client.Insert(ctx, "analytics", "events", event)
// Batch insert
events := []any{event1, event2, event3}
err = client.InsertBatch(ctx, "analytics", "events", events)
// Table management
exists, _ := client.TableExists(ctx, "analytics", "events")
if !exists {
schema := bq.Schema{
{Name: "user_id", Type: bq.StringFieldType},
{Name: "event_type", Type: bq.StringFieldType},
{Name: "timestamp", Type: bq.TimestampFieldType},
}
client.CreateTable(ctx, "analytics", "events", schema,
bigquery.WithTableDescription("User events table"),
bigquery.WithTimePartitioning("timestamp", 90*24*time.Hour),
)
}
}import (
"github.com/axen-software/forge/storage"
"github.com/axen-software/forge/storage/config"
)
func main() {
client, err := storage.NewStorage(ctx, &config.Config{
Enable: true,
Provider: "gcs",
ProjectID: "my-gcp-project",
PublicBucket: "my-public-bucket",
PrivateBucket: "my-private-bucket",
})
if err != nil {
log.Fatal(err)
}
// Upload file
data := []byte("Hello, World!")
err = client.UploadFromData(ctx, "path/to/file.txt", data, false) // false = public bucket
// Download file
content, err := client.GetFileData(ctx, "path/to/file.txt", false)
// Get file URL
url, err := client.GetFileUrl(ctx, "path/to/file.txt", false)
// Delete file
err = client.DeleteFile(ctx, "path/to/file.txt", false)
// Copy/Move files
client.CopyFile(ctx, "src/file.txt", "dst/file.txt", false, true, "", "") // public to private
client.MoveFile(ctx, "old/path.txt", "new/path.txt", false, false, "", "")
// Generate presigned URLs
urlHandler, _ := storage.NewStorageURLHandler(ctx, cfg)
uploadURL, _ := urlHandler.GeneratePutPresignedURLPrivateBucket("uploads", "file.pdf", 15*time.Minute)
downloadURL, _ := urlHandler.GenerateGetPresignedURLPrivateBucket("files", "document.pdf", 1*time.Hour)
}import "github.com/axen-software/forge/firebase"
func main() {
cfg := &firebase.Config{
ProjectID: "my-firebase-project",
}
// Firestore
firestore, err := firebase.NewFirestoreClient(ctx, cfg)
if err != nil {
log.Fatal(err)
}
defer firestore.Close()
// Use embedded firestore.Client
doc, err := firestore.Collection("users").Doc("user-123").Get(ctx)
// Firebase Auth
auth, err := firebase.NewAuthClient(ctx, cfg)
user, err := auth.GetUser(ctx, "user-uid")
// Realtime Database
database, err := firebase.NewDatabaseClient(ctx, cfg)
}import "github.com/axen-software/forge/ctasks"
func main() {
client, err := ctasks.New(ctx, &ctasks.Config{
ProjectID: "my-gcp-project",
Location: "us-central1",
DefaultQueue: "default-queue",
DefaultUrl: "https://my-service.run.app/tasks",
})
if err != nil {
log.Fatal(err)
}
defer client.Close(ctx)
// Create a task with payload
payload := []byte(`{"user_id": "123", "action": "send_email"}`)
headers := map[string]string{"Content-Type": "application/json"}
task, err := client.CreateTask(ctx,
"email-queue",
"https://my-service.run.app/send-email",
headers,
payload,
time.Now().Add(5*time.Minute), // schedule time
)
// Create task with options (more flexible)
task, err := client.CreateTaskWithOptions(ctx, "email-queue",
ctasks.WithHttpRequest(
ctasks.POST,
"https://my-service.run.app/send-email",
headers,
payload,
),
ctasks.WithScheduleTime(time.Now().Add(1*time.Hour)),
ctasks.WithAuth("service-account@project.iam.gserviceaccount.com", "https://my-service.run.app"),
ctasks.WithAutoRevive(), // for tasks scheduled >28 days
)
// Delete task
err = client.DeleteTask(ctx, "email-queue", taskID)
// Use middleware for Cloud Tasks trigger endpoints
e := echo.New()
e.POST("/tasks", handler, client.TriggerHandlerMiddleware)
}CloudEvents-based event publishing and subscribing.
import (
"github.com/axen-software/forge/event"
"github.com/axen-software/forge/event/eventbus"
cloudevents "github.com/cloudevents/sdk-go/v2"
)
// Define event handler
type UserCreatedHandler struct{}
func (h *UserCreatedHandler) EventType() string {
return "user.created"
}
func (h *UserCreatedHandler) Handle(ctx context.Context, e cloudevents.Event) error {
var userData map[string]any
e.DataAs(&userData)
fmt.Printf("User created: %v\n", userData)
return nil
}
func main() {
bus := eventbus.NewEventBus()
// Register handlers
bus.RegisterHandler(&UserCreatedHandler{})
// Start subscribing
bus.Subscribe(ctx)
// Publish event
e := cloudevents.NewEvent()
e.SetType("user.created")
e.SetSource("my-service")
e.SetData(cloudevents.ApplicationJSON, map[string]any{
"user_id": "123",
"email": "user@example.com",
})
bus.Publish(ctx, e)
}import "github.com/axen-software/forge/event"
func main() {
publisher, err := event.NewPublisher(ctx, &event.Config{
ProjectID: "my-gcp-project",
TopicID: "events",
})
if err != nil {
log.Fatal(err)
}
e := cloudevents.NewEvent()
e.SetType("order.created")
e.SetData(cloudevents.ApplicationJSON, orderData)
if err := publisher.Publish(ctx, e); err != nil {
log.Error("Failed to publish event", err)
}
}RBAC/ABAC authorization with database-backed policies.
import "github.com/axen-software/forge/casbin"
func main() {
// Create enforcer with SQL adapter
enforcer, err := casbin.NewEnforcer(
"path/to/model.conf", // Casbin model file
db.DB(), // *sql.DB connection
"postgres", // driver name
"casbin_policies", // policy table name
logger,
)
if err != nil {
log.Fatal(err)
}
// Verify access
hasAccess, err := enforcer.VerifyAccess(ctx, "user:123", "/api/users", "GET")
if !hasAccess {
return echo.ErrForbidden
}
// Add policy (using embedded Casbin enforcer)
enforcer.AddPolicy("user:123", "/api/admin", "GET")
enforcer.SavePolicy()
// Health check
if err := enforcer.Health(); err != nil {
log.Error("Policy store unhealthy")
}
}import "github.com/axen-software/forge/setting"
func main() {
env := setting.Production // or setting.Development, setting.Staging, setting.Local
if env.IsProduction() {
// Production-specific config
}
if env.IsLocal() || env.IsDevelopment() {
// Enable debug features
}
name := env.GetEnvironmentName() // "production"
}import "github.com/axen-software/forge/validator"
type CreateUserRequest struct {
Email string `json:"email" validate:"required,email"`
Name string `json:"name" validate:"required,min=2,max=100"`
Age int `json:"age" validate:"gte=0,lte=150"`
Password string `json:"password" validate:"required,min=8"`
}
func main() {
req := CreateUserRequest{
Email: "invalid-email",
Name: "A",
Age: 200,
Password: "short",
}
if err := validator.Validate(req); err != nil {
// Returns validation errors
fmt.Println(err)
}
}All modules follow a consistent configuration pattern using mapstructure and env tags:
type Config struct {
Enable bool `mapstructure:"enable" env:"SERVICE_ENABLE"`
ProjectID string `mapstructure:"projectId" env:"SERVICE_PROJECT_ID"`
// ...
}Load with Viper:
import "github.com/spf13/viper"
var cfg Config
viper.Unmarshal(&cfg)Or with environment variables:
import "github.com/caarlos0/env/v11"
var cfg Config
env.Parse(&cfg)MIT License - see LICENSE for details.