Разработка бэкенда сайта на Go (Gin)

Наша компания занимается разработкой, поддержкой и обслуживанием сайтов любой сложности. От простых одностраничных сайтов до масштабных кластерных систем построенных на микро сервисах. Опыт разработчиков подтвержден сертификатами от вендоров.
Разработка и обслуживание любых видов сайтов:
Информационные сайты или веб-приложения
Сайты визитки, landing page, корпоративные сайты, онлайн каталоги, квиз, промо-сайты, блоги, новостные ресурсы, информационные порталы, форумы, агрегаторы
Сайты или веб-приложения электронной коммерции
Интернет-магазины, B2B-порталы, маркетплейсы, онлайн-обменники, кэшбэк-сайты, биржи, дропшиппинг-платформы, парсеры товаров
Веб-приложения для управления бизнес-процессами
CRM-системы, ERP-системы, корпоративные порталы, системы управления производством, парсеры информации
Сайты или веб-приложения электронных услуг
Доски объявлений, онлайн-школы, онлайн-кинотеатры, конструкторы сайтов, порталы предоставления электронных услуг, видеохостинги, тематические порталы

Это лишь некоторые из технических типов сайтов, с которыми мы работаем, и каждый из них может иметь свои специфические особенности и функциональность, а также быть адаптированным под конкретные потребности и цели клиента

Предлагаемые услуги
Показано 1 из 1 услугВсе 2065 услуг
Разработка бэкенда сайта на Go (Gin)
Средняя
от 1 недели до 3 месяцев
Часто задаваемые вопросы
Наши компетенции:
Этапы разработки
Последние работы
  • image_website-b2b-advance_0.png
    Разработка сайта компании B2B ADVANCE
    1214
  • image_web-applications_feedme_466_0.webp
    Разработка веб-приложения для компании FEEDME
    1161
  • image_websites_belfingroup_462_0.webp
    Разработка веб-сайта для компании БЕЛФИНГРУПП
    852
  • image_ecommerce_furnoro_435_0.webp
    Разработка интернет магазина для компании FURNORO
    1041
  • image_crm_enviok_479_0.webp
    Разработка веб-приложения для компании Enviok
    823
  • image_bitrix-bitrix-24-1c_fixper_448_0.png
    Разработка веб-сайта для компании ФИКСПЕР
    815

Разработка бэкенда сайта на Go (Gin)

Go и Gin — это выбор, когда производительность и предсказуемость важнее скорости написания кода. Go компилируется в статический бинарник без зависимостей, потребляет ~10–20 МБ памяти на старте (против 200–500 МБ у JVM-приложений), обрабатывает тысячи одновременных соединений на горутинах. Gin — наиболее распространённый HTTP-фреймворк для Go с минимальным overhead поверх стандартной библиотеки.

Типичные сценарии: публичные API с высокой нагрузкой, микросервисы, замена тяжёлых Node.js/Python сервисов при масштабировании.

Структура проекта

Go-проекты принято организовывать по функциональным доменам, не по техническим слоям:

cmd/
  api/
    main.go        # точка входа
internal/
  config/
    config.go      # конфигурация через envconfig или viper
  domain/
    product/
      handler.go   # HTTP handlers
      service.go   # бизнес-логика
      repository.go
      model.go
    user/
    order/
  middleware/
    auth.go
    logger.go
    recovery.go
  database/
    postgres.go
    migrations/
  server/
    router.go      # регистрация всех маршрутов
    server.go
pkg/
  validator/
  response/

Основной сервер

// cmd/api/main.go
package main

import (
    "context"
    "log"
    "net/http"
    "os"
    "os/signal"
    "syscall"
    "time"

    "github.com/myapp/internal/config"
    "github.com/myapp/internal/database"
    "github.com/myapp/internal/server"
)

func main() {
    cfg := config.Load()
    db  := database.NewPostgres(cfg.DatabaseURL)
    defer db.Close()

    srv := server.New(cfg, db)

    go func() {
        if err := srv.ListenAndServe(); err != nil && err != http.ErrServerClosed {
            log.Fatalf("server error: %v", err)
        }
    }()

    quit := make(chan os.Signal, 1)
    signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
    <-quit

    ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
    defer cancel()
    srv.Shutdown(ctx)
}

Роутер

// internal/server/router.go
package server

import (
    "github.com/gin-gonic/gin"
    "github.com/myapp/internal/middleware"
)

func (s *Server) setupRouter() *gin.Engine {
    if s.cfg.Env == "production" {
        gin.SetMode(gin.ReleaseMode)
    }

    r := gin.New()
    r.Use(middleware.Logger())
    r.Use(middleware.Recovery())
    r.Use(middleware.CORS(s.cfg.AllowedOrigins))

    v1 := r.Group("/api/v1")
    {
        auth := v1.Group("/auth")
        auth.POST("/login", s.authHandler.Login)
        auth.POST("/refresh", s.authHandler.Refresh)

        products := v1.Group("/products")
        products.GET("", s.productHandler.List)
        products.GET("/:id", s.productHandler.Get)
        products.Use(middleware.JWT(s.cfg.JWTSecret))
        {
            products.POST("", middleware.RequireRole("admin"), s.productHandler.Create)
            products.PUT("/:id", middleware.RequireRole("admin"), s.productHandler.Update)
            products.DELETE("/:id", middleware.RequireRole("admin"), s.productHandler.Delete)
        }
    }

    return r
}

Handler

// internal/domain/product/handler.go
package product

import (
    "net/http"
    "strconv"

    "github.com/gin-gonic/gin"
)

type Handler struct {
    service *Service
}

func NewHandler(service *Service) *Handler {
    return &Handler{service: service}
}

