<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[ferztyle]]></title><description><![CDATA[ferztyle]]></description><link>https://ferztyle.me</link><image><url>https://cdn.hashnode.com/uploads/logos/61db385c3ce4b2377adfdcda/af07abf2-2daa-47ed-a6c2-c0baa1dc3898.png</url><title>ferztyle</title><link>https://ferztyle.me</link></image><generator>RSS for Node</generator><lastBuildDate>Thu, 11 Jun 2026 06:17:24 GMT</lastBuildDate><atom:link href="https://ferztyle.me/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Go Context Package]]></title><description><![CDATA[What is context
Every Go developer encounters context.Context early. It shows up in function signatures everywhere — HTTP handlers, database calls, AWS SDK methods, gRPC stubs. Most beginners copy the]]></description><link>https://ferztyle.me/go-context-package</link><guid isPermaLink="true">https://ferztyle.me/go-context-package</guid><dc:creator><![CDATA[Fer]]></dc:creator><pubDate>Tue, 09 Jun 2026 02:54:01 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/61db385c3ce4b2377adfdcda/dfa1c86d-94ec-4e1b-bc77-7125a0cbed1b.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>What is context</h1>
<p>Every Go developer encounters <code>context.Context</code> early. It shows up in function signatures everywhere — HTTP handlers, database calls, AWS SDK methods, gRPC stubs. Most beginners copy the pattern without fully understanding it, passing context.Background() everywhere and moving on.</p>
<p>That works until it doesn't. A Lambda that ignores context hangs past its timeout. A database query that ignores context keeps running after the HTTP client disconnected.</p>
<p>Context is a built-in package in the Go standard library that enables the propagation of cancellation signals, deadlines, and values across API boundaries and between processes.</p>
<hr />
<h1>Why Context Exists</h1>
<p>Before <code>context</code> was added to the standard library in Go 1.7, there was no standard way to answer these questions across a call chain:</p>
<ul>
<li><p>Should this operation still be running, or has the caller given up?</p>
</li>
<li><p>Is there a deadline this operation must finish by?</p>
</li>
<li><p>What request-scoped data (trace ID, user ID, auth token) should flow with this call?</p>
</li>
</ul>
<hr />
<h1>What is really a Context?</h1>
<p>The <code>context.Context</code> type is an interface with exactly four methods. Every context implements all four.</p>
<pre><code class="language-go">type Context interface {
    Deadline() (deadline time.Time, ok bool)
    Done() &lt;-chan struct{}
    Err() error
    Value(key any) any
}
</code></pre>
<p>Here's what each one does.</p>
<h2>Deadline() — does this context have an expiry?</h2>
<pre><code class="language-go">deadline, ok := ctx.Deadline()
</code></pre>
<p>Returns the time when this context will automatically cancel, and a boolean indicating whether a deadline was set at all. If you created the context with <code>context.Background()</code> or <code>context.WithCancel()</code>, there's no deadline — <code>ok</code> will be <code>false</code>. If you used <code>context.WithTimeout()</code> or <code>context.WithDeadline()</code>, <code>ok</code> is <code>true</code> and <code>deadline</code> tells you exactly when it expires.</p>
<p>You don't call this often in day-to-day code, but it's useful when you need to decide whether there's enough time left to start an expensive operation.</p>
<h2>Done() — tell me when to stop</h2>
<pre><code class="language-go">case &lt;-ctx.Done():
</code></pre>
<p>Returns a channel that gets closed the moment the context is cancelled or times out. You listen to this channel, typically inside a <code>select</code> statement, to know when to abandon work. Until the context ends, the channel blocks. When it closes, your code unblocks and can clean up.</p>
<p>This is the method you'll use most often in loops and long-running operations.</p>
<h2>Err() — why did we stop?</h2>
<pre><code class="language-go">err := ctx.Err()
</code></pre>
<p>Returns <code>nil</code> while the context is still alive. Once the context ends, it returns one of two sentinel errors:</p>
<ul>
<li><p><code>context.DeadlineExceeded</code> — the timeout fired automatically</p>
</li>
<li><p><code>context.Canceled</code> — someone called <code>cancel()</code> manually</p>
</li>
</ul>
<p>This is how you distinguish between "we ran out of time" and "something upstream decided to abort". Both mean stop, but they mean different things for logging and debugging.</p>
<h2>Value() — carry data across function calls</h2>
<pre><code class="language-go">id := ctx.Value(myKey)
</code></pre>
<p>Retrieves a value stored in the context by key. This is used to pass request-scoped data — like a request ID, a user ID, or an auth token — through a chain of function calls without adding extra parameters to every function signature.</p>
<p>One important rule: use a custom unexported type for your keys (not a plain string), otherwise different packages might accidentally overwrite each other's values.</p>
<hr />
<h1>Context's creation</h1>
<p>With contexts, there is a tree structure. The parent context is located at the root of the context tree, and the newly derived context from the parent context is called the child context. Through the parent context, four types of contexts can be derived using the four <code>With</code> methods, and each new context can continue to derive child contexts, making the whole structure look like a tree.</p>
<img src="https://cdn.hashnode.com/uploads/covers/61db385c3ce4b2377adfdcda/20f75624-d24b-4b7a-a99e-294521158a04.png" alt="" style="display:block;margin:0 auto" />

