A powerful, flexible, and production-ready Go web server built on top of Gin framework.
go get github.com/tomskip123/EpicServer
Create a new web server in just a few lines:
package main
import "github.com/tomskip123/EpicServer"
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
EpicServer.WithHealthCheck("/health"),
EpicServer.WithEnvironment("development"),
})
server.Start()
}
Here's a complete example with routing, database, and authentication:
package main
import (
"github.com/gin-gonic/gin"
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/db"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetHost("localhost", 8080),
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
EpicServer.WithRoutes(
EpicServer.RouteGroup{
Prefix: "/api/v1",
Routes: []EpicServer.Route{
EpicServer.Get("/users", HandleUsers),
},
},
),
EpicServerDb.WithMongo(&EpicServerDb.MongoConfig{
ConnectionName: "default",
URI: "mongodb://localhost:27017",
DatabaseName: "myapp",
}),
})
server.Start()
}
func HandleUsers(c *gin.Context, s *EpicServer.Server) {
client := EpicServerDb.GetMongoClient(s, "default")
db := client.Database("myapp")
collection := db.Collection("users")
// Handle request using MongoDB...
c.JSON(200, gin.H{"message": "users endpoint"})
}
EpicServer provides a flexible configuration system using options pattern.
package main
import (
"github.com/tomskip123/EpicServer"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetHost("localhost", 8080),
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
}
The Config
struct supports the following configurations:
type Config struct {
Server struct {
Host string
Port int
Environment string
}
Security struct {
SecureCookie bool
CookieDomain string
CSPHeader string
Origins []string
}
SecretKey []byte
Custom interface{}
}
Configure server host and port:
EpicServer.SetHost("0.0.0.0", 3000)
Configure secret key for encryption:
EpicServer.SetSecretKey([]byte("32-byte-long-secret-key-here...."))
EpicServer provides a flexible routing system that allows you to organize your routes into groups and access server instance in your handlers.
package main
import (
"github.com/tomskip123/EpicServer"
)
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure routes
EpicServer.WithRoutes(
EpicServer.RouteGroup{
Prefix: "/api/v1",
Routes: []EpicServer.Route{
EpicServer.Get("/users", HandleGetUsers),
EpicServer.Post("/users", HandleCreateUser),
EpicServer.Put("/users/:id", HandleUpdateUser),
EpicServer.Delete("/users/:id", HandleDeleteUser),
},
},
EpicServer.RouteGroup{
Prefix: "/admin",
Routes: []EpicServer.Route{
EpicServer.Get("/stats", HandleAdminStats),
},
},
),
},
})
server.Start()
}
Route handlers have access to both the Gin context and the server instance:
func HandleGetUsers(c *gin.Context, s *EpicServer.Server) {
// Access server components
db := EpicServerDb.GetMongoClient(s, "default")
cache := EpicServerCache.GetMemoryCache(s, "myCache")
// Use gin context as normal
userId := c.Param("id")
query := c.Query("filter")
// Send response
c.JSON(200, gin.H{"message": "success"})
}
Get(path string, handler HandlerFunc)
- HTTP GETPost(path string, handler HandlerFunc)
- HTTP POSTPut(path string, handler HandlerFunc)
- HTTP PUTPatch(path string, handler HandlerFunc)
- HTTP PATCHDelete(path string, handler HandlerFunc)
- HTTP DELETE
Group related routes with common prefix:
EpicServer.WithRoutes(
EpicServer.RouteGroup{
Prefix: "/api/v1",
Routes: []EpicServer.Route{
// All routes here will be prefixed with /api/v1
},
},
)
Route handlers can access all server components:
func MyHandler(c *gin.Context, s *EpicServer.Server) {
// Access configuration
port := s.Config.Server.Port
// Access logger
s.Logger.Info("Handling request")
// Access authentication
session, _ := EpicServer.GetSession(c)
// Access databases
mongoClient := EpicServerDb.GetMongoClient(s, "mongodb")
postgresDB := EpicServerDb.GetPostgresDB(s, "postgres")
// Access cache
cache := EpicServerCache.GetMemoryCache(s, "mycache")
// Access hooks
s.Hooks.Auth.OnUserCreate(claims)
}
EpicServer provides a flexible authentication system supporting multiple providers and custom authentication hooks.
package main
import (
"github.com/tomskip123/EpicServer"
)
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure authentication
EpicServer.WithAuth([]EpicServer.Provider{
{
Name: "google",
ClientId: "your-client-id",
ClientSecret: "your-client-secret",
Callback: "http://localhost:3000/auth/google/callback",
},
}, &EpicServer.SessionConfig{
CookieName: "auth_session",
CookieDomain: "localhost",
CookieSecure: false,
CookieHTTPOnly: true,
SessionDuration: time.Hour * 24,
}),
// Add authentication middleware
EpicServer.WithAuthMiddleware(EpicServer.SessionConfig{
CookieName: "auth_session",
CookieDomain: "localhost",
CookieSecure: false,
}),
},
})
server.Start()
}
Define paths that don't require authentication:
EpicServer.WithPublicPaths(EpicServer.PublicPathConfig{
Exact: []string{
"/health",
"/login",
},
Prefix: []string{
"/public",
"/api/v1/public",
},
})
Implement custom authentication logic:
type MyAuthHooks struct {
db *Database
}
func (h *MyAuthHooks) OnUserCreate(user EpicServer.Claims) (string, error) {
// Create user in database
return userID, nil
}
func (h *MyAuthHooks) GetUserOrCreate(user EpicServer.Claims) (*EpicServer.CookieContents, error) {
// Get or create user and return session data
return &EpicServer.CookieContents{
UserId: user.UserID,
Email: user.Email,
SessionId: generateSessionID(),
IsLoggedIn: true,
ExpiresOn: time.Now().Add(time.Hour * 24),
}, nil
}
// Add auth hooks to server
server.UpdateAppLayer([]EpicServer.AppLayer{
EpicServer.WithAuthHooks(&MyAuthHooks{db: db}),
})
- Google (
"google"
) - Basic Auth (
"basic"
) - Custom providers can be added by implementing the provider interface
Required environment variables for secure authentication:
SECURE_COOKIE_HASH_KEY=base64_encoded_32_byte_key
SECURE_COOKIE_BLOCK_KEY=base64_encoded_32_byte_key
ENCRYPTION_KEY=32_byte_hex_encoded_key
Generate secure keys using:
hashKey, _ := EpicServer.GenerateEncryptionKey()
blockKey, _ := EpicServer.GenerateEncryptionKey()
The following endpoints are automatically created:
/auth/:provider
- Initiates authentication flow/auth/:provider/callback
- OAuth callback URL/auth/logout
- Handles user logout
Access session data in your handlers:
func MyProtectedHandler(c *gin.Context) {
session, err := EpicServer.GetSession(c)
if err != nil {
c.AbortWithStatus(401)
return
}
// Access session data
userEmail := session.Email
userData := session.User
}
EpicServer supports multiple database adapters out of the box:
package main
import (
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/db"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
// Configure MongoDB
EpicServerDb.WithMongo(&EpicServerDb.MongoConfig{
ConnectionName: "default",
URI: "mongodb://localhost:27017",
DatabaseName: "myapp",
}),
})
server.Start()
}
func HandleUsers(c *gin.Context, s *EpicServer.Server) {
client := EpicServerDb.GetMongoClient(s, "default")
db := client.Database("myapp")
collection := db.Collection("users")
// Handle request using MongoDB...
}
package main
import (
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/db"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
// Configure PostgreSQL
EpicServerDb.WithPostgres(EpicServerDb.PostgresConfig{
ConnectionName: "default",
Host: "localhost",
Port: 5432,
User: "postgres",
Password: "password",
Database: "myapp",
SSLMode: "disable",
}),
})
server.Start()
}
func HandleUsers(c *gin.Context, s *EpicServer.Server) {
db := EpicServerDb.GetPostgresDB(s, "default")
// Handle request using PostgreSQL...
}
package main
import (
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/db"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
// Configure MySQL
EpicServerDb.WithMySQL(EpicServerDb.MySQLConfig{
ConnectionName: "default",
Host: "localhost",
Port: 3306,
User: "root",
Password: "password",
Database: "myapp",
}),
})
server.Start()
}
func HandleUsers(c *gin.Context, s *EpicServer.Server) {
db := EpicServerDb.GetMySQLDB(s, "default")
// Handle request using MySQL...
}
package main
import (
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/db"
)
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
// Configure GORM
EpicServerDb.WithGorm(&EpicServerDb.GormConfig{
ConnectionName: "default",
Dialect: "mysql", // "mysql", "postgres", or "sqlite"
DSN: "user:password@tcp(localhost:3306)/dbname",
}),
})
server.Start()
}
func HandleUsers(c *gin.Context, s *EpicServer.Server) {
db := EpicServerDb.GetGormDB(s, "default")
// Handle request using GORM...
}
EpicServer includes a built-in memory cache system for temporary data storage.
package main
import (
"github.com/tomskip123/EpicServer"
"github.com/tomskip123/EpicServer/cache"
)
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure memory cache
EpicServerCache.WithMemoryCache(&EpicServerCache.MemoryCacheConfig{
Name: "myCache",
Type: "memory",
}),
},
})
// Use the cache in your handlers
cache := EpicServerCache.GetMemoryCache(server, "myCache")
server.Start()
}
The cache provides simple key-value storage with expiration:
func MyHandler(c *gin.Context) {
cache := EpicServerCache.GetMemoryCache(server, "myCache")
// Set cache item with 5-minute expiration
cache.Set("myKey", "myValue", 5*time.Minute)
// Get cache item
value, exists := cache.Get("myKey")
if exists {
// Use cached value
}
// Delete cache item
cache.Delete("myKey")
}
- In-memory key-value storage
- Automatic expiration of cached items
- Thread-safe operations
- Zero configuration required for memory cache
- Multiple named cache instances
Set(key string, value interface{}, duration time.Duration)
- Store a value with expirationGet(key string) (interface{}, bool)
- Retrieve a value if it existsDelete(key string)
- Remove a value from the cache
EpicServer provides efficient static file serving with support for embedded files.
Serve static files from a directory:
//go:embed static/*
var staticFiles embed.FS
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Serve static files
EpicServer.WithStaticDirectory("/static", &staticFiles, "static"),
},
})
}
Serve specific static files with custom MIME types:
//go:embed favicon.ico
var favicon embed.FS
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Serve a single static file
EpicServer.WithStaticFile(
"favicon.ico", // URL path
&favicon, // Embedded filesystem
"favicon.ico", // File path in embedded filesystem
"image/x-icon", // MIME type
),
},
})
}
Configure the server to handle SPA routing:
//go:embed dist/*
var spaFiles embed.FS
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure SPA handling
EpicServer.WithSPACatchAll(
&spaFiles, // Embedded filesystem
"dist", // Static files directory
"dist/index.html", // SPA entry point
),
},
})
}
- Embedded file system support using Go 1.16+
embed
package - Automatic MIME type detection
- Custom MIME type configuration
- SPA route fallback support
- Directory listing prevention
- Efficient file serving
The static file system supports:
- Multiple static directories
- Mixed static files and API routes
- Custom 404 handling
- Secure file serving
- File type restrictions
- Path normalization
//go:embed assets/* spa/* favicon.ico
var files embed.FS
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Serve static assets
EpicServer.WithStaticDirectory("/assets", &files, "assets"),
// Serve favicon
EpicServer.WithStaticFile("favicon.ico", &files, "favicon.ico", "image/x-icon"),
// Configure SPA
EpicServer.WithSPACatchAll(&files, "spa", "spa/index.html"),
},
})
}
EpicServer provides several built-in middleware options and supports custom middleware creation.
Automatically compresses responses and sets appropriate cache headers:
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithCompression(),
},
})
}
Features:
- Automatic gzip compression
- Smart cache control headers
- Asset-specific caching rules
- Conditional compression based on Accept-Encoding
Configure Cross-Origin Resource Sharing:
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithCors([]string{
"https://example.com",
"https://api.example.com",
}),
},
})
}
Features:
- Origin validation
- Configurable allowed origins
- Preflight request handling
- Custom headers support
- Credential support
Enable CSRF token validation:
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithCSRFProtection(),
},
})
}
// In your handlers
func MyHandler(c *gin.Context) {
// Generate CSRF token
token, _ := EpicServer.GenerateCSRFToken()
// Validate token in POST/PUT/DELETE requests
if !EpicServer.IsTrustedSource(c.Request) {
// Handle CSRF validation
}
}
Features:
- Automatic token generation
- Token validation
- Trusted source bypass
- Custom token storage
- Header/Form support
Remove 'www' prefix from domains:
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithRemoveWWW(),
},
})
}
Features:
- Automatic www detection
- Permanent redirects (301)
- HTTPS upgrade support
- Path preservation
Create your own middleware:
func MyCustomMiddleware() EpicServer.AppLayer {
return func(s *EpicServer.Server) {
s.Engine.Use(func(c *gin.Context) {
// Pre-processing
c.Set("custom_key", "custom_value")
c.Next()
// Post-processing
status := c.Writer.Status()
if status >= 500 {
s.Logger.Error("Server error occurred")
}
})
}
}
// Usage
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
MyCustomMiddleware(),
},
})
Middleware is executed in the order it's added:
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithCompression(), // 1st
EpicServer.WithCors(origins), // 2nd
EpicServer.WithRemoveWWW(), // 3rd
MyCustomMiddleware(), // 4th
},
})
All responses automatically include security headers:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
- Strict-Transport-Security: max-age=31536000; includeSubDomains
- Content-Security-Policy: configurable
Add your own configuration values:
type MyCustomConfig struct {
APIKey string
MaxRequests int
Features []string
}
customConfig := MyCustomConfig{
APIKey: "my-api-key",
MaxRequests: 1000,
Features: []string{"feature1", "feature2"},
}
server := EpicServer.NewServer(&EpicServer.NewServerParam{
Configs: []EpicServer.Option{
EpicServer.SetCustomConfig(customConfig),
},
})
// Access custom config in handlers
func MyHandler(c *gin.Context, s *EpicServer.Server) {
config := EpicServer.GetCustomConfig(s).(MyCustomConfig)
apiKey := config.APIKey
// Use configuration...
}
You can configure multiple database connections with different connection names:
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure multiple databases
EpicServerDb.WithMongo(&EpicServerDb.MongoConfig{
ConnectionName: "users",
URI: "mongodb://localhost:27017",
DatabaseName: "users",
}),
EpicServerDb.WithPostgres(EpicServerDb.PostgresConfig{
ConnectionName: "products",
Host: "localhost",
Database: "products",
// ...other config
}),
EpicServerDb.WithMySQL(EpicServerDb.MySQLConfig{
ConnectionName: "orders",
Host: "localhost",
Database: "orders",
// ...other config
}),
},
})
Implement custom authentication logic:
type MyAuthHooks struct {
db *Database
}
func (h *MyAuthHooks) OnUserCreate(user EpicServer.Claims) (string, error) {
// Create user in database
return userID, nil
}
func (h *MyAuthHooks) GetUserOrCreate(user EpicServer.Claims) (*EpicServer.CookieContents, error) {
// Get or create user and return session data
return &EpicServer.CookieContents{
UserId: user.UserID,
Email: user.Email,
SessionId: generateSessionID(),
IsLoggedIn: true,
ExpiresOn: time.Now().Add(time.Hour * 24),
}, nil
}
// Add auth hooks to server
server.UpdateAppLayer([]EpicServer.AppLayer{
EpicServer.WithAuthHooks(&MyAuthHooks{db: db}),
})
Create your own middleware:
func MyCustomMiddleware() EpicServer.AppLayer {
return func(s *EpicServer.Server) {
s.Engine.Use(func(c *gin.Context) {
// Pre-processing
c.Set("custom_key", "custom_value")
c.Next()
// Post-processing
status := c.Writer.Status()
if status >= 500 {
s.Logger.Error("Server error occurred")
}
})
}
}
// Usage
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
MyCustomMiddleware(),
},
})
Configure the server to handle SPA routing:
//go:embed dist/*
var spaFiles embed.FS
func main() {
server := EpicServer.NewServer([]EpicServer.Option{
EpicServer.SetSecretKey([]byte("your-secret-key")),
})
server.UpdateAppLayer([]EpicServer.AppLayer{
// Configure SPA handling
EpicServer.WithSPACatchAll(
&spaFiles, // Embedded filesystem
"dist", // Static files directory
"dist/index.html", // SPA entry point
),
})
}
package main
import (
"github.com/tomskip123/EpicServer"
)
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
// Configure authentication
EpicServer.WithAuth([]EpicServer.Provider{
{
Name: "google",
ClientId: "your-client-id",
ClientSecret: "your-client-secret",
Callback: "http://localhost:3000/auth/google/callback",
},
}, &EpicServer.SessionConfig{
CookieName: "auth_session",
CookieDomain: "localhost",
CookieSecure: false,
CookieHTTPOnly: true,
SessionDuration: time.Hour * 24,
}),
// Add authentication middleware
EpicServer.WithAuthMiddleware(EpicServer.SessionConfig{
CookieName: "auth_session",
CookieDomain: "localhost",
CookieSecure: false,
}),
},
})
server.Start()
}
Enable CSRF token validation:
func main() {
server := EpicServer.NewServer(&EpicServer.NewServerParam{
AppLayer: []EpicServer.AppLayer{
EpicServer.WithCSRFProtection(),
},
})
}
// In your handlers
func MyHandler(c *gin.Context) {
// Generate CSRF token
token, _ := EpicServer.GenerateCSRFToken()
// Validate token in POST/PUT/DELETE requests
if !EpicServer.IsTrustedSource(c.Request) {
// Handle CSRF validation
}
}
All responses automatically include security headers:
- X-Content-Type-Options: nosniff
- X-Frame-Options: DENY
- X-XSS-Protection: 1; mode=block
- Strict-Transport-Security: max-age=31536000; includeSubDomains
- Content-Security-Policy: configurable
Required environment variables for secure authentication:
SECURE_COOKIE_HASH_KEY=base64_encoded_32_byte_key
SECURE_COOKIE_BLOCK_KEY=base64_encoded_32_byte_key
ENCRYPTION_KEY=32_byte_hex_encoded_key
Generate secure keys using:
hashKey, _ := EpicServer.GenerateEncryptionKey()
blockKey, _ := EpicServer.GenerateEncryptionKey()
WithHealthCheck(path string)
- Adds a health check endpointWithCompression()
- Enables response compressionWithRemoveWWW()
- Removes www prefix from domainWithCors(origins []string)
- Configures CORS settingsWithEnvironment(environment string)
- Sets runtime environment (development/production/test)WithTrustedProxies(proxies []string)
- Configures trusted proxy addressesWithHttp2()
- Enables HTTP/2 support
MongoDB specific helpers:
StringToObjectID(id string)
- Convert string to MongoDB ObjectIDStringArrayToObjectIDArray(ids []string)
- Convert string array to ObjectID arrayUpdateIndexes(ctx, collection, indexes)
- Create or update collection indexesStringArrayContains(array []string, value string)
- Check if string array contains value
GORM specific helpers:
AutoMigrateModels(s *EpicServer.Server, connectionName string, models ...interface{}) error
- Run GORM AutoMigrate for the given models
Set(key string, value interface{}, duration time.Duration)
- Store a value with expirationGet(key string) (interface{}, bool)
- Retrieve a value if it existsDelete(key string)
- Remove a value from the cache
GenerateCSRFToken() (string, error)
- Generate a CSRF tokenIsTrustedSource(req *http.Request) bool
- Validate CSRF tokenGetSession(c *gin.Context) (*Session, error)
- Retrieve session data
- Fork the repository
- Clone your fork:
git clone https://github.com/yourusername/EpicServer.git
- Create your feature branch:
git checkout -b feature/amazing-feature
- Ensure you have Go 1.16+ installed
- Run tests before making changes:
go test -v ./...
The project includes tests for core functionality. Always run tests before submitting a PR:
# Run all tests
go test -v ./...
# Run specific tests (examples)
go test -run TestVerifyCSRFToken # Test CSRF middleware
go test -run TestCompressMiddleware # Test compression
go test -run TestServer_Start # Test server startup
Key areas covered by tests:
- Server initialization and configuration
- Built-in middleware (CSRF, Compression, CORS, WWW redirect)
- Environment settings
- Server lifecycle
- Logger functionality
- Commit your changes:
git commit -m 'Add some amazing feature'
- Push to your fork:
git push origin feature/amazing-feature
- Open a Pull Request
- Follow standard Go formatting (
go fmt
) - Add tests for new features
- Update documentation as needed
- Keep commits focused and atomic
This project is licensed under the MIT License - see the LICENSE file for details.
Project Link: https://github.com/tomskip123/EpicServer