Przejdź do treści
Backend

Go (Golang) - Building High-Performance APIs and Services

Published on:
·5 min read·Author: MDS Software Solutions Group

Go (Golang) Building

backend

Go (Golang) - Building High-Performance APIs and Services

Go, also known as Golang, is a programming language created by Google in 2009. Designed by Robert Griesemer, Rob Pike, and Ken Thompson, Go was born from frustrations with the existing languages used in Google's infrastructure. The goal was to create a language that combines Python's simplicity with C++'s performance, while offering native concurrency support and fast compilation times.

Today, Go is one of the most popular languages for building microservices, APIs, CLI tools, and distributed systems. Companies like Google, Uber, Dropbox, Twitch, Docker, and Kubernetes rely on it - in fact, Kubernetes itself is written in Go.

Why Has Go Gained Such Popularity?#

Go stands out with several key characteristics:

  • Simple syntax - minimal number of keywords, no class inheritance
  • Fast compilation - large projects compile in seconds
  • Static typing - errors caught at compile time
  • Built-in concurrency - goroutines and channels as language primitives
  • Single binary - no runtime dependencies, easy deployment
  • Garbage collector - automatic memory management with low latency
  • Rich standard library - HTTP server, JSON, cryptography, testing built in

Goroutines and Channels - The Concurrency Model#

Goroutines are lightweight threads managed by the Go runtime. They are far cheaper than OS threads - you can run hundreds of thousands of goroutines on a typical server. Each goroutine starts with just 2 KB of stack, which dynamically grows as needed.

Basic Goroutines#

package main

import (
    "fmt"
    "sync"
    "time"
)

func fetchData(source string, wg *sync.WaitGroup) {
    defer wg.Done()

    // Simulate data fetching
    time.Sleep(100 * time.Millisecond)
    fmt.Printf("Fetched data from: %s\n", source)
}

func main() {
    var wg sync.WaitGroup

    sources := []string{
        "https://api.users.example.com",
        "https://api.orders.example.com",
        "https://api.products.example.com",
        "https://api.payments.example.com",
    }

    start := time.Now()

    for _, source := range sources {
        wg.Add(1)
        go fetchData(source, &wg)
    }

    wg.Wait()
    fmt.Printf("All requests completed in: %v\n", time.Since(start))
    // ~100ms instead of ~400ms sequentially
}

Channels#

Channels enable safe communication between goroutines. Instead of sharing memory, goroutines communicate by passing messages - following Go's motto: "Don't communicate by sharing memory; share memory by communicating."

package main

import (
    "encoding/json"
    "fmt"
    "net/http"
    "time"
)

type Result struct {
    Source string
    Data   interface{}
    Error  error
}

func fetchAPI(url string, ch chan<- Result) {
    client := &http.Client{Timeout: 5 * time.Second}

    resp, err := client.Get(url)
    if err != nil {
        ch <- Result{Source: url, Error: err}
        return
    }
    defer resp.Body.Close()

    var data interface{}
    json.NewDecoder(resp.Body).Decode(&data)
    ch <- Result{Source: url, Data: data}
}

func aggregateData(urls []string) []Result {
    ch := make(chan Result, len(urls))

    for _, url := range urls {
        go fetchAPI(url, ch)
    }

    results := make([]Result, 0, len(urls))
    for i := 0; i < len(urls); i++ {
        results = append(results, <-ch)
    }

    return results
}

func main() {
    urls := []string{
        "https://jsonplaceholder.typicode.com/posts/1",
        "https://jsonplaceholder.typicode.com/users/1",
        "https://jsonplaceholder.typicode.com/comments/1",
    }

    results := aggregateData(urls)
    for _, r := range results {
        if r.Error != nil {
            fmt.Printf("Error from %s: %v\n", r.Source, r.Error)
        } else {
            fmt.Printf("Success from %s\n", r.Source)
        }
    }
}

Select - Multiplexing Channels#

The select statement allows waiting on multiple channel operations simultaneously:

func fetchWithTimeout(url string, timeout time.Duration) (*http.Response, error) {
    ch := make(chan *http.Response, 1)
    errCh := make(chan error, 1)

    go func() {
        resp, err := http.Get(url)
        if err != nil {
            errCh <- err
            return
        }
        ch <- resp
    }()

    select {
    case resp := <-ch:
        return resp, nil
    case err := <-errCh:
        return nil, err
    case <-time.After(timeout):
        return nil, fmt.Errorf("timeout after %v", timeout)
    }
}