<h2><strong>Parent Context</strong></h2>
<p>There are two ways to create the parent context: an empty context still with no functionality.</p>
<h3>context.Background()</h3>
<pre><code class="language-go">ctx := context.Background()
</code></pre>
<p>Use this at the top of your program — in <code>main()</code>, in <code>init()</code>, or as the starting point when creating a server. It's the foundation everything else derives from.</p>
<h3>context.TODO()</h3>
<p>Semantically identical to <code>context.Background()</code> at runtime, but signals intent: <em>"I know a context should go here, I haven't figured out which one yet."</em></p>
<pre><code class="language-go">ctx := context.TODO() // placeholder — replace with a real context later
</code></pre>
<p>Use it when you're refactoring code to add context support and haven't wired up the full chain yet. It's a marker for future work, not a production pattern.</p>
<h2><strong>Child Contexts</strong></h2>
<p>To make the context useful, we use one of the following child contexts:</p>
<pre><code class="language-go">func WithCancel(parent Context) (ctx Context, cancel CancelFunc)
func WithDeadline(parent Context, deadline time.Time) (Context, CancelFunc)
func WithTimeout(parent Context, timeout time.Duration) (Context, CancelFunc)
func WithValue(parent Context, key, val interface{}) Context
</code></pre>
<h3>context.WithCancel()</h3>
<p>Returns a copy of the parent context with a new <code>Done</code> channel, plus a <code>cancel</code> function. Calling <code>cancel</code> closes the <code>Done</code> channel and marks the context as canceled.</p>
<pre><code class="language-go">ctx, cancel := context.WithCancel(context.Background())
defer cancel() // always call cancel
</code></pre>
<p><strong>Always call</strong> <code>cancel</code><strong>.</strong> If you don't, the context and everything derived from it leaks until the parent is canceled or the program exits. <code>defer cancel()</code> immediately after creation is the idiomatic pattern.</p>
<h3>context.WithTimeout() and context.WithDeadline()</h3>
<p>These set an automatic cancellation time. <code>WithTimeout</code> takes a duration; <code>WithDeadline</code> takes an absolute <code>time.Time</code>.</p>
<pre><code class="language-go">// Cancel after 5 seconds
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

// Cancel at a specific moment
deadline := time.Now().Add(5 * time.Second)
ctx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()
</code></pre>
<p><code>WithTimeout(parent, d)</code> is shorthand for <code>WithDeadline(parent, time.Now().Add(d))</code>. They behave identically. When the deadline passes, the context's <code>Done</code> channel is closed automatically and <code>Err()</code> returns <code>context.DeadlineExceeded</code>.</p>
<p>Still call <code>defer cancel()</code> even with a timeout — if your operation finishes before the deadline, calling cancel releases the timer resources immediately rather than waiting for it to fire.</p>
<h3>context.WithValue</h3>
<p>Creates a child context from the parent context for value passing. Used for passing context information, such as the unique request ID and trace ID, for link tracing and configuration passing.</p>
<pre><code class="language-go">ctx := context.WithValue(context.Background(), authToken, "XYZ_123")
fmt.Println(ctx.Value(authToken))
</code></pre>
<hr />
<h1>How Cancellation Propagates</h1>
<p>Remember, contexts form a tree. When a parent context is canceled, all contexts derived from it are canceled too — automatically, immediately, without any extra code.</p>
<pre><code class="language-go">parent, cancelParent := context.WithCancel(context.Background())

child1, cancelChild1 := context.WithCancel(parent)
child2, cancelTimeout := context.WithTimeout(parent, 10*time.Second)

// Canceling the parent cancels both children
cancelParent()

// child1.Err() == context.Canceled
// child2.Err() == context.Canceled
</code></pre>
<p>This is the mechanism that makes context useful across a call tree. An HTTP handler creates a context with a timeout. It passes that context to a database call, which passes it to a connection pool, which passes it to a network read. When the HTTP client disconnects, the handler's context is canceled, and that cancellation flows down through every layer automatically.</p>
<hr />
<h1>Real-World Use Cases</h1>
<h2>HTTP Servers: Respecting Client Disconnects</h2>
<p>Go's <code>net/http</code> package attaches a context to every incoming request. The context is canceled when the client disconnects or the server's write deadline fires. Your handler receives it via <code>r.Context()</code>.</p>
<p>The following is a fully runnable example. It simulates a slow <code>db.Search</code> using a <code>select</code> with <code>time.After</code>, shows what happens when the client disconnects mid-query, and adds a middleware that attaches a request ID to the context, so every layer of the call chain can log it without it being passed as an explicit argument. You can find the <a href="https://github.com/FerRiosCosta/slow-query-simulation">repo</a> here:</p>
<pre><code class="language-go">package main

import (
    "context"
    "errors"
    "fmt"
    "log/slog"
    "net/http"
    "os"
    "time"

    "github.com/google/uuid"
)

// contextKey is an unexported type for context keys in this package.
// Using a custom type prevents collisions with keys from other packages
// that might also store something under a plain string like "request_id".
type contextKey string

const keyRequestID contextKey = "request_id"

// withRequestID attaches a request ID to the context.
func withRequestID(ctx context.Context, id string) context.Context {
    return context.WithValue(ctx, keyRequestID, id)
}

// requestIDFromContext retrieves the request ID from the context.
// Returns an empty string if no ID was set — callers should handle that gracefully.
func requestIDFromContext(ctx context.Context) string {
    id, _ := ctx.Value(keyRequestID).(string)
    return id
}

var logger = slog.New(slog.NewTextHandler(os.Stdout, &amp;slog.HandlerOptions{
    ReplaceAttr: func(_ []string, a slog.Attr) slog.Attr {
        if a.Key == slog.TimeKey {
            return slog.Attr{}
        }
        return a
    },
}))

// requestIDMiddleware generates a unique ID for every incoming request,
// attaches it to the context, and sends it back in the response header.
// Every function below the handler can read it from ctx for logging —
// without it being passed as a function argument anywhere.
func requestIDMiddleware(next http.Handler) http.Handler {
    return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        requestID := uuid.NewString()

        ctx := withRequestID(r.Context(), requestID)
        w.Header().Set("X-Request-ID", requestID)

        next.ServeHTTP(w, r.WithContext(ctx))
    })
}

