Skip to content

abdussamadbello/echonext

Repository files navigation

EchoNext

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.

Features

  • 🔒 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

Installation

go get github.com/abdussamadbello/echonext

Quick Start

package 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
}

Handler Signatures

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) error

Validation

Use 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"`
}

Query Parameters

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
}

Error Handling

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
}

Middleware & Echo Compatibility

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
}))

Echo Features Available

  • 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.

Example with Echo Features

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!

Advanced OpenAPI Features

Security Schemes

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"},
    },
})

Custom Response Status Codes

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
})

Request/Response Headers

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",
        },
    },
})

Content Types and Examples

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,
        },
    },
})

Complete API Configuration

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"},
})

File Uploads

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",
})

Multiple Files & Configuration

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 avatar

See examples/upload-demo/ for a complete example.

WebSocket Support

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)

Hub Pattern for Broadcasting

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 chat

See examples/websocket-demo/ for a complete example.

GraphQL Integration

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(),
    }),
})

Access Echo Context in Resolvers

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 Configuration Options

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 graphql

See examples/graphql-demo/ for a complete example.

Code Generation from OpenAPI

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=api

Generated files:

  • models/models.go - Data models from schema components
  • dto/dto.go - Request/Response DTOs
  • handlers/handlers.go - Handler function stubs
  • routes.go - Route registration

Example Application

Run the example Todo API:

go run example/main.go

Then visit:

Development

Running Tests

go test ./...                    # Run all tests
go test -v ./...                # Run with verbose output
go test -bench=.                # Run benchmarks
go test -cover                  # Run with coverage

Project Structure

echonext/
├── 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

API Response Format

All responses are wrapped in a consistent format:

{
  "success": true,
  "data": { ... },
  "error": ""
}

Error responses:

{
  "success": false,
  "data": null,
  "error": "Validation failed: Name is required"
}

Optional Contrib Packages

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.

📦 Database (pkg/contrib/database)

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()

🔄 Database Migrations (Atlas)

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=1

Declarative 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.

⚙️ Config (pkg/contrib/config)

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 (pkg/contrib/testing)

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.

🎓 Example Projects

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

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

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

Distributed system with service-to-service communication and events.

See examples/README.md for detailed guides and more examples.

Contributing

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add some amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request

License

MIT License - see the LICENSE file for details.

Roadmap

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)

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Contributors 3

  •  
  •  
  •  

Languages