Level 8 /

Standard Library

A good builder knows their tools. Gopher steps into the tool shed, where every tool hangs on a labeled pegboard: fmt, net/url, time, encoding/json. Go's standard library is a workshop full of reliable, well-made tools, and unlike most toolsheds, everything is organized.

Gopher says

Go ships with a powerful standard library - no npm install needed for URL parsing, HTTP clients, JSON encoding, or string conversion. Most of what you need is built in.

In baton-junction, we typically use stdlib packages like fmt, net/url, net/http, encoding/json, strconv, time, and os. Let’s tour each one.

Formatted I/O: fmt

The fmt package is Go’s Swiss Army knife for building strings, wrapping errors, and writing output. You’ll reach for it constantly. Three functions cover 90% of usage:

// Build a string
path := fmt.Sprintf("/api/users/%s", userID)

// Wrap an error (preserving the chain)
return fmt.Errorf("baton-junction: failed to get user: %w", err)

// Write to stderr
fmt.Fprintf(os.Stderr, "error: %v\n", err)
VerbMeaningExample
%sStringfmt.Sprintf("hi %s", name)
%dIntegerfmt.Sprintf("count: %d", 42)
%vDefault formatfmt.Sprintf("val: %v", anything)
%wWrap errorfmt.Errorf("failed: %w", err)

The %w verb is special - it only works in fmt.Errorf and preserves the error chain for errors.Is.

URL Parsing: net/url

baton-junction’s doGet method builds paginated URLs:

u, err := url.Parse(c.baseURL + path)
if err != nil {
    return fmt.Errorf("baton-junction: invalid URL: %w", err)
}

q := u.Query()                              // get existing query params
q.Set("limit", strconv.Itoa(defaultPageSize))  // add pagination
if cursor != "" {
    q.Set("cursor", cursor)
}
u.RawQuery = q.Encode()                     // write params back
parse existing paramsadd a parameterencode back to URL
q := u.Query()
q.Set("limit", "100")
u.RawQuery = q.Encode()

Never build query strings by hand - url.Values handles encoding special characters.

HTTP Requests: net/http

Go ships with a full HTTP client and server in the standard library. No third-party dependency needed. The client uses HTTP methods as constants and builds requests with context:

contextHTTP method constanttarget URLrequest options
req, err := client.NewRequest(
    ctx,
    http.MethodGet,
    url,
    options...,
)
req, err := c.httpClient.NewRequest(ctx, http.MethodGet, u,
    uhttp.WithAcceptJSONHeader(),
)

Common constants: http.MethodGet, http.MethodPost, http.MethodPut, http.MethodDelete.

Variadic Parameters and the Spread Operator

You may have noticed options... in the whitebox above. The ... syntax shows up in two contexts in Go, and both are related to variable-length arguments.

In a function signature, ...Type means the function accepts zero or more arguments of that type. They arrive as a slice inside the function:

keywordfunction nameregular parametervariadic (zero or more)
func NewResource(name string, opts ...Option)

When calling such a function with an existing slice, you append ... after the slice to “spread” it into individual arguments:

opts := []resource.UserTraitOption{resource.WithEmail(u.Email, true)}
r, err := resource.NewUserResource(name, resourceType, id, opts...)

Without the trailing ..., Go would try to pass the entire slice as a single argument and the compiler would reject it.

Type Conversions: strconv

URL query params and JSON numbers arrive as strings; APIs often want integers. You need a reliable way to convert between them. Convert between strings and numbers:

q.Set("limit", strconv.Itoa(defaultPageSize))  // int → string
FunctionDirectionExample
strconv.Itoa(n)int → stringItoa(100)"100"
strconv.Atoi(s)string → intAtoi("100")100, nil

Atoi returns an error because the string might not be a valid number.

Timestamps: time

Go’s time package handles dates, durations, and timezones without external libraries. API responses typically include created_at, updated_at, and similar fields. The User struct uses time.Time for timestamps:

type User struct {
    CreatedAt time.Time  `json:"created_at"`          // always present
    LastLogin *time.Time `json:"last_login,omitempty"` // optional (nil = never)
}

Use *time.Time for optional fields - a plain time.Time has a zero value of year 0001, which is misleading.

JSON Encoding: encoding/json

Connectors talk to APIs that speak JSON. Struct tags tell Go how to map your fields to JSON keys and how to handle optional values. Struct tags control serialization:

keywordGo field nameGo typeJSON key mappingomitempty option
type User struct {
    FirstName  string `json:"first_name"`
    Department string `json:"department,omitempty"`
}
type User struct {
    FirstName  string `json:"first_name"`
    Department string `json:"department,omitempty"`

baton-junction delegates JSON handling to the SDK’s HTTP client, but the tags drive all the serialization behavior.

Quick Check

What fmt verb wraps an error while preserving the error chain?

Exercise: Build a Paginated URL

Implement BuildURL: parse the base URL and path into a URL, add a "limit" query parameter from the page size, add a "cursor" parameter if one was provided, and return the final URL string. Handle any errors.

Exercise: Format Error Messages

Implement WrapError: return a new error that combines the prefix and the original error. Use error wrapping so the original error is preserved in the chain.

Gopher says

You’ve now covered the core stdlib packages that appear in most connectors. With fmt, net/url, net/http, encoding/json, strconv, time, and os, you have the essential toolkit for building Go applications.

In baton-junction, these packages handle everything from URL construction in the client to JSON decoding of API responses to error formatting in every method.

Next: protobuf and the SDK type system!

Explore the related connector code files

Next Lesson

Protobuf & gRPC

Continue