// Search simulates a slow database query that respects context cancellation.
// It reads the request ID directly from ctx — no extra parameter needed.
func Search(ctx context.Context, query string, delay time.Duration) ([]string, error) {
    requestID := requestIDFromContext(ctx)

    logger.Info("db.Search started",
        "request_id", requestID,
        "query", query,
        "simulated_delay", delay,
    )

    select {
    case &lt;-time.After(delay):
        logger.Info("db.Search completed", "request_id", requestID, "query", query)
        return []string{"result-1", "result-2", "result-3"}, nil

    case &lt;-ctx.Done():
        logger.Warn("db.Search aborted",
            "request_id", requestID,
            "query", query,
            "reason", ctx.Err(),
        )
        return nil, ctx.Err()
    }
}

func searchHandler(w http.ResponseWriter, r *http.Request) {
    requestID := requestIDFromContext(r.Context())

    query := r.URL.Query().Get("q")
    if query == "" {
        query = "gophers"
    }

    delay := 5 * time.Second
    if d := r.URL.Query().Get("delay"); d != "" {
        if parsed, err := time.ParseDuration(d); err == nil {
            delay = parsed
        }
    }

    logger.Info("handler started",
        "request_id", requestID,
        "query", query,
        "client", r.RemoteAddr,
    )

    // r.Context() carries both the cancellation signal AND the request ID value.
    // Search receives one ctx and gets everything it needs from it.
    results, err := Search(r.Context(), query, delay)
    if err != nil {
        if errors.Is(err, context.Canceled) {
            logger.Warn("client disconnected before query finished, no response sent",
                "request_id", requestID,
            )
            return
        }
        logger.Error("unexpected search error", "request_id", requestID, "error", err)
        http.Error(w, "search failed", http.StatusInternalServerError)
        return
    }

    logger.Info("handler finished — sending response",
        "request_id", requestID,
        "result_count", len(results),
    )
    fmt.Fprintf(w, "results: %v\n", results)
}

func main() {
    mux := http.NewServeMux()
    mux.HandleFunc("/search", searchHandler)

    // Wrap the mux — every request gets a request ID attached to its context
    handler := requestIDMiddleware(mux)

    logger.Info("server listening on :8080")
    logger.Info("try: curl -v 'http://localhost:8080/search?q=gophers&amp;delay=5s'")
    logger.Info("cancel with Ctrl+C before 5s to trigger client disconnect")

    if err := http.ListenAndServe(":8080", handler); err != nil {
        logger.Error("server error", "error", err)
        os.Exit(1)
    }
}
</code></pre>
<p>Before running, initialize the module and fetch the <code>uuid</code> package, which is in charge of creating random uuids for the incoming requests:</p>
<pre><code class="language-bash">go mod init example/context-demo
go get github.com/google/uuid
</code></pre>
<h3><strong>To test the cancellation path:</strong></h3>
<pre><code class="language-bash"># Terminal 1 — start the server
go run main.go

# Terminal 2 — send a request with a 5s delay, then hit Ctrl+C before it finishes
curl 'http://localhost:8080/search?q=gophers&amp;delay=5s'
# Press Ctrl+C after ~2 seconds
</code></pre>
<p>You should see in the server logs:</p>
<pre><code class="language-plaintext">level=INFO msg="handler started" request_id=4a1f9c2e-... query=gophers client=127.0.0.1:PORT
level=INFO msg="db.Search started" request_id=4a1f9c2e-... query=gophers simulated_delay=5s
level=WARN msg="db.Search aborted" request_id=4a1f9c2e-... query=gophers reason="context canceled"
level=WARN msg="client disconnected before query finished, no response sent" request_id=4a1f9c2e-...
</code></pre>
<p>Notice the same <code>request_id</code> appears in every log line, from the middleware, through the handler, all the way into <code>Search</code>, without it ever being passed as a function argument. It traveled through the context.</p>
<p>You can also verify the ID was sent back in the response header:</p>
<pre><code class="language-bash">curl -v 'http://localhost:8080/search?q=gophers&amp;delay=5s'| grep X-Request-ID
# X-Request-ID: 4a1f9c2e-...
</code></pre>
<h3><strong>To test the happy path</strong></h3>
<p>Let the query finish before disconnecting:</p>
<pre><code class="language-bash">curl 'http://localhost:8080/search?q=gophers&amp;delay=2s'
# Wait for it to complete
</code></pre>
<pre><code class="language-plaintext">level=INFO msg="handler started" request_id=9b3d7f1a-... query=gophers client=127.0.0.1:PORT
level=INFO msg="db.Search started" request_id=9b3d7f1a-... query=gophers simulated_delay=2s
level=INFO msg="db.Search completed" request_id=9b3d7f1a-... query=gophers
level=INFO msg="handler finished — sending response" request_id=9b3d7f1a-... result_count=3
</code></pre>
<p>The key is the <code>select</code> inside <code>Search</code>. It blocks on two channels simultaneously: <code>time.After(delay)</code> represents the slow query, and <code>ctx.Done()</code> represents the client. Whichever fires first wins. If the client disconnects, <code>ctx.Done()</code> closes and <code>Search</code> returns <code>context.Canceled</code> immediately, without waiting for the timer.</p>
<p>This example shows both things context is good for working together in one flow: the <strong>cancellation signal</strong> (<code>ctx.Done()</code>) stops the query when the client leaves, and the <strong>value</strong> (<code>request_id</code>) flows through every layer for correlated logging, all through the same single <code>ctx</code> argument.</p>
<h2>Database Calls: Enforcing Query Timeouts</h2>
<p>Most database drivers in Go accept a context. Pass one with a timeout to prevent slow queries from holding connections indefinitely.</p>
<pre><code class="language-go">func getUserByID(ctx context.Context, db *sql.DB, userID string) (*User, error) {
    // Give the query 3 seconds — no matter what the caller's deadline is
    queryCtx, cancel := context.WithTimeout(ctx, 3*time.Second)
    defer cancel()

    var user User
    err := db.QueryRowContext(queryCtx,
        "SELECT id, name, email FROM users WHERE id = $1", userID,
    ).Scan(&amp;user.ID, &amp;user.Name, &amp;user.Email)
    if err != nil {
        if errors.Is(err, context.DeadlineExceeded) {
            return nil, fmt.Errorf("user query timed out after 3s: %w", err)
        }
        return nil, fmt.Errorf("querying user %s: %w", userID, err)
    }

    return &amp;user, nil
}
</code></pre>
<p>Notice that <code>queryCtx</code> derives from <code>ctx</code> — the caller's context. If the caller's context is already canceled (client disconnected, Lambda timed out), <code>queryCtx</code> is canceled immediately regardless of the 3-second timeout. The child inherits the parent's cancellation, but can set a <em>tighter</em> deadline on top of it.</p>
<hr />
<h2>Checking for Cancellation in Long Loops</h2>
<p>If your function does significant work in a loop, check <code>ctx.Err()</code> periodically so it can exit early when canceled.</p>
<pre><code class="language-go">func processFiles(ctx context.Context, files []string) error {
    for _, file := range files {
        // Check at the top of each iteration
        if err := ctx.Err(); err != nil {
            return fmt.Errorf("processing canceled after %s: %w", file, err)
        }

        if err := processFile(ctx, file); err != nil {
            return fmt.Errorf("processing %s: %w", file, err)
        }
    }
    return nil
}
</code></pre>
<p>Without this check, a canceled context doesn't stop the loop — it only stops operations that accept and check the context themselves. If <code>processFile</code> takes 500ms and you have 1000 files, a cancellation mid-loop still runs all remaining iterations.</p>
<hr />
<h2>Common Mistakes and Gotchas</h2>
<p><strong>Passing</strong> <code>context.Background()</code> <strong>everywhere.</strong> It works, but it opts every operation out of cancellation and timeout propagation. If your HTTP handler times out, your database queries keep running. Pass the incoming context through your call chain — that's the whole point.</p>
<p><strong>Not calling</strong> <code>cancel()</code><strong>.</strong> Every <code>WithCancel</code>, <code>WithTimeout</code>, and <code>WithDeadline</code> must have its <code>cancel</code> called. The Go runtime cannot garbage-collect a context until cancel is called or the parent is canceled. <code>defer cancel()</code> right after creation is non-negotiable.</p>
<p><strong>Storing contexts in structs.</strong> The Go documentation is explicit: do not store contexts in structs. A context is for a single operation, not for the lifetime of an object. Pass it as the first argument to every function that needs it.</p>
<pre><code class="language-go">// Wrong
type Service struct {
    ctx context.Context // don't do this
}

