EchoNext is a type-safe wrapper around the Echo web framework that automatically generates OpenAPI specifications and provides request validation. Build robust, well-documented APIs with compile-time type safety.
- 🔒 Type-Safe Handlers - Define handlers with strongly-typed request and response structs
- 📚 Automatic OpenAPI Generation - Generate OpenAPI 3.0 specs from your code
- ✅ Built-in Validation - Validate requests using struct tags
- 📖 Swagger UI - Interactive API documentation out of the box
- 🚀 Zero Boilerplate - Focus on business logic, not HTTP details
- 🔌 Echo Compatible - Use all Echo middleware and features
- 📁 File Uploads - Type-safe file uploads with OpenAPI documentation
- 🔌 WebSocket Support - Real-time communication with Hub pattern
- 📊 GraphQL Integration - Seamless gqlgen integration with context sharing
- 🛠️ CLI Tool - Code generation, hot reload, and project scaffolding
go get github.com/abdussamadbello/echonextpackage main
import (
"github.com/abdussamadbello/echonext"
"github.com/labstack/echo/v4"
)
// Define your request/response types
type CreateUserRequest struct {
Name string `json:"name" validate:"required,min=2"`
Email string `json:"email" validate:"required,email"`
}
type UserResponse struct {
ID string `json:"id"`
Name string `json:"name"`
Email string `json:"email"`
}
func main() {
// Create new EchoNext app
app := echonext.New()
// Set API info
app.SetInfo("User API", "1.0.0", "User management service")
// Register typed routes
app.POST("/users", createUser, echonext.Route{
Summary: "Create a new user",
Description: "Creates a new user with the provided information",
Tags: []string{"Users"},
})
app.GET("/users/:id", getUser, echonext.Route{
Summary: "Get user by ID",
Tags: []string{"Users"},
})
// Serve OpenAPI spec and Swagger UI
app.ServeOpenAPISpec("/api/openapi.json")
app.ServeSwaggerUI("/api/docs", "/api/openapi.json")
// Start server
app.Start(":8080")
}
// Handlers with typed parameters
func createUser(c echo.Context, req CreateUserRequest) (UserResponse, error) {
// Your business logic here
user := UserResponse{
ID: "123",
Name: req.Name,
Email: req.Email,
}
return user, nil
}
func getUser(c echo.Context) (UserResponse, error) {
id := c.Param("id")
// Fetch user logic
return UserResponse{
ID: id,
Name: "John Doe",
Email: "john@example.com",
}, nil
}EchoNext supports various handler signatures:
// No request body (GET, DELETE)
func handler(c echo.Context) (ResponseType, error)
// With request body (POST, PUT, PATCH)
func handler(c echo.Context, req RequestType) (ResponseType, error)
// No response body
func handler(c echo.Context) errorUse struct tags for validation:
type CreatePostRequest struct {
Title string `json:"title" validate:"required,min=3,max=200"`
Content string `json:"content" validate:"required,min=10"`
Tags []string `json:"tags" validate:"max=5,dive,min=2,max=20"`
Status string `json:"status" validate:"required,oneof=draft published"`
}For GET requests, use query tags:
type ListUsersRequest struct {
Page int `query:"page" validate:"min=1"`
Limit int `query:"limit" validate:"min=1,max=100"`
Sort string `query:"sort" validate:"omitempty,oneof=name email created_at"`
}
func listUsers(c echo.Context, req ListUsersRequest) (ListResponse, error) {
// Access validated query params from req
}Return errors from handlers for automatic error responses:
func getUser(c echo.Context) (UserResponse, error) {
id := c.Param("id")
user, err := db.GetUser(id)
if err != nil {
return UserResponse{}, echo.NewHTTPError(404, "user not found")
}
return user, nil
}EchoNext is fully compatible with all Echo middleware and features. Since it wraps *echo.Echo, you have access to everything Echo provides:
import "github.com/labstack/echo/v4/middleware"
app := echonext.New()
// All standard Echo middleware works
app.Use(middleware.Logger())
app.Use(middleware.Recover())
app.Use(middleware.CORS())
app.Use(middleware.Gzip())
app.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))
app.Use(middleware.BasicAuth(func(username, password string, c echo.Context) (bool, error) {
return username == "admin" && password == "secret", nil
}))- Context methods:
c.Param(),c.QueryParam(),c.FormValue(), etc. - File uploads:
c.FormFile(),c.MultipartForm() - Static files:
app.Static("/static", "assets") - Route groups:
api := app.Group("/api") - Custom binders: Custom request binding logic
- Error handling: Echo's centralized error handler
- Server options: TLS, graceful shutdown, etc.
app := echonext.New()
// Use Echo middleware
app.Use(middleware.Logger())
app.Use(middleware.CORS())
// Create route groups (standard Echo)
api := app.Group("/api/v1")
// Static files (standard Echo)
app.Static("/assets", "public")
// EchoNext typed routes work within groups
api.POST("/users", createUser, echonext.Route{
Summary: "Create user",
Tags: []string{"Users"},
})
// Mix typed and standard Echo handlers
app.POST("/upload", func(c echo.Context) error {
file, err := c.FormFile("upload")
if err != nil {
return err
}
// Standard Echo file handling
return c.String(200, "Uploaded: "+file.Filename)
})EchoNext adds type safety and OpenAPI generation on top of Echo without removing any functionality!
Define security requirements for your API:
app := echonext.New()
// Add security schemes
app.AddSecurityScheme("bearerAuth", echonext.Security{
Type: "bearer",
Scheme: "JWT",
})
app.AddSecurityScheme("apiKey", echonext.Security{
Type: "apiKey",
Name: "X-API-Key",
In: "header",
})
// Apply security to routes
app.POST("/protected", handler, echonext.Route{
Security: []echonext.Security{
{Type: "bearer"},
{Type: "apiKey", Name: "X-API-Key"},
},
})Use appropriate HTTP status codes:
app.POST("/users", createUser, echonext.Route{
SuccessStatus: 201, // Returns 201 Created instead of 200 OK
})
app.DELETE("/users/:id", deleteUser, echonext.Route{
SuccessStatus: 204, // Returns 204 No Content
})Document required and optional headers:
app.POST("/upload", uploadHandler, echonext.Route{
RequestHeaders: map[string]echonext.HeaderInfo{
"X-Request-ID": {
Description: "Unique request identifier",
Required: true,
Schema: "string",
},
},
ResponseHeaders: map[string]echonext.HeaderInfo{
"X-Upload-ID": {
Description: "ID of uploaded file",
Schema: "string",
},
},
})Support multiple content types and provide examples:
type CreateUserRequest struct {
Name string `json:"name" example:"John Doe"`
Age int `json:"age" example:"30"`
}
app.POST("/users", createUser, echonext.Route{
ContentTypes: []string{"application/json", "application/xml"},
Examples: map[string]interface{}{
"basic": map[string]interface{}{
"name": "John Doe",
"age": 30,
},
},
})app := echonext.New()
// Set comprehensive API information
app.SetInfo("My API", "1.0.0", "A comprehensive API example")
app.SetContact("API Team", "https://example.com/support", "api@example.com")
app.SetLicense("MIT", "https://opensource.org/licenses/MIT")
app.SetServers([]echonext.Server{
{URL: "https://api.example.com/v1", Description: "Production"},
{URL: "https://staging.example.com/v1", Description: "Staging"},
})EchoNext provides type-safe file upload support with automatic OpenAPI documentation:
import "github.com/abdussamadbello/echonext/upload"
type AvatarRequest struct {
File *upload.File `form:"avatar" validate:"required"`
}
type AvatarResponse struct {
URL string `json:"url"`
}
func uploadAvatar(c echo.Context, req AvatarRequest) (AvatarResponse, error) {
// Access file metadata
fmt.Printf("Filename: %s, Size: %d\n", req.File.Filename, req.File.Size)
// Save the file easily
if err := req.File.SaveTo("/uploads/" + req.File.Filename); err != nil {
return AvatarResponse{}, err
}
return AvatarResponse{URL: "/uploads/" + req.File.Filename}, nil
}
// Register upload endpoint
app.Upload("/avatar", uploadAvatar, echonext.Route{
Summary: "Upload avatar image",
})type DocumentsRequest struct {
Files []*upload.File `form:"documents" validate:"max=10"`
}
app.Upload("/documents", handler, echonext.Route{
FileConfig: &echonext.FileUploadConfig{
MaxFileSize: 10 << 20, // 10MB per file
MaxTotalSize: 50 << 20, // 50MB total
AllowedMIMETypes: []string{"image/jpeg", "image/png", "application/pdf"},
AllowedExtensions: []string{".jpg", ".png", ".pdf"},
MaxFiles: 5,
},
})Generate upload handlers with the CLI:
echonext generate upload avatarSee examples/upload-demo/ for a complete example.
Type-safe WebSocket handlers with connection management:
import "github.com/abdussamadbello/echonext/websocket"
// Simple handler
func chatHandler(conn *websocket.Connection) error {
for {
var msg ChatMessage
if err := conn.ReadJSON(&msg); err != nil {
return err
}
response := ChatResponse{Text: "Echo: " + msg.Text}
if err := conn.WriteJSON(response); err != nil {
return err
}
}
}
app.WS("/chat", chatHandler)type ChatHandler struct {
hub *websocket.Hub
}
func (h *ChatHandler) OnConnect(conn *websocket.Connection) error {
h.hub.Register(conn)
return nil
}
func (h *ChatHandler) OnMessage(conn *websocket.Connection, msg []byte) error {
return h.hub.Broadcast(msg) // Broadcast to all connections
}
func (h *ChatHandler) OnDisconnect(conn *websocket.Connection, err error) {
h.hub.Unregister(conn)
}
// Usage
hub := websocket.NewHub()
go hub.Run()
app.WS("/ws/chat", &ChatHandler{hub: hub})Generate WebSocket handlers with the CLI:
echonext generate websocket chatSee examples/websocket-demo/ for a complete example.
Seamless integration with gqlgen:
import "github.com/abdussamadbello/echonext/graphql"
app.GraphQL(graphql.Config{
Path: "/graphql",
PlaygroundPath: "/playground",
Schema: graph.NewExecutableSchema(graph.Config{
Resolvers: graph.NewResolver(),
}),
})func (r *queryResolver) CurrentUser(ctx context.Context) (*model.User, error) {
echoCtx := graphql.GetEchoContext(ctx)
userID := echoCtx.Get("user_id").(string)
return r.userService.GetByID(userID)
}graphql.Config{
Path: "/graphql",
PlaygroundPath: "/playground", // Empty to disable
Schema: schema,
ComplexityLimit: 100,
QueryCacheSize: 1000,
EnableIntrospection: true,
}Generate GraphQL boilerplate with the CLI:
echonext generate graphqlSee examples/graphql-demo/ for a complete example.
Generate EchoNext code from existing OpenAPI specifications:
# From local file
echonext generate openapi api.yaml
# From URL
echonext generate openapi https://api.example.com/openapi.json
# With options
echonext generate openapi api.yaml --output=./generated --package=apiGenerated files:
models/models.go- Data models from schema componentsdto/dto.go- Request/Response DTOshandlers/handlers.go- Handler function stubsroutes.go- Route registration
Run the example Todo API:
go run example/main.goThen visit:
- API Server: http://localhost:8080
- API Documentation: http://localhost:8080/api/docs
- OpenAPI Spec: http://localhost:8080/api/openapi.json
go test ./... # Run all tests
go test -v ./... # Run with verbose output
go test -bench=. # Run benchmarks
go test -cover # Run with coverageechonext/
├── echonext.go # Main package implementation
├── echonext_test.go # Test suite
├── upload/ # File upload package
│ └── upload.go
├── websocket/ # WebSocket package
│ └── websocket.go
├── graphql/ # GraphQL integration
│ └── graphql.go
├── cmd/echonext-cli/ # CLI tool
│ ├── commands.go
│ └── generator/ # Code generation templates
├── examples/
│ ├── graphql-demo/ # GraphQL example
│ ├── websocket-demo/ # WebSocket chat example
│ └── upload-demo/ # File upload example
├── pkg/contrib/ # Optional helper packages
└── example/main.go # Quick start example
All responses are wrapped in a consistent format:
{
"success": true,
"data": { ... },
"error": ""
}Error responses:
{
"success": false,
"data": null,
"error": "Validation failed: Name is required"
}EchoNext provides optional helper packages in pkg/contrib/ for common tasks. These are completely optional - you can use the underlying libraries directly if you prefer.
GORM integration helpers with:
- Connection management with retry logic
- Generic Repository[T] pattern
- Transaction utilities
- Migration helpers
- Atlas integration for schema migrations
import "github.com/abdussamadbello/echonext/pkg/contrib/database"
cfg := database.DefaultConfig()
db, err := database.Connect(postgres.Open(dsn), cfg)
// Use repository pattern with generics
userRepo := database.NewRepository[User](db)
user, err := userRepo.Find(1)
users, err := userRepo.Where("active = ?", true).FindAll()EchoNext uses Atlas for database schema management:
# Initialize Atlas in your project
echonext db init
# Apply migrations
echonext db migrate
# Generate migration from schema changes
echonext db migrate:diff add_users_table
# Check migration status
echonext db migrate:status
# Rollback migrations
echonext db migrate:down --count=1Declarative Schema - Define your schema in schema.hcl:
table "users" {
schema = schema.public
column "id" { type = bigserial }
column "email" { type = varchar(255) }
primary_key { columns = [column.id] }
}See CLAUDE.md for detailed Atlas documentation.
Viper integration helpers with:
- Generic config loading
- Environment variable binding
- Hot reload support
- Standard config structures
import "github.com/abdussamadbello/echonext/pkg/contrib/config"
type MyConfig struct {
App config.AppConfig `mapstructure:"app"`
Database config.DatabaseConfig `mapstructure:"database"`
}
var cfg MyConfig
config.LoadSimple(&cfg)Testing utilities with:
- APIClient for testing endpoints
- FixtureManager for test data
- Test suite with setup/teardown
- Factory pattern for test entities
import echonexttest "github.com/abdussamadbello/echonext/pkg/contrib/testing"
client := echonexttest.NewAPIClient(app)
resp := client.POST("/users", userRequest)
resp.AssertStatus(t, 201).AssertSuccess(t)See pkg/contrib/README.md for detailed documentation.
Learn by example! Check out our complete example projects:
⚡ Quickstart - Running Example
Complete working Todo API. Run it now!
go run example/main.go
# Visit http://localhost:8080/api/docs📝 Todo List API - Beginner
Simple CRUD operations demonstrating the basics of EchoNext.
echonext init todo-api
echonext generate domain todo
go run ./cmd/api📰 Blog API - Intermediate
Multi-domain blog platform with authentication, search, and relationships.
echonext init blog-api
echonext generate domain post
echonext generate domain comment
echonext generate domain user🛒 E-commerce API - Advanced
Complete e-commerce platform with orders, payments, and inventory.
echonext init ecommerce-api
echonext generate domain product
echonext generate domain order
echonext generate domain payment🔧 Microservices - Expert
Distributed system with service-to-service communication and events.
See examples/README.md for detailed guides and more examples.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
MIT License - see the LICENSE file for details.
Completed:
- ✅ Database integration helpers (see
pkg/contrib/database) - ✅ Configuration management helpers (see
pkg/contrib/config) - ✅ Testing utilities (see
pkg/contrib/testing) - ✅ Middleware helpers (see
pkg/contrib/middleware) - ✅ CLI tool for project generation (
echonext init) - ✅ Code generation commands (
echonext generate domain/handler/service/model/dto) - ✅ Database management commands (
echonext db init/migrate/seed) - ✅ Atlas migration integration for schema management
- ✅ Complete example projects (Todo, Blog, E-commerce, Microservices)
- ✅ OpenTelemetry integration (see
pkg/contrib/middleware)
v1.4.0:
- ✅ Hot reload dev command (
echonext dev) - ✅ Enhanced test runner (
echonext test) - ✅ Build automation (
echonext build) - ✅ Support for file uploads in OpenAPI spec
- ✅ WebSocket support with type safety
- ✅ GraphQL integration
- ✅ Code generation from OpenAPI spec
Planned:
- Plugin system for custom generators
- gRPC support
- API versioning helpers
- Server-Sent Events (SSE)