HTTP Server from the Standard Library#

Go includes a production-ready HTTP server out of the box. Unlike many languages, you don't need an external framework to run a performant web server:

package main

import (
    "encoding/json"
    "log"
    "net/http"
    "sync"
)

type User struct {
    ID    int    `json:"id"`
    Name  string `json:"name"`
    Email string `json:"email"`
}

type UserStore struct {
    mu     sync.RWMutex
    users  map[int]User
    nextID int
}

func NewUserStore() *UserStore {
    return &UserStore{
        users:  make(map[int]User),
        nextID: 1,
    }
}

func (s *UserStore) Create(user User) User {
    s.mu.Lock()
    defer s.mu.Unlock()

    user.ID = s.nextID
    s.nextID++
    s.users[user.ID] = user
    return user
}

func (s *UserStore) GetAll() []User {
    s.mu.RLock()
    defer s.mu.RUnlock()

    users := make([]User, 0, len(s.users))
    for _, u := range s.users {
        users = append(users, u)
    }
    return users
}

func main() {
    store := NewUserStore()

    mux := http.NewServeMux()

    mux.HandleFunc("/api/users", func(w http.ResponseWriter, r *http.Request) {
        w.Header().Set("Content-Type", "application/json")

        switch r.Method {
        case http.MethodGet:
            json.NewEncoder(w).Encode(store.GetAll())

        case http.MethodPost:
            var user User
            if err := json.NewDecoder(r.Body).Decode(&user); err != nil {
                http.Error(w, `{"error":"invalid data"}`, http.StatusBadRequest)
                return
            }
            created := store.Create(user)
            w.WriteHeader(http.StatusCreated)
            json.NewEncoder(w).Encode(created)

        default:
            http.Error(w, `{"error":"method not allowed"}`, http.StatusMethodNotAllowed)
        }
    })

    handler := loggingMiddleware(mux)

    log.Println("Server started on :8080")
    log.Fatal(http.ListenAndServe(":8080", handler))
}

func loggingMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        log.Printf("%s %s %s", r.Method, r.URL.Path, r.RemoteAddr)
        next.ServeHTTP(w, r)
    })
}

While the standard library is sufficient for many use cases, frameworks add convenient features like parameterized routing, validation, middleware chains, and documentation generation.

Gin is the most widely used Go web framework, offering excellent performance and an intuitive API:

package main

import (
    "net/http"
    "strconv"

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

type Product struct {
    ID          int     `json:"id"`
    Name        string  `json:"name" binding:"required,min=2"`
    Description string  `json:"description"`
    Price       float64 `json:"price" binding:"required,gt=0"`
    Category    string  `json:"category" binding:"required"`
}

var products = []Product{
    {ID: 1, Name: "Laptop", Description: "Developer laptop", Price: 1299.99, Category: "electronics"},
    {ID: 2, Name: "Keyboard", Description: "Mechanical keyboard", Price: 89.99, Category: "accessories"},
}

func main() {
    r := gin.Default()

    // CORS middleware
    r.Use(func(c *gin.Context) {
        c.Header("Access-Control-Allow-Origin", "*")
        c.Header("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
        c.Header("Access-Control-Allow-Headers", "Content-Type, Authorization")
        c.Next()
    })

    api := r.Group("/api/v1")
    {
        api.GET("/products", getProducts)
        api.GET("/products/:id", getProduct)
        api.POST("/products", createProduct)
        api.PUT("/products/:id", updateProduct)
        api.DELETE("/products/:id", deleteProduct)
    }

    r.Run(":8080")
}

func getProducts(c *gin.Context) {
    category := c.Query("category")

    if category != "" {
        filtered := make([]Product, 0)
        for _, p := range products {
            if p.Category == category {
                filtered = append(filtered, p)
            }
        }
        c.JSON(http.StatusOK, gin.H{"data": filtered, "count": len(filtered)})
        return
    }

    c.JSON(http.StatusOK, gin.H{"data": products, "count": len(products)})
}

func getProduct(c *gin.Context) {
    id, err := strconv.Atoi(c.Param("id"))
    if err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": "invalid ID"})
        return
    }

    for _, p := range products {
        if p.ID == id {
            c.JSON(http.StatusOK, gin.H{"data": p})
            return
        }
    }

    c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
}

func createProduct(c *gin.Context) {
    var product Product
    if err := c.ShouldBindJSON(&product); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    product.ID = len(products) + 1
    products = append(products, product)
    c.JSON(http.StatusCreated, gin.H{"data": product})
}

func updateProduct(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))

    var input Product
    if err := c.ShouldBindJSON(&input); err != nil {
        c.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
        return
    }

    for i, p := range products {
        if p.ID == id {
            input.ID = id
            products[i] = input
            c.JSON(http.StatusOK, gin.H{"data": input})
            return
        }
    }

    c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
}