// Right
func (s *Service) DoWork(ctx context.Context) error { ... }
</code></pre>
<p><strong>Using</strong> <code>context.WithValue</code> <strong>for function dependencies.</strong> If you're pulling a database client out of a context, something has gone wrong. Use dependency injection or package-level variables for clients and config. Context values are for request-scoped metadata.</p>
<p><strong>Ignoring</strong> <code>ctx.Err()</code> <strong>in loops.</strong> Passing a context to a function that calls <code>ctx.Err()</code> isn't enough if your own loop never checks it. Long-running loops need explicit cancellation checks.</p>
<p><strong>Wrapping errors without checking for cancellation.</strong> When a context-aware call fails, check whether the cause is cancellation before deciding how to handle it:</p>
<pre><code class="language-go">if err != nil {
    if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) {
        // The operation was stopped — not necessarily a bug
        return err
    }
    // Real error — log, alert, or return
    return fmt.Errorf("unexpected error: %w", err)
}
</code></pre>
<hr />
<h2>Summary</h2>
<p>The <code>context</code> package gives every Go operation a standard way to answer three questions: should I keep running, when must I finish by, and what metadata flows with this request?</p>
<p>The key things to take away:</p>
<ul>
<li><p><code>context.Background()</code> is the root — use it at program entry points.</p>
</li>
<li><p><code>WithCancel</code>, <code>WithTimeout</code>, and <code>WithDeadline</code> create derived contexts — always <code>defer cancel()</code>.</p>
</li>
<li><p>Cancellation propagates from parent to child automatically — wire it through your call chain and it works for free.</p>
</li>
<li><p>Pass context as the first argument to every function that does I/O — HTTP, database, AWS SDK, gRPC.</p>
</li>
<li><p><code>WithValue</code> is for cross-cutting metadata like trace IDs, not for dependencies.</p>
</li>
<li><p>Check <code>ctx.Err()</code> explicitly in long-running loops.</p>
</li>
</ul>
]]></content:encoded></item><item><title><![CDATA[Go Packages and Modules explained]]></title><description><![CDATA[What is a package?
In Go, every Go program is made up of packages. A package is a directory of .go files that share the same package declaration. The primary purpose of packages is to help you isolate]]></description><link>https://ferztyle.me/go-packages-and-modules-explained</link><guid isPermaLink="true">https://ferztyle.me/go-packages-and-modules-explained</guid><dc:creator><![CDATA[Fer]]></dc:creator><pubDate>Mon, 01 Jun 2026 13:58:58 GMT</pubDate><enclosure url="https://cdn.hashnode.com/uploads/covers/61db385c3ce4b2377adfdcda/da00503c-8623-4aed-8515-53cba26a39e3.jpg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<h1>What is a package?</h1>
<p>In Go, every Go program is made up of packages. A package is a directory of <code>.go</code> files that share the same <code>package</code> declaration. The primary purpose of packages is to help you isolate and reuse code.</p>
<pre><code class="language-plaintext">myapp/
├── main.go       ← package main
└── math/
    ├── add.go    ← package math
    └── sub.go    ← package math
