High-Load Go Backend with Echo Framework

High-Load Go Backend with Echo Framework

Development and maintenance of all types of websites:

Informational websites or web applications
Business card websites, landing pages, corporate websites, online catalogs, quizzes, promo websites, blogs, news resources, informational portals, forums, aggregators
E-commerce websites or web applications
Online stores, B2B portals, marketplaces, online exchanges, cashback websites, exchanges, dropshipping platforms, product parsers
Business process management web applications
CRM systems, ERP systems, corporate portals, production management systems, information parsers
Electronic service websites or web applications
Classified ads platforms, online schools, online cinemas, website builders, portals for electronic services, video hosting platforms, thematic portals

These are just some of the technical types of websites we work with, and each of them can have its own specific features and functionality, as well as be customized to meet the specific needs and goals of the client.

Our competencies:

Frequently Asked Questions

Latest works

  • image_web-applications_feedme_466_0.webp
    Development of a web application for FEEDME
    1281
  • image_ecommerce_furnoro_435_0.webp
    Development of an online store for the company FURNORO
    1237
  • image_crm_enviok_479_0.webp
    Development of a web application for Enviok
    977
  • image_crm_chasseurs_493_0.webp
    CRM development for Chasseurs
    1026
  • image_website-sbh_0.webp
    Website development for SBH Partners
    1103
  • image_website-_0.webp
    Website development for Red Pear
    550

High-Load Go Backend with Echo Framework

In production, a Go server must handle tens of thousands of concurrent connections, process requests in milliseconds, and gracefully shut down during restarts. The standard net/http package lacks built-in rate limiting, graceful shutdown, and flexible routing. That's why we choose Echo – a framework that doesn't impose architecture but provides all the tools for a production-ready solution. Our team has over 7 years of Go development experience and has delivered 50+ projects, from simple APIs to real-time platforms. As stated in the Echo documentation: Echo is a high performance, extensible, minimalist Go web framework.

Why Echo Over Gin?

Echo and Gin solve the same problem, but Echo emphasizes extensibility: middleware, context, binder – all are interfaces that can be replaced. Gin is slightly faster in synthetic benchmarks (e.g., 10k RPS vs 9.5k RPS), but Echo is more architecturally flexible, especially for writing middleware. In real projects, performance differences are negligible – the bottleneck is almost always the database, not the router.

Core Components of an Echo Backend

Initialization and Routing

// internal/server/server.go package server import ( "net/http" "os" "os/signal" "syscall" "context" "time" "github.com/labstack/echo/v4" "github.com/labstack/echo/v4/middleware" "github.com/myapp/internal/domain/product" "github.com/myapp/internal/domain/auth" custmw "github.com/myapp/internal/middleware" ) type Server struct { echo *echo.Echo product *product.Handler auth *auth.Handler logger Logger cfg Config } func New(deps Dependencies) *Server { e := echo.New() e.HideBanner = true e.Validator = custmw.NewValidator() // Built-in middleware e.Use(middleware.RequestLoggerWithConfig(middleware.RequestLoggerConfig{ LogMethod: true, LogURI: true, LogStatus: true, LogLatency: true, LogValuesFunc: func(c echo.Context, v middleware.RequestLoggerValues) error { deps.Logger.Info("request", "method", v.Method, "uri", v.URI, "status", v.Status, "latency", v.Latency) return nil }, })) e.Use(middleware.RecoverWithConfig(middleware.RecoverConfig{ LogErrorFunc: func(c echo.Context, err error, stack []byte) error { deps.Logger.Error("panic recovered", "error", err) return nil }, })) e.Use(middleware.CORSWithConfig(middleware.CORSConfig{ AllowOrigins: deps.Config.AllowedOrigins, AllowCredentials: true, })) e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(100))) s := &Server{echo: e, product: product.NewHandler(deps), auth: auth.NewHandler(deps), logger: deps.Logger, cfg: deps.Config} s.registerRoutes() return s } func (s *Server) registerRoutes() { api := s.echo.Group("/api/v1") // Public authGroup := api.Group("/auth") authGroup.POST("/login", s.auth.Login) authGroup.POST("/refresh", s.auth.Refresh) // Protected restricted := api.Group("", custmw.JWT(s.cfg.JWTSecret)) restricted.GET("/profile", s.auth.Profile) products := api.Group("/products") products.GET("", s.product.List) products.GET("/:id", s.product.Get) adminProducts := products.Group("", custmw.JWT(s.cfg.JWTSecret), custmw.RequireRole("admin")) adminProducts.POST("", s.product.Create) adminProducts.PUT("/:id", s.product.Update) adminProducts.DELETE("/:id", s.product.Delete) } func (s *Server) Start(addr string) error { go func() { if err := s.echo.Start(addr); err != http.ErrServerClosed { s.logger.Error("server error", "err", err) } }() quit := make(chan os.Signal, 1) signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM) <-quit ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() return s.echo.Shutdown(ctx) } 