func deleteProduct(c *gin.Context) {
    id, _ := strconv.Atoi(c.Param("id"))

    for i, p := range products {
        if p.ID == id {
            products = append(products[:i], products[i+1:]...)
            c.JSON(http.StatusOK, gin.H{"message": "deleted"})
            return
        }
    }

    c.JSON(http.StatusNotFound, gin.H{"error": "product not found"})
}

Echo - Minimalist and Performant#

package main

import (
    "net/http"

    "github.com/labstack/echo/v4"
    "github.com/labstack/echo/v4/middleware"
)

func main() {
    e := echo.New()

    e.Use(middleware.Logger())
    e.Use(middleware.Recover())
    e.Use(middleware.RateLimiter(middleware.NewRateLimiterMemoryStore(20)))

    e.GET("/api/health", func(c echo.Context) error {
        return c.JSON(http.StatusOK, map[string]string{
            "status":  "healthy",
            "version": "1.0.0",
        })
    })

    e.Logger.Fatal(e.Start(":8080"))
}

Fiber - Inspired by Express.js#

Fiber is built on top of fasthttp and feels familiar to developers coming from Node.js:

package main

import (
    "github.com/gofiber/fiber/v2"
    "github.com/gofiber/fiber/v2/middleware/cors"
    "github.com/gofiber/fiber/v2/middleware/limiter"
    "time"
)

func main() {
    app := fiber.New(fiber.Config{
        Prefork:       true,
        CaseSensitive: true,
        ServerHeader:  "MDS-API",
    })

    app.Use(cors.New())
    app.Use(limiter.New(limiter.Config{
        Max:        100,
        Expiration: 1 * time.Minute,
    }))

    app.Get("/api/status", func(c *fiber.Ctx) error {
        return c.JSON(fiber.Map{
            "status": "ok",
            "uptime": time.Since(time.Now()).String(),
        })
    })

    app.Listen(":3000")
}

Data Modeling with Structs#

Go doesn't have classes in the traditional sense. Instead, it uses structs with methods and interfaces for polymorphism:

package models

import "time"

type User struct {
    ID        uint      `json:"id" gorm:"primaryKey"`
    Email     string    `json:"email" gorm:"uniqueIndex;not null" validate:"required,email"`
    Name      string    `json:"name" gorm:"not null" validate:"required,min=2,max=100"`
    Password  string    `json:"-" gorm:"not null"` // "-" hides field from JSON
    Role      string    `json:"role" gorm:"default:user"`
    Active    bool      `json:"active" gorm:"default:true"`
    CreatedAt time.Time `json:"created_at"`
    UpdatedAt time.Time `json:"updated_at"`
    Orders    []Order   `json:"orders,omitempty" gorm:"foreignKey:UserID"`
}

type Order struct {
    ID        uint      `json:"id" gorm:"primaryKey"`
    UserID    uint      `json:"user_id" gorm:"index;not null"`
    Total     float64   `json:"total" gorm:"not null"`
    Status    string    `json:"status" gorm:"default:pending"`
    Items     []Item    `json:"items" gorm:"foreignKey:OrderID"`
    CreatedAt time.Time `json:"created_at"`
}

type Item struct {
    ID        uint    `json:"id" gorm:"primaryKey"`
    OrderID   uint    `json:"order_id"`
    ProductID uint    `json:"product_id"`
    Quantity  int     `json:"quantity"`
    Price     float64 `json:"price"`
}

// Interface defining the repository contract
type UserRepository interface {
    FindByID(id uint) (*User, error)
    FindByEmail(email string) (*User, error)
    Create(user *User) error
    Update(user *User) error
    Delete(id uint) error
    List(page, limit int) ([]User, int64, error)
}

Error Handling in Go#

Go takes an explicit approach to error handling. Instead of exceptions, functions return an error value that must be checked:

package service

import (
    "errors"
    "fmt"
)

// Defining custom error types
var (
    ErrNotFound      = errors.New("resource not found")
    ErrAlreadyExists = errors.New("resource already exists")
    ErrUnauthorized  = errors.New("unauthorized")
    ErrValidation    = errors.New("validation error")
)