</code></pre>
<p>Both <code>add.go</code> and <code>sub.go</code> declare <code>package math</code>. They can call each other's functions directly — no import needed within the same package.</p>
<p>Inside a package, every <code>.go</code> file should begin with a <code>package {name}</code> statement which indicates the name of the package that the file is a part of. Every exported identifier (capitalized name) in that directory is accessible to anyone who imports the package.</p>
<p>Here's what that looks like in practice:</p>
<pre><code class="language-go">// math/add.go
package math

// pi is an unexported variable.
var pi = 3.14159

// Add returns the sum of two integers.
// Exported — starts with a capital letter.
func Add(a, b int) int {
    return a + b
}
</code></pre>
<pre><code class="language-go">// math/sub.go
package math

// Exported — starts with a capital letter.
func Subtract(a, b int) int {
    
    return a-b
}
</code></pre>
<pre><code class="language-go">// main.go
package main

import (
    "fmt"
    "github.com/yourname/myapp/math"
)

func main() {
    fmt.Println(math.Add(3, 4))      // 7
    fmt.Println(math.Subtract(10, 3)) // 7
    // fmt.Println(math.pi) — compile error: unexported
}
</code></pre>
<p>Two rules to remember:</p>
<ul>
<li><p>Capital letter = exported (public). Lowercase = unexported (private to the package).</p>
</li>
<li><p>One package per directory. One directory per package.</p>
</li>
</ul>
<hr />
<h1>What is a module?</h1>
<p>If a package is a folder, a module is the whole project — a tree of packages with a name, a Go version requirement, and a list of external dependencies.</p>
<p>When you start a Go project, you create a module, and inside that module, there will be packages.</p>
<p>Every Go project has exactly one <code>go.mod</code> file at its root. That file defines the module. Here's what a real one looks like:</p>
<pre><code class="language-plaintext">module github.com/yourname/weather-cli

go 1.21

require (
    github.com/aws/aws-sdk-go-v2 v1.24.0
    github.com/aws/aws-sdk-go-v2/service/s3 v1.47.0
    gopkg.in/yaml.v3 v3.0.1
)
</code></pre>
<p>Three things in every <code>go.mod</code>:</p>
<ul>
<li><p><code>module</code> — the module path. This is the base import path for every package in your project. It's usually your GitHub URL, but it can be anything.</p>
</li>
<li><p><code>go</code> — the minimum Go version your code requires.</p>
</li>
<li><p><code>require</code> — the external dependencies your module needs, each pinned to an exact version.</p>
</li>
</ul>
<h2>go.sum — the lockfile</h2>
<p>Alongside <code>go.mod</code> lives <code>go.sum</code>. You never edit this by hand. It contains cryptographic checksums for every dependency (and their dependencies) your module uses:</p>
<pre><code class="language-plaintext">github.com/aws/aws-sdk-go-v2 v1.24.0 h1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
github.com/aws/aws-sdk-go-v2 v1.24.0/go.mod h1:xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx=
</code></pre>
<p><code>go.sum</code> guarantees that the code you download is bit-for-bit identical to what was there when you first added the dependency. It's your protection against supply chain attacks and "works on my machine" problems. Always commit both <code>go.mod</code> and <code>go.sum</code>.</p>
<h1>Working with modules day to day</h1>
<h2>Creating a new module</h2>
<p>So, let's say you want to start a new project: you create the folder first, then you create your go.mod file, passing your module path to it (github.com/yourname/myapp).</p>
<pre><code class="language-bash">mkdir myapp &amp;&amp; cd myapp
go mod init github.com/yourname/myapp
</code></pre>
<p>This creates <code>go.mod</code> with your module path. That's it — you're ready to write Go.</p>
<h2>Adding a dependency</h2>
<pre><code class="language-bash">go get github.com/aws/aws-sdk-go-v2/service/s3@latest
</code></pre>
<p><code>go get</code> does three things: downloads the package, adds it to <code>go.mod</code>, and updates <code>go.sum</code>. After running it your <code>go.mod</code> will have a new <code>require</code> entry.</p>
<p>You can also just write the import in your code and run <code>go mod tidy</code> — it figures out what's missing and adds it:</p>
<pre><code class="language-bash">go mod tidy
</code></pre>
<p><code>go mod tidy</code> is the command you'll run most often. It adds missing dependencies and removes unused ones, keeping <code>go.mod</code> clean.</p>
<h2>Upgrading a dependency</h2>
<pre><code class="language-bash"># Upgrade to the latest minor/patch version
go get github.com/aws/aws-sdk-go-v2@latest

# Upgrade to a specific version
go get github.com/aws/aws-sdk-go-v2@v1.25.0
</code></pre>
<h2>Removing a dependency</h2>
<p>Remove the import from your code, then run:</p>
<pre><code class="language-bash">go mod tidy
</code></pre>
<p><code>go mod tidy</code> will remove the <code>require</code> entry automatically if nothing in your code imports it anymore.</p>
<h2>Viewing your dependency tree</h2>
<pre><code class="language-bash">go mod graph
</code></pre>
<p>This prints the full dependency graph — your dependencies, their dependencies, and so on. Useful for debugging version conflicts.</p>
<h2>Vendoring dependencies</h2>
<p>For environments without internet access (some CI setups, air-gapped servers), you can vendor all dependencies into a local <code>vendor/</code> directory:</p>
<pre><code class="language-bash">go mod vendor
</code></pre>
<p>After vendoring, <code>go build</code> uses <code>vendor/</code> instead of the module cache. Commit <code>vendor/</code> to your repo and your build never needs to call the internet.</p>
<hr />
<h1>How modules and packages connect</h1>
<p>Let's make the relationship tangible. Say you run:</p>
<pre><code class="language-bash">go get github.com/spf13/cobra@v1.8.0
</code></pre>
<p>Your <code>go.mod</code> now contains:</p>
<pre><code class="language-plaintext">require (
    github.com/spf13/cobra v1.8.0
)
</code></pre>
<p>Cobra is a <strong>module</strong> at <code>github.com/spf13/cobra</code>. Inside that module, there are multiple <strong>packages</strong>:</p>
<pre><code class="language-plaintext">github.com/spf13/cobra          ← the root package
github.com/spf13/cobra/doc      ← generates documentation
github.com/spf13/cobra/completions ← shell completions
</code></pre>
<p>When you import <code>github.com/spf13/cobra</code> in your code, you're importing one specific package from that module. The module is what gets versioned and downloaded; the package is what you actually use in your code.</p>
<pre><code class="language-go">import (
    "github.com/spf13/cobra"     // imports the root package of the cobra module
)
</code></pre>
<p>One <code>require</code> in <code>go.mod</code>, many importable packages available. That's the module/package split.</p>
<hr />
<h1>Package main is special</h1>
<p>Every Go program needs exactly one <code>package main</code> with exactly one <code>main()</code> function. That's the entry point. Everything else is a library package — it exports functions and types but can't be run directly.</p>
<pre><code class="language-go">// This is a runnable program
package main