Custom Validator

// internal/middleware/validator.go package middleware import ( "github.com/go-playground/validator/v10" "github.com/labstack/echo/v4" "net/http" ) type CustomValidator struct { v *validator.Validate } func NewValidator() *CustomValidator { v := validator.New() // Custom tag for slug v.RegisterValidation("slug", func(fl validator.FieldLevel) bool { val := fl.Field().String() for _, c := range val { if !((c >= 'a' && c <= 'z') || (c >= '0' && c <= '9') || c == '-') { return false } } return len(val) > 0 }) return &CustomValidator{v: v} } func (cv *CustomValidator) Validate(i interface{}) error { if err := cv.v.Struct(i); err != nil { errs := err.(validator.ValidationErrors) fields := make(map[string]string, len(errs)) for _, e := range errs { fields[e.Field()] = e.Tag() } return echo.NewHTTPError(http.StatusUnprocessableEntity, fields) } return nil } 

Handler with Echo Context

// internal/domain/product/handler.go package product import ( "net/http" "strconv" "github.com/labstack/echo/v4" ) type Handler struct { svc *Service } type CreateRequest struct { Name string `json:"name" validate:"required,min=2,max=255"` Price float64 `json:"price" validate:"required,gt=0"` CategoryID *int `json:"category_id" validate:"omitempty,gt=0"` Tags []string `json:"tags" validate:"omitempty,max=10,dive,min=1,max=50"` } func (h *Handler) Create(c echo.Context) error { var req CreateRequest if err := c.Bind(&req); err != nil { return echo.NewHTTPError(http.StatusBadRequest, err.Error()) } if err := c.Validate(&req); err != nil { return err // echo.HTTPError with fields } userID := c.Get("userID").(int) product, err := h.svc.Create(c.Request().Context(), req, userID) if err != nil { return mapServiceError(err) } return c.JSON(http.StatusCreated, product) } func (h *Handler) Get(c echo.Context) error { id, err := strconv.Atoi(c.Param("id")) if err != nil { return echo.NewHTTPError(http.StatusBadRequest, "invalid id") } product, err := h.svc.GetByID(c.Request().Context(), id) if err != nil { return mapServiceError(err) } return c.JSON(http.StatusOK, product) } 

WebSocket and SSE

// WebSocket func (h *NotificationHandler) Subscribe(c echo.Context) error { userID := c.Get("userID").(int) conn, _, _, err := ws.UpgradeHTTP(c.Request(), c.Response()) if err != nil { return err } defer conn.Close() ch := h.broker.Subscribe(userID) defer h.broker.Unsubscribe(userID, ch) for msg := range ch { if err := wsutil.WriteServerMessage(conn, ws.OpText, msg); err != nil { break } } return nil } // Server-Sent Events func (h *EventHandler) Stream(c echo.Context) error { c.Response().Header().Set(echo.HeaderContentType, "text/event-stream") c.Response().Header().Set("Cache-Control", "no-cache") c.Response().Header().Set("Connection", "keep-alive") c.Response().WriteHeader(http.StatusOK) ticker := time.NewTicker(5 * time.Second) defer ticker.Stop() for { select { case <-c.Request().Context().Done(): return nil case t := <-ticker.C: fmt.Fprintf(c.Response(), "data: %s\n\n", t.Format(time.RFC3339)) c.Response().Flush() } } } 

Caching with Redis