type ListQuery struct {
    Page       int    `form:"page,default=1" binding:"min=1"`
    Limit      int    `form:"limit,default=20" binding:"min=1,max=100"`
    CategoryID *int   `form:"category_id"`
    Search     string `form:"search"`
}

func (h *Handler) List(c *gin.Context) {
    var q ListQuery
    if err := c.ShouldBindQuery(&q); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    products, total, err := h.service.List(c.Request.Context(), ListParams{
        Page:       q.Page,
        Limit:      q.Limit,
        CategoryID: q.CategoryID,
        Search:     q.Search,
    })
    if err != nil {
        c.JSON(http.StatusInternalServerError, gin.H{"error": "internal error"})
        return
    }

    c.JSON(http.StatusOK, gin.H{
        "data": products,
        "pagination": gin.H{
            "page":  q.Page,
            "limit": q.Limit,
            "total": total,
        },
    })
}

type CreateRequest struct {
    Name        string  `json:"name" binding:"required,min=2,max=255"`
    Price       float64 `json:"price" binding:"required,gt=0"`
    CategoryID  *int    `json:"category_id"`
    Description string  `json:"description" binding:"max=5000"`
}

func (h *Handler) Create(c *gin.Context) {
    var req CreateRequest
    if err := c.ShouldBindJSON(&req); err != nil {
        c.JSON(http.StatusUnprocessableEntity, gin.H{"errors": parseValidationErrors(err)})
        return
    }

    product, err := h.service.Create(c.Request.Context(), req)
    if err != nil {
        handleServiceError(c, err)
        return
    }

    c.JSON(http.StatusCreated, product)
}

Repository и pgx

// internal/domain/product/repository.go
package product

import (
    "context"
    "github.com/jackc/pgx/v5/pgxpool"
)

type Repository struct {
    db *pgxpool.Pool
}

func (r *Repository) FindAll(ctx context.Context, params ListParams) ([]*Product, int, error) {
    offset := (params.Page - 1) * params.Limit

    var countQuery = `SELECT COUNT(*) FROM products WHERE is_active = true`
    var listQuery  = `
        SELECT p.id, p.name, p.slug, p.price, p.created_at,
               c.id as category_id, c.name as category_name
        FROM products p
        LEFT JOIN categories c ON c.id = p.category_id
        WHERE p.is_active = true
        ORDER BY p.created_at DESC
        LIMIT $1 OFFSET $2
    `

    var total int
    if err := r.db.QueryRow(ctx, countQuery).Scan(&total); err != nil {
        return nil, 0, err
    }

    rows, err := r.db.Query(ctx, listQuery, params.Limit, offset)
    if err != nil {
        return nil, 0, err
    }
    defer rows.Close()

    var products []*Product
    for rows.Next() {
        var p Product
        if err := rows.Scan(&p.ID, &p.Name, &p.Slug, &p.Price, &p.CreatedAt,
            &p.Category.ID, &p.Category.Name); err != nil {
            return nil, 0, err
        }
        products = append(products, &p)
    }

    return products, total, rows.Err()
}

pgx/v5 — быстрейший PostgreSQL-драйвер для Go. pgxpool управляет пулом соединений.

JWT middleware

// internal/middleware/auth.go
package middleware

import (
    "net/http"
    "strings"

    "github.com/gin-gonic/gin"
    "github.com/golang-jwt/jwt/v5"
)

type Claims struct {
    UserID int    `json:"sub"`
    Role   string `json:"role"`
    jwt.RegisteredClaims
}

func JWT(secret string) gin.HandlerFunc {
    return func(c *gin.Context) {
        auth := c.GetHeader("Authorization")
        if !strings.HasPrefix(auth, "Bearer ") {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "unauthorized"})
            return
        }

        token, err := jwt.ParseWithClaims(auth[7:], &Claims{}, func(t *jwt.Token) (interface{}, error) {
            if _, ok := t.Method.(*jwt.SigningMethodHMAC); !ok {
                return nil, jwt.ErrSignatureInvalid
            }
            return []byte(secret), nil
        })

        if err != nil || !token.Valid {
            c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{"error": "invalid token"})
            return
        }

        claims := token.Claims.(*Claims)
        c.Set("userID", claims.UserID)
        c.Set("role", claims.Role)
        c.Next()
    }
}

func RequireRole(roles ...string) gin.HandlerFunc {
    roleSet := make(map[string]struct{}, len(roles))
    for _, r := range roles {
        roleSet[r] = struct{}{}
    }
    return func(c *gin.Context) {
        role, _ := c.Get("role")
        if _, ok := roleSet[role.(string)]; !ok {
            c.AbortWithStatusJSON(http.StatusForbidden, gin.H{"error": "forbidden"})
            return
        }
        c.Next()
    }
}

Тестирование

func TestProductHandler_List(t *testing.T) {
    gin.SetMode(gin.TestMode)
    mockService := &MockProductService{}
    h := NewHandler(mockService)

    r := gin.New()
    r.GET("/products", h.List)

    mockService.On("List", mock.Anything, mock.AnythingOfType("ListParams")).
        Return([]*Product{{ID: 1, Name: "Test"}}, 1, nil)

    req := httptest.NewRequest(http.MethodGet, "/products?page=1&limit=20", nil)
    w   := httptest.NewRecorder()
    r.ServeHTTP(w, req)

    assert.Equal(t, http.StatusOK, w.Code)
    // ...
}

Сроки разработки

  • Структура + конфигурация + БД — 3–5 дней
  • Handlers + роутер + middleware — 1–1,5 недели
  • Business logic + repository — 1–3 недели
  • Тесты — 1 неделя
  • Docker + CI — 2–3 дня

API для сайта или сервиса: 4–8 недель. Go требует больше кода, чем Python/Node.js, но даёт бинарник с предсказуемой производительностью и минимальными operational costs.