import "fmt"

func main() {
    fmt.Println("hello from main")
}
</code></pre>
<pre><code class="language-go">// This is a library — can't be run, only imported
package greet

import "fmt"

func Hello(name string) string {
    return fmt.Sprintf("Hello, %s!", name)
}
</code></pre>
<p>You can have multiple <code>package main</code> files in a project — but each one must live in its own directory. This is exactly what the <code>cmd/</code> convention is for, which we'll get to shortly.</p>
<h1>How imports work</h1>
<p>The import path is always: <strong>module path</strong> (from <code>go.mod</code>) <strong>+ directory path</strong> from the module root.</p>
<pre><code class="language-plaintext">module github.com/yourname/myapp   ← module path

myapp/
├── go.mod
└── internal/
    └── config/
        └── config.go              ← import path: github.com/yourname/myapp/internal/config
</code></pre>
<pre><code class="language-go">// Importing your own packages — always use the full module path
import "github.com/yourname/myapp/internal/config"

// Importing from the standard library — no module path needed
import "fmt"
import "net/http"
import "encoding/json"

// Importing a third-party package — use its full module path + sub-path
import "github.com/spf13/cobra"
import "github.com/aws/aws-sdk-go-v2/service/s3"
</code></pre>
<h2>Aliasing imports</h2>
<p>When two packages have the same name, or when a name is too long, you can alias the import:</p>
<pre><code class="language-go">import (
    "fmt"

    // Alias to avoid conflict with standard library math
    gomath "github.com/yourname/myapp/math"

    // Alias for readability
    yaml "gopkg.in/yaml.v3"
)

func main() {
    fmt.Println(gomath.Add(1, 2))
    _ = yaml.Marshal
}
</code></pre>
<h2>The blank identifier import</h2>
<p>Sometimes you import a package only for its side effects — database drivers are the classic example. The <code>_</code> alias tells Go "import this but don't use its name":</p>
<pre><code class="language-go">import (
    "database/sql"
    _ "github.com/lib/pq" // registers the postgres driver via init()
)
</code></pre>
<p>Without the <code>_</code>, Go's compiler would complain about an unused import and refuse to compile.</p>
<hr />
<h1>A practical project structure</h1>
<p>Here's a real structure that scales from small to large. This is what most production Go projects converge on:</p>
<pre><code class="language-plaintext">myapp/
├── go.mod
├── go.sum
├── Makefile
│
├── cmd/
│   ├── server/
│   │   └── main.go     ← the HTTP server entry point
│   └── worker/
│       └── main.go     ← the background worker entry point
│
├── internal/
│   ├── handler/
│   │   ├── handler.go
│   │   └── handler_test.go
│   ├── store/
│   │   ├── postgres.go
│   │   └── store.go
│   └── config/
│       └── config.go
│
└── pkg/
    └── validate/
        ├── validate.go
        └── validate_test.go
</code></pre>
<p>Let's walk through each directory.</p>
<h2>cmd/ — entry points</h2>
<p>One subdirectory per runnable binary. Each contains a single <code>main.go</code>. The <code>main.go</code> should do almost nothing except wire dependencies together and start the program.</p>
<pre><code class="language-go">// cmd/server/main.go
package main

import (
    "log/slog"
    "net/http"
    "os"

    "github.com/yourname/myapp/internal/config"
    "github.com/yourname/myapp/internal/handler"
    "github.com/yourname/myapp/internal/store"
)

func main() {
    cfg := config.Load()

    db, err := store.Connect(cfg.DatabaseURL)
    if err != nil {
        slog.Error("failed to connect to database", "error", err)
        os.Exit(1)
    }

    h := handler.New(db)

    slog.Info("starting server", "port", cfg.Port)
    if err := http.ListenAndServe(":"+cfg.Port, h); err != nil {
        slog.Error("server failed", "error", err)
        os.Exit(1)
    }
}
</code></pre>
<div>
<div>💡</div>
<div>No business logic in <code>main.go</code>. Ever. It's a wiring diagram, not an application.</div>
</div>

<h2>internal/ — private application code</h2>
<p>The <code>internal/</code> directory is enforced by the Go toolchain. Packages inside <code>internal/</code> can only be imported by code in the parent directory tree. Code outside your module cannot import them — not even if they find your repo on the internet.</p>
<p>This is how you keep implementation details private:</p>
<pre><code class="language-go">// internal/config/config.go
package config

import "os"

type Config struct {
    Port        string
    DatabaseURL string
    LogLevel    string
}

