f in x
> cd .. / HUB_EDITORIALE
Sviluppo di siti web

Go (Golang) for Backend — Concurrency, REST APIs and Microservices for Developers Who Ship

[2026-06-22] Author: Ing. Calogero Bono

You have a service that must handle thousands of requests per second, or a microservice that needs to start and respond in milliseconds. You've tried Node.js, Python, PHP, but concurrency drove you crazy with callbacks, event loops, and GIL. Go (Golang) was built to solve exactly that: a compiled, typed language with native concurrency and a standard library that feels tailored for building backends. At Meteora Web, we've used it in production for years for proprietary platforms and APIs. In this pillar we bring you everything you need to go from first lines of code to a scalable and maintainable backend.

Why Go for backend is a choice that pays off?

Go was designed at Google to solve systems programming and network service problems. No classes, no inheritance, but implicit interfaces, lightweight goroutines, and a blazing fast compiler. For a backend that means:

  • Performance: compiled binaries run without a VM, consume little memory.
  • Simple concurrency: goroutines and channels are part of the language, not an external library.
  • Easy deployment: a single static executable, no runtime dependencies.
  • Solid ecosystem: net/http, database/sql, encoding/json are in the stdlib.

We chose Go for our social management platform precisely for these reasons: concurrency to publish to multiple channels in parallel, low RAM usage on servers, and a binary that can be updated with a simple file replace.

Sponsored Protocol

How to install Go and start your first backend project?

If you've never written a line of Go, the setup is straightforward. Download the archive from golang.org, extract to /usr/local and add to PATH. Then verify:

$ go version
go version go1.22.4 linux/amd64

The modern Go workspace is based on modules. No more $GOPATH needed. Create a folder and initialize a module:

$ mkdir my-backend && cd my-backend
$ go mod init github.com/meteoraweb/my-backend

Now write your first HTTP server. Create main.go:

Sponsored Protocol

package main

import (
    "fmt"
    "net/http"
)

func handler(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Hello from Meteora Web!")
}

func main() {
    http.HandleFunc("/", handler)
    http.ListenAndServe(":8080", nil)
}

Run go run main.go and open http://localhost:8080. You have a working backend in fewer than 10 lines. No framework, no package manager to install.

What are the fundamental types of Go for backend?

In Go everything revolves around struct, interface, slice, map, and pointers. If you come from OOP languages you must abandon classes and inheritance, but in return you get composition and implicit interfaces.

Structs and interfaces

type User struct {
    Name  string
    Email string
}

type Saver interface {
    Save() error
}

func (u User) Save() error {
    // write to database
    return nil
}

A struct satisfies an interface automatically if it implements its methods. No explicit declarations. This makes testing easy: just create a mock that implements the same interface.

Sponsored Protocol

Slices, maps and pointers

Slices are dynamic arrays. Maps are built-in hash maps. Pointers are used to share data without copying, but be careful with concurrency.

users := []User{{Name: "Alice"}, {Name: "Bob"}}
m := map[string]int{"Alice": 30, "Bob": 25}
p := &users[0] // pointer to first element

A practical rule: if a function modifies a parameter, pass a pointer. If not, pass by value.

How does concurrency work in Go for backend?

This is Go's superpower. Goroutines are functions that run concurrently, managed by the runtime. A single Go process can handle millions of goroutines without exploding memory.

Goroutines and channels

func main() {
    ch := make(chan string)
    go func() {
        ch <- "Hello"
    }()
    msg := <-ch
    fmt.Println(msg)
}

Channels are the idiomatic way to communicate between goroutines. They avoid explicit locks and race conditions if used correctly.

Concurrency patterns

A classic worker pool:

func worker(id int, jobs <-chan int, results chan<- int) {
    for j := range jobs {
        results <- j * 2
    }
}

func main() {
    jobs := make(chan int, 10)
    results := make(chan int, 10)
    for w := 1; w <= 3; w++ {
        go worker(w, jobs, results)
    }
    for j := 1; j <= 5; j++ {
        jobs <- j
    }
    close(jobs)
    for r := 1; r <= 5; r++ {
        <-results
    }
}

