GoExpress
A fast, lightweight, and Express.js-inspired web framework for Go. GoExpress provides an intuitive API for building web applications and microservices with built-in support for routing, middleware, CORS, authentication, and more.
Features
- 🚀 Fast & Lightweight - Minimal overhead with maximum performance
- 🛣️ Powerful Routing - Path parameters, wildcards, and route groups
- 🔐 Authentication - JWT, Basic Auth, API Keys, and Session support
- 🌐 CORS Support - Configurable CORS middleware
- 🔄 Middleware - Composable middleware system
- 📝 Request Parsing - JSON, XML, Form data support
- 🎯 Type-Safe - Fully type-safe with Go's type system
- 🔧 Extensible - Easy to extend with custom middleware
- 📦 Production Ready - Graceful shutdown, logging, recovery
- 🎨 Express.js-like API - Familiar API for JavaScript developers
Installation
go get github.com/abreed05/goexpress
Quick Start
package main
import (
"github.com/abreed05/goexpress"
"github.com/abreed05/goexpress/middleware"
)
func main() {
app := goexpress.New()
// Middleware
app.Use(middleware.Logger())
app.Use(middleware.CORS())
// Routes
app.GET("/", func(c *goexpress.Context) error {
return c.JSON(map[string]interface{}{
"message": "Hello, World!",
})
})
app.Listen("3000")
}
Table of Contents
Configuration
Create an app with custom configuration:
app := goexpress.New(&goexpress.Config{
Port: "8080",
ReadTimeout: 15 * time.Second,
WriteTimeout: 15 * time.Second,
ShutdownTimeout: 30 * time.Second,
EnableLogging: true,
})
Routing
Basic Routes
app.GET("/users", getUsersHandler)
app.POST("/users", createUserHandler)
app.PUT("/users/:id", updateUserHandler)
app.PATCH("/users/:id", patchUserHandler)
app.DELETE("/users/:id", deleteUserHandler)
Route Parameters
app.GET("/users/:id", func(c *goexpress.Context) error {
id := c.Param("id")
return c.JSON(map[string]string{"id": id})
})
app.GET("/posts/:category/:id", func(c *goexpress.Context) error {
category := c.Param("category")
id := c.Param("id")
return c.JSON(map[string]string{
"category": category,
"id": id,
})
})
Query Parameters
app.GET("/search", func(c *goexpress.Context) error {
query := c.Query("q")
page := c.QueryDefault("page", "1")
limit, err := c.QueryInt("limit")
return c.JSON(map[string]interface{}{
"query": query,
"page": page,
"limit": limit,
})
})
Route Groups
// API v1 routes
api := app.Group("/api/v1")
{
api.GET("/users", listUsers)
api.POST("/users", createUser)
// Nested groups
admin := api.Group("/admin", authMiddleware)
{
admin.GET("/stats", getStats)
admin.POST("/settings", updateSettings)
}
}
Wildcard Routes
app.GET("/files/*", func(c *goexpress.Context) error {
filepath := c.Param("*")
return c.String("File path: " + filepath)
})
Context
The Context object provides methods to interact with the request and response.
Request Data
// Body parsing
var user User
if err := c.BodyParser(&user); err != nil {
return err
}
// Raw body
body, err := c.Body()
// Form values
name := c.FormValue("name")
// File upload
file, err := c.FormFile("avatar")
// Headers
authHeader := c.Header("Authorization")
// Client IP
ip := c.IP()
// User Agent
ua := c.UserAgent()
Response Methods
// JSON response
return c.JSON(map[string]string{"message": "Success"})
// Status code + JSON
return c.Status(201).JSON(user)
// String response
return c.String("Hello, World!")
// HTML response
return c.HTML("<h1>Welcome</h1>")
// XML response
return c.XML(data)
// Redirect
return c.Redirect("/login", 302)
// Set headers
c.SetHeader("X-Custom-Header", "value")
// Cookies
c.Cookie(&http.Cookie{
Name: "session",
Value: "abc123",
MaxAge: 3600,
})
cookie, err := c.GetCookie("session")
Context Storage
Store and retrieve values in the context:
// Set a value
c.Set("user_id", "12345")
// Get a value
if val, ok := c.Get("user_id"); ok {
userID := val.(string)
}
// Must get (panics if not found)
userID := c.MustGet("user_id").(string)
Redis Session Storage
import (
"github.com/abreed05/goexpress"
"github.com/abreed05/goexpress-redis/session"
)
// Initialize Redis session store
store, _ := session.NewRedisStore(session.RedisConfig{
Addr: "localhost:6379",
Prefix: "session:",
})
// Add session middleware
sessionConfig := session.DefaultConfig(store)
app.Use(session.Middleware(sessionConfig))
// Use sessions in handlers
app.POST("/login", func(c *goexpress.Context) error {
sess, _ := session.GetSession(c)
sess.Set("user_id", "123")
return c.JSON(map[string]string{"message": "Logged in"})
})
Redis Cache
import "github.com/abreed05/goexpress-redis/cache"
// Initialize Redis cache
redisCache, _ := cache.NewRedisCache(cache.RedisConfig{
Addr: "localhost:6379",
Prefix: "cache:",
})
// Cache middleware
cacheConfig := cache.DefaultCacheConfig(redisCache)
app.GET("/users", usersHandler, cache.Middleware(cacheConfig))
// Manual cache usage
app.GET("/products/:id", func(c *goexpress.Context) error {
var product Product
key := "product:" + c.Param("id")
// Try cache first
err := redisCache.Get(key, &product)
if err == nil {
return c.JSON(product)
}
// Fetch from database
product = fetchFromDB()
redisCache.Set(key, product, 10*time.Minute)
return c.JSON(product)
})
Session Management
Session Stores
1. Redis Store
store, err := session.NewRedisStore(session.RedisConfig{
Addr: "localhost:6379",
Password: "", // Set if Redis has auth
DB: 0, // Redis database number
Prefix: "session:", // Key prefix
})
2. Memory Store (No Redis Required)
store := session.NewMemoryStore(5 * time.Minute) // Cleanup interval
3. Cookie Store
store := session.NewCookieStore(24 * time.Hour)
Session Configuration
config := session.Config{
Store: store,
CookieName: "session_id",
CookiePath: "/",
CookieDomain: "",
MaxAge: 24 * time.Hour,
Secure: true, // HTTPS only
HttpOnly: true, // No JavaScript access
SameSite: http.SameSiteLaxMode,
ContextKey: "session",
}
app.Use(session.Middleware(config))
Working with Sessions
// Get session
sess, err := session.GetSession(c)
// Set values
sess.Set("user_id", 123)
sess.Set("username", "john")
sess.Set("role", "admin")
// Get values
userID, ok := sess.Get("user_id")
username, _ := sess.Get("username")
// Delete values
sess.Delete("temp_data")
// Clear all data
sess.Clear()
// Check expiration
if sess.IsExpired() {
// Session expired
}
Flash Messages
One-time messages that survive a single redirect:
// Set flash message
session.Flash(c, "success", "Profile updated!")
session.Flash(c, "error", "Invalid input")
// Get flash message (automatically deleted)
message, ok := session.GetFlash(c, "success")
if ok {
// Display message
}
Session Operations
// Destroy session
session.DestroySession(c, config)
// Regenerate session ID (prevents fixation attacks)
session.RegenerateSession(c, config)
Caching
Cache Middleware
Automatically cache GET requests:
cacheConfig := cache.DefaultCacheConfig(redisCache)
cacheConfig.TTL = 5 * time.Minute
cacheConfig.OnlyStatus = []int{200} // Only cache successful responses
app.GET("/users", usersHandler, cache.Middleware(cacheConfig))
Custom cache key:
cacheConfig.KeyFunc = func(c *goexpress.Context) string {
return c.Path() + ":" + c.Query("page")
}
Skip caching conditionally:
cacheConfig.SkipFunc = func(c *goexpress.Context) bool {
return c.Query("nocache") == "true"
}
Manual Cache Operations
// Set
redisCache.Set("key", data, 10*time.Minute)
// Get
var data MyStruct
err := redisCache.Get("key", &data)
// String operations
redisCache.SetString("key", "value", time.Hour)
value, _ := redisCache.GetString("key")
// Delete
redisCache.Delete("key")
// Check existence
exists, _ := redisCache.Exists("key")
// Clear all
redisCache.Clear()
Advanced Cache Features
Increment/Decrement
count, _ := redisCache.Increment("page_views")
redisCache.IncrementBy("counter", 5)
redisCache.Decrement("stock")
TTL Management
// Get remaining TTL
ttl, _ := redisCache.TTL("key")
// Set expiration
redisCache.Expire("key", 1*time.Hour)
Remember Pattern
Execute function only on cache miss:
var users []User
err := redisCache.Remember("users", 5*time.Minute, func() (interface{}, error) {
return fetchUsersFromDB()
}, &users)
Tagged Cache
Group related cache entries for easy invalidation:
// Cache with tags
tagged := redisCache.Tags("users", "api", "v1")
tagged.Set("user:123", user, 10*time.Minute)
// Flush all cache entries with these tags
tagged.Flush()
Cache Invalidation
// Invalidate specific keys
cache.Invalidate(redisCache, "user:123", "user:456")
// Invalidate by pattern (Redis only)
cache.InvalidatePattern(redisCache, "user:*")
// Invalidate on data changes
app.POST("/users/:id", func(c *goexpress.Context) error {
// Update user...
// Clear cache
redisCache.Delete("user:" + c.Param("id"))
cache.InvalidatePattern(redisCache, "users:*")
return c.JSON(user)
})
Complete Examples
E-commerce API with Redis
package main
import (
"github.com/abreed05/goexpress"
"github.com/abreed05/goexpress-redis/cache"
"github.com/abreed05/goexpress-redis/session"
)
func main() {
app := goexpress.New()
// Redis session
sessionStore, _ := session.NewRedisStore(session.RedisConfig{
Addr: "localhost:6379",
})
app.Use(session.Middleware(session.DefaultConfig(sessionStore)))
// Redis cache
redisCache, _ := cache.NewRedisCache(cache.RedisConfig{
Addr: "localhost:6379",
DB: 1,
})
// Public routes
app.POST("/login", loginHandler)
app.POST("/register", registerHandler)
// Cached product catalog
cacheConfig := cache.DefaultCacheConfig(redisCache)
app.GET("/products", productsHandler, cache.Middleware(cacheConfig))
app.GET("/products/:id", productHandler, cache.Middleware(cacheConfig))
// Protected routes
protected := app.Group("/api", requireAuth)
{
// Shopping cart in session
protected.GET("/cart", getCartHandler)
protected.POST("/cart/add", addToCartHandler)
protected.DELETE("/cart/:id", removeFromCartHandler)
// Checkout
protected.POST("/checkout", checkoutHandler)
// Profile (cached per user)
protected.GET("/profile", profileHandler)
}
app.Listen("3000")
}
func loginHandler(c *goexpress.Context) error {
// Authenticate user...
sess, _ := session.GetSession(c)
sess.Set("user_id", user.ID)
sess.Set("email", user.Email)
return c.JSON(map[string]string{"message": "Logged in"})
}
func addToCartHandler(c *goexpress.Context) error {
sess, _ := session.GetSession(c)
// Get cart from session
cart, _ := sess.Get("cart")
if cart == nil {
cart = []CartItem{}
}
// Add item...
sess.Set("cart", cart)
return c.JSON(cart)
}
func requireAuth(next goexpress.HandlerFunc) goexpress.HandlerFunc {
return func(c *goexpress.Context) error {
sess, _ := session.GetSession(c)
if _, ok := sess.Get("user_id"); !ok {
return goexpress.ErrUnauthorized
}
return next(c)
}
}
API Rate Limiting with Redis
func RateLimitMiddleware(cache *cache.RedisCache, maxRequests int, window time.Duration) goexpress.Middleware {
return func(next goexpress.HandlerFunc) goexpress.HandlerFunc {
return func(c *goexpress.Context) error {
ip := c.IP()
key := "ratelimit:" + ip
// Increment counter
count, _ := cache.Increment(key)
if count == 1 {
// First request, set expiration
cache.Expire(key, window)
}
if count > int64(maxRequests) {
return c.Status(429).JSON(map[string]string{
"error": "Rate limit exceeded",
})
}
return next(c)
}
}
}
// Usage
app.Use(RateLimitMiddleware(redisCache, 100, time.Minute))
Configuration Options
Redis Connection
config := session.RedisConfig{
Addr: "localhost:6379",
Password: "secret",
DB: 0,
Prefix: "myapp:",
}
Session Options
sessionConfig := session.Config{
Store: store,
CookieName: "sid",
MaxAge: 7 * 24 * time.Hour, // 7 days
Secure: true,
HttpOnly: true,
SameSite: http.SameSiteStrictMode,
}
Cache Options
cacheConfig := cache.CacheConfig{
Cache: redisCache,
TTL: 10 * time.Minute,
OnlyStatus: []int{200, 201},
KeyFunc: func(c *goexpress.Context) string {
return cache.GenerateCacheKey(c)
},
}
Best Practices
1. Use Redis for Production
// Development: In-memory
store := session.NewMemoryStore(5 * time.Minute)
// Production: Redis
store, _ := session.NewRedisStore(session.RedisConfig{
Addr: os.Getenv("REDIS_URL"),
})
2. Separate Redis Databases
// DB 0 for sessions
sessionStore, _ := session.NewRedisStore(session.RedisConfig{
Addr: "localhost:6379",
DB: 0,
})
// DB 1 for cache
cacheStore, _ := cache.NewRedisCache(cache.RedisConfig{
Addr: "localhost:6379",
DB: 1,
})
3. Set Appropriate TTLs
// Short TTL for frequently changing data
redisCache.Set("stock:123", stock, 1*time.Minute)
// Long TTL for static data
redisCache.Set("config", config, 24*time.Hour)
// Sessions
sessionConfig.MaxAge = 7 * 24 * time.Hour // 7 days
4. Invalidate Cache on Updates
app.PUT("/products/:id", func(c *goexpress.Context) error {
id := c.Param("id")
// Update product...
// Invalidate cache
redisCache.Delete("product:" + id)
cache.InvalidatePattern(redisCache, "products:*")
return c.JSON(product)
})
app.POST("/contact", func(c *goexpress.Context) error {
// Process form...
if err != nil {
session.Flash(c, "error", "Failed to send message")
return c.Redirect("/contact")
}
session.Flash(c, "success", "Message sent!")
return c.Redirect("/thank-you")
})
Testing
import "testing"
func TestSessionStore(t *testing.T) {
store := session.NewMemoryStore(0)
sess := session.NewSession(time.Hour)
sess.Set("key", "value")
store.Set(sess)
retrieved, err := store.Get(sess.ID)
if err != nil {
t.Fatal(err)
}
if val, _ := retrieved.Get("key"); val != "value" {
t.Error("Value mismatch")
}
}
Middleware
Built-in Middleware
Logger
app.Use(middleware.Logger())
Recovery
Recovers from panics and returns a 500 error:
app.Use(middleware.Recovery())
CORS
// Default CORS
app.Use(middleware.CORS())
// Custom CORS
app.Use(middleware.CORS(middleware.CORSConfig{
AllowOrigins: []string{"https://example.com"},
AllowMethods: []string{"GET", "POST", "PUT", "DELETE"},
AllowHeaders: []string{"Content-Type", "Authorization"},
AllowCredentials: true,
MaxAge: 3600,
}))
Body Limit
Limit request body size:
app.Use(middleware.BodyLimit(1024 * 1024)) // 1MB
Rate Limiting
app.Use(middleware.RateLimit(middleware.RateLimitConfig{
Max: 100,
Window: time.Minute,
}))
app.Use(middleware.Secure())
Custom Middleware
func CustomMiddleware() goexpress.Middleware {
return func(next goexpress.HandlerFunc) goexpress.HandlerFunc {
return func(c *goexpress.Context) error {
// Before request
start := time.Now()
// Process request
err := next(c)
// After request
duration := time.Since(start)
log.Printf("Request took %v", duration)
return err
}
}
}
app.Use(CustomMiddleware())
Route-Specific Middleware
app.GET("/admin", adminHandler, authMiddleware, adminRoleMiddleware)
Authentication
JWT Authentication
import "github.com/abreed05/goexpress/auth"
// Configure JWT
jwtConfig := auth.DefaultJWTConfig("your-secret-key")
// Apply to routes
protected := app.Group("/api", auth.JWT(jwtConfig))
{
protected.GET("/profile", profileHandler)
}
// Generate a token
token, err := auth.GenerateToken(jwt.MapClaims{
"user_id": "123",
"email": "user@example.com",
}, "your-secret-key", 24*time.Hour)
// Verify a token
claims, err := auth.VerifyToken(token, "your-secret-key")
// Refresh a token
newToken, err := auth.RefreshToken(oldToken, "your-secret-key", 24*time.Hour)
Basic Authentication
app.Use(auth.BasicAuth(auth.BasicAuthConfig{
Validator: func(username, password string) bool {
return username == "admin" && password == "secret"
},
}))
API Key Authentication
app.Use(auth.APIKey(auth.APIKeyConfig{
KeyLookup: "header:X-API-Key",
Validator: func(key string) bool {
return key == "valid-api-key"
},
}))
Session Authentication
// Implement a session store
type MemoryStore struct {
sessions map[string]map[string]interface{}
mu sync.RWMutex
}
func (s *MemoryStore) Get(id string) (map[string]interface{}, error) {
s.mu.RLock()
defer s.mu.RUnlock()
return s.sessions[id], nil
}
func (s *MemoryStore) Set(id string, data map[string]interface{}) error {
s.mu.Lock()
defer s.mu.Unlock()
s.sessions[id] = data
return nil
}
func (s *MemoryStore) Delete(id string) error {
s.mu.Lock()
defer s.mu.Unlock()
delete(s.sessions, id)
return nil
}
// Use session middleware
store := &MemoryStore{sessions: make(map[string]map[string]interface{})}
app.Use(auth.Session(auth.DefaultSessionConfig(store)))
Error Handling
HTTP Errors
// Return predefined errors
return goexpress.ErrNotFound
return goexpress.ErrUnauthorized
return goexpress.ErrBadRequest
// Create custom errors
return goexpress.NewHTTPError(400, "Invalid email format")
return goexpress.NewHTTPError(409, "User already exists", "Email is taken")
Global Error Handler
Errors returned by handlers are automatically handled and formatted as JSON:
{
"error": "Not found",
"details": null
}
Examples
Complete REST API
package main
import (
"time"
"github.com/abreed05/goexpress"
"github.com/abreed05/goexpress/middleware"
)
type Product struct {
ID string `json:"id"`
Name string `json:"name"`
Price float64 `json:"price"`
}
var products = make(map[string]Product)
func main() {
app := goexpress.New()
app.Use(middleware.Logger())
app.Use(middleware.Recovery())
app.Use(middleware.CORS())
api := app.Group("/api/v1")
{
api.GET("/products", listProducts)
api.GET("/products/:id", getProduct)
api.POST("/products", createProduct)
api.PUT("/products/:id", updateProduct)
api.DELETE("/products/:id", deleteProduct)
}
app.Listen("3000")
}
func listProducts(c *goexpress.Context) error {
list := make([]Product, 0, len(products))
for _, p := range products {
list = append(list, p)
}
return c.JSON(list)
}
func getProduct(c *goexpress.Context) error {
id := c.Param("id")
product, ok := products[id]
if !ok {
return goexpress.ErrNotFound
}
return c.JSON(product)
}
func createProduct(c *goexpress.Context) error {
var product Product
if err := c.BodyParser(&product); err != nil {
return goexpress.NewHTTPError(400, "Invalid request body")
}
product.ID = generateID()
products[product.ID] = product
return c.Status(201).JSON(product)
}
func updateProduct(c *goexpress.Context) error {
id := c.Param("id")
if _, ok := products[id]; !ok {
return goexpress.ErrNotFound
}
var product Product
if err := c.BodyParser(&product); err != nil {
return goexpress.NewHTTPError(400, "Invalid request body")
}
product.ID = id
products[id] = product
return c.JSON(product)
}
func deleteProduct(c *goexpress.Context) error {
id := c.Param("id")
if _, ok := products[id]; !ok {
return goexpress.ErrNotFound
}
delete(products, id)
return c.Status(204).String("")
}
func generateID() string {
return fmt.Sprintf("%d", time.Now().UnixNano())
}
Authentication Example
See examples/auth/main.go for a complete authentication example with JWT.
Microservices Example
See examples/microservices/main.go for a complete microservices architecture example.
Best Practices
1. Use Middleware Wisely
// Global middleware for all routes
app.Use(middleware.Logger())
app.Use(middleware.Recovery())
// Group-specific middleware
api := app.Group("/api", rateLimitMiddleware)
// Route-specific middleware
app.GET("/admin", handler, authMiddleware, adminMiddleware)
2. Structure Your Application
myapp/
├── main.go
├── config/
│ └── config.go
├── handlers/
│ ├── users.go
│ └── products.go
├── middleware/
│ └── auth.go
├── models/
│ └── user.go
└── routes/
└── routes.go
3. Error Handling
Always return appropriate errors:
func getUser(c *goexpress.Context) error {
id := c.Param("id")
user, err := db.FindUser(id)
if err == sql.ErrNoRows {
return goexpress.ErrNotFound
}
if err != nil {
return goexpress.ErrInternalServerError
}
return c.JSON(user)
}
4. Use Route Groups
api := app.Group("/api")
v1 := api.Group("/v1")
v2 := api.Group("/v2")
v1.GET("/users", v1UsersHandler)
v2.GET("/users", v2UsersHandler)
5. Configuration Management
type Config struct {
Port string
JWTSecret string
DBUrl string
}
func LoadConfig() *Config {
return &Config{
Port: os.Getenv("PORT"),
JWTSecret: os.Getenv("JWT_SECRET"),
DBUrl: os.Getenv("DATABASE_URL"),
}
}
Testing
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestGetUser(t *testing.T) {
app := goexpress.New()
app.GET("/users/:id", getUserHandler)
req := httptest.NewRequest(http.MethodGet, "/users/123", nil)
rec := httptest.NewRecorder()
app.ServeHTTP(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("Expected status 200, got %d", rec.Code)
}
}
- Use connection pooling for database connections
- Enable response compression for large payloads
- Implement caching where appropriate
- Use rate limiting to prevent abuse
- Monitor and profile your application
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
License
MIT License - see LICENSE file for details.
Acknowledgments
Inspired by Express.js and other Go web frameworks like Fiber and Echo.
Support