type AppError struct {
    Code    int    `json:"code"`
    Message string `json:"message"`
    Detail  string `json:"detail,omitempty"`
    Err     error  `json:"-"`
}

func (e *AppError) Error() string {
    return fmt.Sprintf("[%d] %s: %s", e.Code, e.Message, e.Detail)
}

func (e *AppError) Unwrap() error {
    return e.Err
}

func NewNotFoundError(resource string, id interface{}) *AppError {
    return &AppError{
        Code:    404,
        Message: "not found",
        Detail:  fmt.Sprintf("%s with ID %v does not exist", resource, id),
        Err:     ErrNotFound,
    }
}

func NewValidationError(detail string) *AppError {
    return &AppError{
        Code:    400,
        Message: "validation error",
        Detail:  detail,
        Err:     ErrValidation,
    }
}

// Usage in a service
type UserService struct {
    repo UserRepository
}

func (s *UserService) GetUser(id uint) (*User, error) {
    user, err := s.repo.FindByID(id)
    if err != nil {
        return nil, fmt.Errorf("fetching user %d: %w", id, err)
    }

    if user == nil {
        return nil, NewNotFoundError("user", id)
    }

    return user, nil
}

func (s *UserService) CreateUser(user *User) error {
    // Check if email already exists
    existing, err := s.repo.FindByEmail(user.Email)
    if err != nil {
        return fmt.Errorf("checking email: %w", err)
    }
    if existing != nil {
        return &AppError{
            Code:    409,
            Message: "conflict",
            Detail:  "a user with this email already exists",
            Err:     ErrAlreadyExists,
        }
    }

    if err := s.repo.Create(user); err != nil {
        return fmt.Errorf("creating user: %w", err)
    }

    return nil
}

Database Access - GORM and sqlx#

GORM - The ORM for Go#

GORM is the most popular ORM in the Go ecosystem, offering auto-migrations, relationships, and a query builder:

package database

import (
    "fmt"
    "log"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
    "gorm.io/gorm/logger"
)

func Connect(dsn string) (*gorm.DB, error) {
    db, err := gorm.Open(postgres.Open(dsn), &gorm.Config{
        Logger: logger.Default.LogMode(logger.Info),
    })
    if err != nil {
        return nil, fmt.Errorf("database connection: %w", err)
    }

    // Connection pooling
    sqlDB, _ := db.DB()
    sqlDB.SetMaxIdleConns(10)
    sqlDB.SetMaxOpenConns(100)

    // Auto-migration
    err = db.AutoMigrate(&User{}, &Order{}, &Item{})
    if err != nil {
        return nil, fmt.Errorf("migration: %w", err)
    }

    log.Println("Connected to database")
    return db, nil
}

// Repository implementation with GORM
type GormUserRepository struct {
    db *gorm.DB
}

func NewGormUserRepository(db *gorm.DB) *GormUserRepository {
    return &GormUserRepository{db: db}
}

func (r *GormUserRepository) FindByID(id uint) (*User, error) {
    var user User
    result := r.db.Preload("Orders").First(&user, id)
    if result.Error != nil {
        if errors.Is(result.Error, gorm.ErrRecordNotFound) {
            return nil, nil
        }
        return nil, result.Error
    }
    return &user, nil
}

func (r *GormUserRepository) List(page, limit int) ([]User, int64, error) {
    var users []User
    var total int64

    r.db.Model(&User{}).Count(&total)

    offset := (page - 1) * limit
    result := r.db.Offset(offset).Limit(limit).
        Order("created_at DESC").
        Find(&users)

    return users, total, result.Error
}

func (r *GormUserRepository) Create(user *User) error {
    return r.db.Create(user).Error
}

sqlx - Extending database/sql#

For finer control over SQL queries, sqlx is a lightweight alternative:

package database

import (
    "github.com/jmoiron/sqlx"
    _ "github.com/lib/pq"
)

type SqlxUserRepository struct {
    db *sqlx.DB
}

func NewSqlxConnection(dsn string) (*sqlx.DB, error) {
    return sqlx.Connect("postgres", dsn)
}

func (r *SqlxUserRepository) FindByID(id uint) (*User, error) {
    var user User
    err := r.db.Get(&user,
        `SELECT id, email, name, role, active, created_at
         FROM users WHERE id = $1`, id)
    if err != nil {
        return nil, err
    }
    return &user, nil
}