In the backend we use this pattern to process webhooks, send batch emails, or call external APIs in parallel. With Python we would have had to use asyncio or threads with locks; in Go it's native and performant.

How to build an HTTP server in Go for backend?

The net/http library is complete. You can do everything without a framework, but for complex projects you'll want a more flexible multiplexer like httprouter or a middleware pattern.

net/http and default mux

mux := http.NewServeMux()
mux.HandleFunc("/api/users", getUsers)
http.ListenAndServe(":8080", mux)

The default mux handles fixed paths, but doesn't support URL parameters (e.g., /users/{id}). For that you need Go 1.22 which introduced pattern matching, or an external router.

Middleware and routing

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

mux := http.NewServeMux()
// ... route
handler := loggingMiddleware(mux)
http.ListenAndServe(":8080", handler)

We use a middleware stack for logging, authentication, rate limiting, and CORS. Go makes it easy to chain them without inheritance.

How to design REST APIs in Go for backend?

If you need to build RESTful APIs, you have two paths: use the stdlib with a custom router, or adopt a lightweight framework like Gin or Echo. We've used both; the choice depends on project complexity.

Gin vs Echo frameworks

Gin is more performant (built on httprouter), has a richer DSL for validation and binding. Echo is equally fast and has a very clean syntax. Both are battle-tested in thousands of projects.

// Example with Gin
import "github.com/gin-gonic/gin"

func main() {
    r := gin.Default()
    r.GET("/users/:id", func(c *gin.Context) {
        id := c.Param("id")
        c.JSON(200, gin.H{"id": id})
    })
    r.Run()
}

Best practices we apply in every project:

  • Version your API (e.g., /api/v1/...).
  • Use JSON for input/output.
  • Return structured errors with code and message.
  • Document with OpenAPI/Swagger automatically.

Remember: a REST API in Go must be designed to fail well. Don't let the server die for a parsing error.

How to handle errors in Go for a robust backend?

Go treats errors as values. No exceptions. Every function that can fail returns an error as the last return value.

Errors as values

f, err := os.Open("file.txt")
if err != nil {
    return fmt.Errorf("opening file: %w", err)
}

The modern pattern is to use fmt.Errorf with %w to create wrapped errors. Then you can use errors.Is and errors.As to check the underlying cause.

Sentinel errors and wrapping

var ErrNotFound = errors.New("resource not found")

func GetUser(id int) (User, error) {
    if id <= 0 {
        return User{}, ErrNotFound
    }
    // ...
}

In a backend platform, every layer must propagate the error adding context. The HTTP layer then decides whether to return 404, 500, etc. Never swallow an error without logging it.

How to manage dependencies in Go for backend?

Go modules have been the standard since 2019. Each project has a go.mod file listing dependencies and the Go version. Add a dependency with:

$ go get github.com/gin-gonic/gin@v1.9.1

Then run go mod tidy to clean up. The go.sum file ensures module integrity. No central package manager: everything is based on Git repos and modules.

Practical tips:

  • Keep go.mod clean with regular go mod tidy.
  • Don't commit the vendor/ folder unless necessary; on-demand download is fine.
  • Use semantic versioning and lock critical dependencies.

How to test backend code in Go?

Go has a built-in test runner. Test files end with _test.go. Test functions start with Test and take a *testing.T.

Unit tests and table-driven tests

func TestSum(t *testing.T) {
    testCases := []struct {
        a, b, expected int
    }{
        {1, 2, 3},
        {0, 0, 0},
        {-1, 1, 0},
    }
    for _, tc := range testCases {
        result := Sum(tc.a, tc.b)
        if result != tc.expected {
            t.Errorf("Sum(%d,%d) = %d; expected %d", tc.a, tc.b, result, tc.expected)
        }
    }
}

This pattern reduces duplication and makes it easy to add cases. For APIs, use httptest.NewServer to test handlers without starting the real server.

Benchmarks

Go makes writing benchmarks trivial:

func BenchmarkSum(b *testing.B) {
    for i := 0; i < b.N; i++ {
        Sum(1, 2)
    }
}

Run with go test -bench=.. Measuring performance helps choose between approaches, especially in backend bottlenecks.

How to interact with databases in Go for backend?

Go provides database/sql as a generic interface for relational databases. Specific drivers (MySQL, PostgreSQL, SQLite) are imported with side effects.

database/sql and GORM

import (
    "database/sql"
    _ "github.com/go-sql-driver/mysql"
)

db, err := sql.Open("mysql", "user:pass@tcp(127.0.0.1:3306)/dbname")
if err != nil { log.Fatal(err) }
defer db.Close()

rows, err := db.Query("SELECT id, name FROM users")
if err != nil { log.Fatal(err) }
for rows.Next() {
    var id int
    var name string
    rows.Scan(&id, &name)
}

GORM is a popular ORM that abstracts queries. We use it for projects with complex relationships, but for critical operations we prefer raw SQL for predictable performance. Always configure connection pooling: Go defaults are bad (no limits). Set db.SetMaxOpenConns(25) and db.SetMaxIdleConns(10) to avoid saturating the database. For more depth, check our SQL and relational databases guide.

How to design microservices in Go for backend?

Go is the most used language for microservices after Java. Reason: small binaries, fast startup, low resource consumption. For inter-service communication you have two paths: REST over HTTP or gRPC.

gRPC and Docker

gRPC uses protobuf for serialization and HTTP/2 for transport. In Go it is natively supported with the google.golang.org/grpc library. Example gRPC server:

import (
    "log"
    "net"
    "google.golang.org/grpc"
    pb "my/proto"
)

type server struct { pb.UnimplementedUserServiceServer }

func (s *server) GetUser(ctx context.Context, req *pb.GetUserRequest) (*pb.User, error) {
    return &pb.User{Id: req.Id, Name: "Mario"}, nil
}

func main() {
    lis, _ := net.Listen("tcp", ":50051")
    s := grpc.NewServer()
    pb.RegisterUserServiceServer(s, &server{})
    log.Fatal(s.Serve(lis))
}

Each microservice should be containerized with a minimal Dockerfile:

FROM golang:1.22 AS builder
WORKDIR /app
COPY go.mod go.sum ./
RUN go mod download
COPY . .
RUN CGO_ENABLED=0 GOOS=linux go build -o server .

FROM scratch
COPY --from=builder /app/server /server
EXPOSE 50051
CMD ["/server"]

The resulting image weighs a few MB.

Deployment on Kubernetes

On Kubernetes we use deployments, services, and ingress. Go is perfect for orchestration because startup is immediate and termination can be handled gracefully with signal.Notify. A pattern we use is graceful shutdown:

quit := make(chan os.Signal, 1)
signal.Notify(quit, syscall.SIGINT, syscall.SIGTERM)
<-quit
log.Println("Shutting down server...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
server.Shutdown(ctx)

This way Kubernetes can terminate pods without interrupting in-flight requests.

What to do now

If you want to start with Go for backend, here are three immediate steps:

  1. Install Go and write the sample HTTP server above. Run it and curl it.
  2. Experiment with concurrency: modify the example to read 10 URLs in parallel using goroutines and channels.
  3. Build a mini REST API with Gin or Echo that interacts with a SQLite database (lightweight).

Then, if you have a legacy project, consider migrating one microservice to Go: you'll immediately see the difference in memory consumption and response speed. We do it daily. If you have questions, contact us: we're based in Sciacca, but work across Italy.

Ing. Calogero Bono

> AUTHOR_EXTRACTED

Ing. Calogero Bono

Ingegnere Informatico, co-fondatore di Meteora Web. Esperto in architetture software, sicurezza informatica e sviluppo sistemi scalabili.
[ Read Full Dossier ]

> METEORA_WEB // DIGITAL AGENCY

We build the digital presence your business deserves.

Websites, social media, online advertising, e-commerce and high-performance hosting, engineered with method by computer engineers in Sciacca, for all of Italy.

> MW_JOURNAL

> READ_ALL()