// Load reads config from environment variables.
// This is internal — callers outside this module can't import it.
func Load() Config {
    port := os.Getenv("PORT")
    if port == "" {
        port = "8080"
    }
    return Config{
        Port:        port,
        DatabaseURL: os.Getenv("DATABASE_URL"),
        LogLevel:    os.Getenv("LOG_LEVEL"),
    }
}
</code></pre>
<pre><code class="language-go">// internal/store/store.go
package store

import (
    "context"
    "database/sql"
    "fmt"

    _ "github.com/lib/pq"
)

// Store handles database operations.
type Store struct {
    db *sql.DB
}

func Connect(dsn string) (*Store, error) {
    db, err := sql.Open("postgres", dsn)
    if err != nil {
        return nil, fmt.Errorf("open db: %w", err)
    }
    if err := db.Ping(); err != nil {
        return nil, fmt.Errorf("ping db: %w", err)
    }
    return &amp;Store{db: db}, nil
}

// GetUser fetches a user by ID.
func (s *Store) GetUser(ctx context.Context, id string) (string, error) {
    var name string
    err := s.db.QueryRowContext(ctx, "SELECT name FROM users WHERE id = $1", id).Scan(&amp;name)
    if err != nil {
        return "", fmt.Errorf("get user %s: %w", id, err)
    }
    return name, nil
}
</code></pre>
<h2>pkg/ — reusable public code</h2>
<p><code>pkg/</code> contains packages that are safe to import by external projects. Put things here that are genuinely reusable across projects — validation logic, utility functions, shared types. If in doubt, use <code>internal/</code> instead.</p>
<pre><code class="language-go">// pkg/validate/validate.go
package validate

import (
    "errors"
    "strings"
)

// Email returns an error if the given string is not a valid email address.
// Simple check — not RFC 5322 compliant, but good enough for most cases.
func Email(email string) error {
    if email == "" {
        return errors.New("email is required")
    }
    if !strings.Contains(email, "@") || !strings.Contains(email, ".") {
        return errors.New("email is invalid")
    }
    return nil
}

// Required returns an error if the string is empty or whitespace only.
func Required(field, value string) error {
    if strings.TrimSpace(value) == "" {
        return fmt.Errorf("%s is required", field)
    }
    return nil
}
</code></pre>
<hr />
<h1>A practical example: a CLI tool</h1>
<p>GitHub Repository: <a href="https://github.com/FerRiosCosta/weather-cli">https://github.com/FerRiosCosta/weather-cli</a></p>
<p>Let's make this concrete with a small but complete project — a CLI that fetches weather for a city. It shows package structure, separation of concerns, and how packages talk to each other. Let's create the following directory structure:</p>
<pre><code class="language-plaintext">weather-cli/
├── go.mod
├── cmd/
│   └── weather/
│       └── main.go
└── internal/
    ├── api/
    │   └── api.go        ← HTTP client for weather API
    └── display/
        └── display.go    ← formats output for the terminal
</code></pre>
<p>Open your terminal and create the following directories and files:</p>
<pre><code class="language-shell">mkdir weather-cli
cd weather-cli
mkdir -p cmd/weather
touch cmd/weather/main.go
mkdir -p internal/api
touch internal/api/api.go
mkdir -p internal/display
touch internal/display/display.go
</code></pre>
<p>Open VSCode while you are in the weather-cli root directory:</p>
<pre><code class="language-shell">code .
</code></pre>
<p>Go to the <a href="https://openweathermap.org/faq#error401">Openweathermap</a> portal and create an account if you don't have one. Once you have created it, your API key will be enabled after a couple of hours.</p>
<p>Open a new terminal from VScode since we are going to run our program from here, but first we need to export our API KEY.</p>
<pre><code class="language-shell">export WEATHER_API_KEY=&lt;your-api-key&gt;
</code></pre>
<p>Now, initialize your module (or project) by running the following command:</p>
<pre><code class="language-shell">go mod init
</code></pre>
<p>You should see your new go.mod file created now:</p>
<pre><code class="language-go.mod">module github.com/FerRiosCosta/weather-cli

go 1.26.3
</code></pre>
<p>Start filling the files you created with the code below:</p>
<pre><code class="language-go">// internal/api/api.go
package api

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

// WeatherResponse holds the data we care about from the API.
type WeatherResponse struct {
	City        string  `json:"name"`
	Temperature float64 `json:"main.temp"`
	Description string  `json:"weather.0.description"`
}

// apiResponse mirrors the actual nested JSON structure from OpenWeatherMap.
// Go's JSON decoder doesn't support dot notation like `json:"main.temp"` —
// nested fields require nested structs.
//
// The API returns something like:
//
//	{
//	  "name": "Asuncion",
//	  "main": { "temp": 28.4 },
//	  "weather": [{ "description": "partly cloudy" }]
//	}
type apiResponse struct {
	Name string `json:"name"`
	Main struct {
		Temp float64 `json:"temp"`
	} `json:"main"`
	Weather []struct {
		Description string `json:"description"`
	} `json:"weather"`
}

// Client makes requests to the weather API.
type Client struct {
	apiKey     string
	httpClient *http.Client
}

// NewClient creates and returns a new Client configured with the given API key.
// This is a constructor function — Go doesn't have classes or `new` keywords,
// so by convention we define a function named New&lt;TypeName&gt; to build a struct.
func NewClient(apiKey string) *Client {
	return &amp;Client{
		// Store the API key so every request this client makes can use it.
		// It's set once here and reused — no need to pass it on every call.
		apiKey: apiKey,
		// Create a default HTTP client for making requests.
		// We initialize it here rather than inside GetWeather so it's
		// reused across calls — http.Client maintains a connection pool
		// internally, so reusing it is more efficient than creating a new
		// one each time.
		httpClient: &amp;http.Client{},
	}
}