func (r *SqlxUserRepository) Search(query string, limit int) ([]User, error) {
    var users []User
    err := r.db.Select(&users,
        `SELECT id, email, name, role, created_at
         FROM users
         WHERE name ILIKE $1 OR email ILIKE $1
         ORDER BY created_at DESC
         LIMIT $2`,
        "%"+query+"%", limit)
    return users, err
}

gRPC and Protocol Buffers#

Go is one of the best languages for building gRPC services. Protocol Buffers provide efficient serialization, and gRPC delivers high-speed inter-service communication:

// user.proto
syntax = "proto3";
package user;
option go_package = "./pb";

message User {
    int64 id = 1;
    string name = 2;
    string email = 3;
    string role = 4;
}

message GetUserRequest {
    int64 id = 1;
}

message CreateUserRequest {
    string name = 1;
    string email = 2;
    string role = 3;
}

message UserListResponse {
    repeated User users = 1;
    int64 total = 2;
}

message ListUsersRequest {
    int32 page = 1;
    int32 limit = 2;
}

service UserService {
    rpc GetUser(GetUserRequest) returns (User);
    rpc CreateUser(CreateUserRequest) returns (User);
    rpc ListUsers(ListUsersRequest) returns (UserListResponse);
}

gRPC server implementation in Go:

package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    "google.golang.org/grpc/codes"
    "google.golang.org/grpc/status"
    pb "myapp/pb"
)

type userServer struct {
    pb.UnimplementedUserServiceServer
    users map[int64]*pb.User
}

func (s *userServer) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    user, ok := s.users[req.Id]
    if !ok {
        return nil, status.Errorf(codes.NotFound, "user %d not found", req.Id)
    }
    return user, nil
}

func (s *userServer) CreateUser(ctx context.Context, req *pb.CreateUserRequest) (*pb.User, error) {
    id := int64(len(s.users) + 1)
    user := &pb.User{
        Id:    id,
        Name:  req.Name,
        Email: req.Email,
        Role:  req.Role,
    }
    s.users[id] = user
    return user, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }

    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &userServer{
        users: make(map[int64]*pb.User),
    })

    log.Println("gRPC server started on :50051")
    log.Fatal(s.Serve(lis))
}

Testing in Go#

Go has a built-in testing framework - no external libraries needed. Tests are written in _test.go files:

package service_test

import (
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"

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

func setupRouter() *gin.Engine {
    gin.SetMode(gin.TestMode)
    r := gin.Default()
    r.GET("/api/products", getProducts)
    r.POST("/api/products", createProduct)
    return r
}

func TestGetProducts(t *testing.T) {
    router := setupRouter()

    w := httptest.NewRecorder()
    req, _ := http.NewRequest("GET", "/api/products", nil)
    router.ServeHTTP(w, req)

    if w.Code != http.StatusOK {
        t.Errorf("expected status 200, got %d", w.Code)
    }

    if !strings.Contains(w.Body.String(), "data") {
        t.Error("response does not contain 'data' key")
    }
}

func TestCreateProduct(t *testing.T) {
    router := setupRouter()

    body := `{"name":"Test Product","price":29.99,"category":"test"}`

    w := httptest.NewRecorder()
    req, _ := http.NewRequest("POST", "/api/products", strings.NewReader(body))
    req.Header.Set("Content-Type", "application/json")
    router.ServeHTTP(w, req)

    if w.Code != http.StatusCreated {
        t.Errorf("expected status 201, got %d", w.Code)
    }
}

// Table-driven tests - idiomatic Go pattern
func TestValidateEmail(t *testing.T) {
    tests := []struct {
        name    string
        email   string
        isValid bool
    }{
        {"valid email", "user@example.com", true},
        {"no domain", "user@", false},
        {"no @", "userexample.com", false},
        {"empty string", "", false},
        {"with subdomain", "user@sub.example.com", true},
    }

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            result := ValidateEmail(tt.email)
            if result != tt.isValid {
                t.Errorf("ValidateEmail(%q) = %v, expected %v",
                    tt.email, result, tt.isValid)
            }
        })
    }
}

// Benchmark
func BenchmarkGetProducts(b *testing.B) {
    router := setupRouter()

    for i := 0; i < b.N; i++ {
        w := httptest.NewRecorder()
        req, _ := http.NewRequest("GET", "/api/products", nil)
        router.ServeHTTP(w, req)
    }
}

Running tests:

# All tests
go test ./...

# With verbose output
go test -v ./...

# With code coverage
go test -cover ./...

# Benchmarks
go test -bench=. ./...

# Race detector
go test -race ./...

Cross-Compilation and Deployment#

One of Go's greatest strengths is the ability to cross-compile for any platform from any platform:

# Compile for Linux from Windows/Mac
GOOS=linux GOARCH=amd64 go build -o server-linux ./cmd/server

# Compile for Windows
GOOS=windows GOARCH=amd64 go build -o server.exe ./cmd/server

# Compile for ARM (Raspberry Pi, AWS Graviton)
GOOS=linux GOARCH=arm64 go build -o server-arm64 ./cmd/server

# Optimize binary size
go build -ldflags="-s -w" -o server ./cmd/server

Docker and Kubernetes Integration#

Go works exceptionally well with containerization. Thanks to static compilation, Docker images can be minimal:

# Multi-stage build
FROM golang:1.22-alpine AS builder

WORKDIR /app

# Cache dependencies
COPY go.mod go.sum ./
RUN go mod download

# Copy source and build
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -ldflags="-s -w" -o /server ./cmd/server

# Minimal production image
FROM scratch

COPY --from=builder /server /server
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/

EXPOSE 8080

ENTRYPOINT ["/server"]

This image weighs just 5-15 MB - compare that to a typical Node.js image (200+ MB) or Java (300+ MB).

Kubernetes manifest:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: go-api
spec:
  replicas: 3
  selector:
    matchLabels:
      app: go-api
  template:
    metadata:
      labels:
        app: go-api
    spec:
      containers:
      - name: go-api
        image: registry.example.com/go-api:latest
        ports:
        - containerPort: 8080
        resources:
          requests:
            memory: "32Mi"
            cpu: "50m"
          limits:
            memory: "128Mi"
            cpu: "500m"
        livenessProbe:
          httpGet:
            path: /health
            port: 8080
          initialDelaySeconds: 3
          periodSeconds: 10
        readinessProbe:
          httpGet:
            path: /ready
            port: 8080
          initialDelaySeconds: 5
          periodSeconds: 5
---
apiVersion: v1
kind: Service
metadata:
  name: go-api-service
spec:
  selector:
    app: go-api
  ports:
  - port: 80
    targetPort: 8080
  type: ClusterIP

Performance - Comparison with Other Languages#

Go delivers exceptional performance relative to its simplicity. Below is a typical comparison for a REST API returning data from a database:

| Metric | Go (Gin) | Node.js (Express) | Python (FastAPI) | Java (Spring Boot) | |---|---|---|---|---| | Requests/s | ~45,000 | ~15,000 | ~8,000 | ~35,000 | | Latency p99 | 2 ms | 12 ms | 25 ms | 5 ms | | RAM Usage | 20 MB | 80 MB | 60 MB | 200 MB | | Startup Time | 10 ms | 500 ms | 2 s | 8 s | | Docker Size | 10 MB | 200 MB | 150 MB | 300 MB |

Go achieves performance close to Java with significantly lower resource consumption and faster startup times, which is critical in containerized and serverless environments.

When to Choose Go?#

Go is an ideal choice when:

  • Building microservices - fast startup, small footprint, easy deployment
  • High concurrency is needed - goroutines handle thousands of connections
  • Creating CLI tools - single binary with no dependencies
  • Working with the cloud - Docker, Kubernetes, AWS Lambda, Cloud Functions
  • Performance matters - low-latency APIs and data processing
  • Simplicity is valued - minimal syntax, fast onboarding for new developers

Go is not the best choice when you need an extensive ORM and scaffolding ecosystem (like Rails or Django), advanced functional programming, or desktop GUI applications.

Summary#

Go is a language that has proven its value in production. From microservices to infrastructure tooling, from APIs to distributed systems, Go offers a unique combination of performance, simplicity, and reliability.

Key advantages of Go include:

  • Goroutines providing lightweight concurrency
  • Simple error model enforcing explicit error handling
  • Fast compilation and instant application startup
  • Minimal Docker images ideal for Kubernetes
  • Built-in tools for testing, profiling, and code formatting
  • Rich ecosystem with frameworks like Gin, Echo, and Fiber

Need a High-Performance API in Go?#

At MDS Software Solutions Group, we specialize in building high-performance backend systems:

  • Designing and implementing APIs in Go
  • Microservice architecture with gRPC
  • Performance optimization of existing systems
  • Containerization and Kubernetes deployment
  • Technical training and consulting

Contact us to discuss your project and learn how Go can accelerate your services!

Author
MDS Software Solutions Group

Team of programming experts specializing in modern web technologies.

Go (Golang) - Building High-Performance APIs and Services | MDS Software Solutions Group | MDS Software Solutions Group