DEV Community

Cover image for How to Golang: The Infra Darling.
Sk
Sk

Posted on • Edited on

How to Golang: The Infra Darling.

The "tiny" systems language with a punch!

Don't let Go's simplicity fool you; it's a beast! Look at infra. Docker? That's Go.

After 10 years of coding, here’s one golden rule I’ve learned:
Don’t memorize syntax. That’s what Google’s for.

Instead, focus on the core ideas of a language, in its natural habitat..

For Python, that’s ML.
For Golang? Backend & Infra.

So that's what we’re doing here: core Golang concepts by building a server app.
I'll guide you step-by-step, from the basics to mutexes and concurrency.

This tutorial is for intermediate to advanced beginners.
If you’re already comfortable with Go fundamentals, you can skip ahead to Interface-Driven Design.


A TCP Server in Go

TCP is a reliable server connection. Let's get straight into it:

package main

import (
  "fmt"
  "net"
)

func OnConnect(conn net.Conn) {}

func main() {
  ln, err := net.Listen("tcp", ":9000")
  if err != nil {
    panic(err)
  }
  fmt.Println("Server listening on :9000")
  for {
    conn, err := ln.Accept()
    if err != nil {
      fmt.Println("Accept error:", err)
      continue
    }
    OnConnect(conn)
  }
}
Enter fullscreen mode Exit fullscreen mode

We bind a new TCP server on port 9000:

ln, err := net.Listen("tcp", ":9000") 
Enter fullscreen mode Exit fullscreen mode

The line above encodes one of Go’s most important idioms: explicit error handling.


First Lesson: Go Error Handling

In Go, errors are values: you have to deal with them directly.
Almost every function returns a tuple: (value, error).

func CustFunc() (string, error) {
  return "value", nil
}
Enter fullscreen mode Exit fullscreen mode

To handle the error:

val, err := CustFunc()
if err != nil {
   // handle error
}
// continue safely
fmt.Println(val)
Enter fullscreen mode Exit fullscreen mode

If you want to be a gopher, you need to live this pattern.


Second Idiom: Short Variable Declarations

:=  // declares & assigns in one shot
Enter fullscreen mode Exit fullscreen mode

Third Idiom: Strong Typing

func OnConnect(conn net.Conn) {}
Enter fullscreen mode Exit fullscreen mode

net.Conn is your TCP socket. In backend systems, we usually buffer these connections, toss them into a queue or array for processing.

But we don’t store raw sockets. We wrap them into predictable, structured formats, aka objects.


User-Defined Types ("Objects" in Go)

In Go, that’s a struct:

type Client struct {
  Conn net.Conn
  ID   string
}
Enter fullscreen mode Exit fullscreen mode

On connection:

func OnConnect(conn net.Conn) {
  client := Client{Conn: conn, ID: conn.RemoteAddr().String()}
  fmt.Println("New client:", client.ID)
  // next: read from client
}
Enter fullscreen mode Exit fullscreen mode

Now your connection now has a descriptive type we can easily pass around.


Reading From Connections

import "bufio"

func process(input string) string {}

func OnConnect(conn net.Conn) {
  client := Client{Conn: conn, ID: conn.RemoteAddr().String()}
  reader := bufio.NewReader(conn)

  for {
    line, err := reader.ReadString('\n')
    if err != nil {
      fmt.Println("Read error:", err)
      break
    }
    response := process(line)
    conn.Write([]byte(response + "\n"))
  }
}
Enter fullscreen mode Exit fullscreen mode

The Takeaway: The Go Standard Library is incredible. Learn it well.

  • bufio helps us read the raw TCP stream.
  • We read strings, handle errors, process input, and write back responses.

Designing a Protocol

Since strings can represent anything (JSON, plain text, whatever), we need a protocol.
We’ll support just two commands: ECHO and TIME.

parts := strings.Fields(strings.TrimSpace(input)) // trim
cmd := strings.ToUpper(parts[0]) // split

switch cmd { // match
case "ECHO":
  return strings.Join(parts[1:], " ")
case "TIME":
  return time.Now().Format(time.RFC3339)
default:
  return "ERR unknown command"
}
Enter fullscreen mode Exit fullscreen mode

We trim, split, and pattern match.

Now the client request has meaning, and we can respond accordingly.

But Wait… It’s Blocking!

Concurrency: Goroutines

Right now, we can only handle one client at a time.
Solution? Concurrency.

In Go, it's this easy:

go OnConnect(conn)
Enter fullscreen mode Exit fullscreen mode

Goroutines are lightweight "green threads" managed by Go's runtime. You can spawn hundreds of thousands of them.

But with concurrency comes great danger.


Synchronization: Locks & Mutexes

Imagine a busy intersection without traffic light. There's bound to be an accident.

Concurrency means multiple readers and writers, which brings race conditions.

Mutexes are traffic lights.

var (
  clients   = make([]Client, 0)
  clientsMu sync.Mutex
)

func register(c Client) {
  clientsMu.Lock()
  clients = append(clients, c)
  clientsMu.Unlock()
}
Enter fullscreen mode Exit fullscreen mode

Now we safely register new clients.

Update OnConnect:

func OnConnect(conn net.Conn) {
  client := Client{Conn: conn, ID: conn.RemoteAddr().String()}
  register(client)
  // handle I/O
}
Enter fullscreen mode Exit fullscreen mode

Broadcasting

Now that we store clients, we can broadcast messages:

func broadcast(msg string) {
  clientsMu.Lock()
  defer clientsMu.Unlock()
  for _, c := range clients {
    go c.Conn.Write([]byte("BROADCAST: " + msg + "\n"))
  }
}
Enter fullscreen mode Exit fullscreen mode

defer ensures the mutex unlocks even if the function panics or returns early.


Clean Up After Yourself

Always close I/O devices in systems code:

func closeAll() {
  clientsMu.Lock()
  defer clientsMu.Unlock()
  for _, c := range clients {
    c.Conn.Close()
  }
}
Enter fullscreen mode Exit fullscreen mode

Wrapping Up

Now look at how much you’ve touched in just a short time:

  • Imports & modules
  • Functions & signatures
  • Idiomatic error handling
  • User-defined types (struct)
  • Bonus: Methods on structs:
func (c *Client) Print() {
  fmt.Println(c.ID)
}
Enter fullscreen mode Exit fullscreen mode
  • Reading buffered data with bufio
  • String manipulation (strings.TrimSpace, strings.Fields, strings.ToUpper)
  • Goroutines for concurrency
  • Slices
  • Mutexes for synchronization

And notice, we never wasted time on variables, loops, numbers, or data types. Those are just syntax.

The fundamentals don’t change between languages, it's just syntax and the language's idioms

If you want to learn a language fast, skip the first 100 pages of the book and figure out what makes it tick.

I didn’t explain conditionals or loops, but you understood:
"Ahh, that’s a for loop."

Top comments (2)

Collapse
 
quentin_delignon_aa08e211 profile image
Quentin delignon

Your series is great ! Thanks for sharing , really interesting topics not mentioned in basic tutorials

Collapse
 
sfundomhlungu profile image
Sk

Thank you! I am glad you enjoyed it! It was fun making it.🔥

Some comments may only be visible to logged-in visitors. Sign in to view all comments.