// GetWeather fetches current weather for the given city.
func (c *Client) GetWeather(ctx context.Context, city string) (*WeatherResponse, error) {
	url := fmt.Sprintf(
		"https://api.openweathermap.org/data/2.5/weather?q=%s&amp;appid=%s&amp;units=metric",
		city, c.apiKey,
	)

	req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
	if err != nil {
		return nil, fmt.Errorf("create request: %w", err)
	}

	resp, err := c.httpClient.Do(req)
	if err != nil {
		return nil, fmt.Errorf("fetch weather: %w", err)
	}
	defer resp.Body.Close()

	if resp.StatusCode != http.StatusOK {
		return nil, fmt.Errorf("unexpected status: %d", resp.StatusCode)
	}

	// Decode into the nested API struct first.
	var raw apiResponse
	if err := json.NewDecoder(resp.Body).Decode(&amp;raw); err != nil {
		return nil, fmt.Errorf("decode response: %w", err)
	}

	// Guard against an empty weather array — the API should always return
	// at least one entry, but defensive code is good code.
	description := ""
	if len(raw.Weather) &gt; 0 {
		description = raw.Weather[0].Description
	}

	// Return the clean, flat struct callers actually care about.
	return &amp;WeatherResponse{
		City:        raw.Name,
		Temperature: raw.Main.Temp,
		Description: description,
	}, nil
}
</code></pre>
<pre><code class="language-go">// internal/display/display.go
package display

import (
    "fmt"
    "io"

    "github.com/yourname/weather-cli/internal/api"
)

// Weather prints a formatted weather summary to w.
func Weather(w io.Writer, data *api.WeatherResponse) {
    fmt.Fprintf(w, "City:        %s\n", data.City)
    fmt.Fprintf(w, "Temperature: %.1f°C\n", data.Temperature)
    fmt.Fprintf(w, "Conditions:  %s\n", data.Description)
}
</code></pre>
<pre><code class="language-go">// cmd/weather/main.go
package main

import (
    "context"
    "log/slog"
    "os"

    "github.com/yourname/weather-cli/internal/api"
    "github.com/yourname/weather-cli/internal/display"
)

func main() {
    if len(os.Args) &lt; 2 {
        fmt.Fprintln(os.Stderr, "usage: weather &lt;city&gt;")
        os.Exit(1)
    }

    city := os.Args[1]
    apiKey := os.Getenv("WEATHER_API_KEY")
    if apiKey == "" {
        slog.Error("WEATHER_API_KEY environment variable is required")
        os.Exit(1)
    }

    client := api.NewClient(apiKey)

    weather, err := client.GetWeather(context.Background(), city)
    if err != nil {
        slog.Error("failed to get weather", "city", city, "error", err)
        os.Exit(1)
    }

    display.Weather(os.Stdout, weather)
}
</code></pre>
<p>Run it:</p>
<pre><code class="language-bash">go run ./cmd/weather Asuncion
</code></pre>
<pre><code class="language-plaintext">City:        Asuncion
Temperature: 28.4°C
Conditions:  partly cloudy
</code></pre>
<p>Notice how each package has one job:</p>
<ul>
<li><p><code>api</code> — knows how to talk to the weather API. Nothing else.</p>
</li>
<li><p><code>display</code> — knows how to format output. Nothing else.</p>
</li>
<li><p><code>cmd/weather/main.go</code> — wires them together. Nothing else.</p>
</li>
</ul>
<p>Adding a second output format (JSON, CSV) means a new file in <code>display/</code>. Adding a second weather provider means a new file in <code>api/</code>. Neither change touches the other.</p>
<hr />
<h1>Common gotchas</h1>
<p><strong>1. Circular imports — Go forbids them</strong></p>
<p>Package A cannot import package B if package B imports package A. Go will refuse to compile.</p>
<pre><code class="language-plaintext">// This will not compile
package a imports package b
package b imports package a  ← circular — error
</code></pre>
<p>The fix: extract the shared type into a third package that neither A nor B imports from each other.</p>
<p><strong>2. Package name vs directory name</strong></p>
<p>The directory name and the package name don't have to match — but they should. The one common exception is <code>main</code>: the directory is usually named after the binary (<code>server</code>, <code>worker</code>, <code>weather</code>), but the package is always <code>package main</code>.</p>
<pre><code class="language-go">// Directory: cmd/server/
// File: main.go
package main  // always "main", not "server"
</code></pre>
<p><strong>3. Trying to have multiple packages in one directory</strong></p>
<p>Every <code>.go</code> file in a directory must declare the same package name (with the exception of <code>_test.go</code> files, which can use <code>package foo_test</code> for black-box testing). This is a compile error:</p>
<pre><code class="language-plaintext">myapp/
├── server.go    ← package server
└── handler.go   ← package handler  ← ERROR: can't mix packages in one directory
</code></pre>
<p><strong>4. Exporting by accident</strong></p>
<p>If you capitalize a function name, it's exported — immediately accessible to anyone who imports the package. This isn't always what you want, especially for internal helpers. Default to lowercase until you know something needs to be public.</p>
<hr />
<h1>Summary</h1>
<p>Go's package and module system is built on a few simple rules that compound into something powerful.</p>
<p>Four things to take away:</p>
<ul>
<li><p>A <strong>package</strong> is a folder — everything in it shares a namespace, capital letters are exported.</p>
</li>
<li><p>A <strong>module</strong> is a versioned collection of packages — defined by <code>go.mod</code>, locked by <code>go.sum</code>.</p>
</li>
<li><p>Use <code>cmd/</code> for entry points, <code>internal/</code> for private code, <code>pkg/</code> for reusable public code.</p>
</li>
<li><p><code>go mod tidy</code> is your best friend — run it whenever you add, remove, or change dependencies.</p>
</li>
</ul>
<p>The project structure in this post is the same one I'll use for every project on this blog. Every Go + Lambda, Go + Kubernetes, and Go + AWS post builds on it. Once it's familiar, you'll recognize it instantly in any Go project you open on GitHub.</p>
]]></content:encoded></item></channel></rss>