// internal/cache/redis.go type Cache struct { client *redis.Client } func (c *Cache) GetOrSet(ctx context.Context, key string, ttl time.Duration, fn func() (interface{}, error)) (interface{}, error) { cached, err := c.client.Get(ctx, key).Bytes() if err == nil { var result interface{} if err := json.Unmarshal(cached, &result); err == nil { return result, nil } } value, err := fn() if err != nil { return nil, err } if data, err := json.Marshal(value); err == nil { c.client.Set(ctx, key, data, ttl) } return value, nil } // Usage in handler func (h *Handler) List(c echo.Context) error { cacheKey := fmt.Sprintf("products:list:%s", c.QueryString()) result, err := h.cache.GetOrSet(c.Request().Context(), cacheKey, 5*time.Minute, func() (interface{}, error) { return h.svc.List(c.Request().Context(), parseListParams(c)) }) if err != nil { return echo.NewHTTPError(http.StatusInternalServerError) } return c.JSON(http.StatusOK, result) } 

How Echo Handles High Load

Echo's mechanisms – allocation-free middleware, context pooling, and built-in rate limiter – deliver low TTFB (e.g., <50ms at 10k RPS) and predictable memory usage. Combined with Redis caching, we keep LCP in the green zone of Core Web Vitals even at 10k RPS. The GetOrSet pattern above is the cache-aside approach we apply to every GET endpoint.

Case study: On a recent e-commerce project, we reduced average response time from 1.2 seconds to 180ms by implementing Redis cache-aside and optimizing SQL queries. The system now handles 10k RPS while maintaining consistent LCP under 2.5 seconds. Infrastructure costs decreased by 40% due to reduced server load.

Common Pitfalls and Solutions

  • N+1 queries: Avoid by using Repository pattern with explicit JOINs and pagination. We always use eager loading and single-query approaches.
  • Missing graceful shutdown: Implement echo.Shutdown with a 10-second timeout to drain connections on restart.
  • Weak validation: Register a custom validator with business rules (e.g., slug format) to get structured error responses.
  • CORS misconfiguration: Set AllowOrigins precisely in CORS middleware to prevent broad origins.

What's Included in Our Work (Deliverables)

Our Go backend development service includes:

  • Architecture design: pattern selection (Repository, BFF), DB schema, API contracts (OpenAPI 3.0)
  • Full implementation: middleware, handlers, services, integration tests (80%+ coverage)
  • Infrastructure setup: Docker, docker-compose, CI/CD (GitHub Actions), Nginx config
  • Performance optimization: profiling, caching strategy, load testing (k6)
  • Documentation: OpenAPI (Swagger), README with run instructions, deployment guide
  • Access: full source code, commit history, credentials, admin panel
  • Training: optional 1-day team session on running and extending the solution
  • Warranty: 2 weeks post-delivery support for bug fixes and questions

How We Work: Process and Scope

Component Description
Architecture Pattern selection (Repository, BFF), DB schema, API contracts
Development Middleware, handlers, services, integration tests
Infrastructure Docker, docker-compose, CI/CD (GitHub Actions)
Documentation OpenAPI (Swagger), README with run instructions
Deployment Nginx setup, certificates, monitoring (Prometheus/Grafana)
Warranty 2 weeks post-delivery support, bug fixes per contract
  1. Analysis – We audit the current architecture, profile bottlenecks.
  2. Design – Schemas, stack choices (PostgreSQL/Redis/Elasticsearch).
  3. Implementation – Write code, cover with unit and e2e tests.
  4. Testing – Load tests (vegeta/k6), check LCP/CLS/INP.
  5. Deployment – Deploy on your hosting (Selectel, Beget, REG.RU) or cloud.
  6. Handover – Full documentation, access, optional team training.

Timeline and Budget Estimates

  • Scaffold + DI + database: 3–5 days
  • Routes, handlers, middleware: 1–1.5 weeks
  • Services + repositories: 1–3 weeks
  • WebSocket/SSE: 3–5 days extra
  • Tests + Docker: 1 week

An API for a typical site takes 4 to 8 weeks. Budget ranges from $15,000 to $40,000 depending on complexity (e.g., real-time features, number of integrations). Proper caching can reduce infrastructure costs substantially – we've seen up to 40% monthly savings for clients. For a detailed estimate, contact us with your project scope.

Get a free engineering consultation – we'll evaluate your project within 24 hours. Contact us to discuss details.

What is included in the warranty? After project delivery, we provide 2 weeks of support during which we fix bugs and answer questions. We can extend the warranty period under a separate contract.