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)
| Verb | Meaning | Example |
|---|---|---|
%s | String | fmt.Sprintf("hi %s", name) |
%d | Integer | fmt.Sprintf("count: %d", 42) |
%v | Default format | fmt.Sprintf("val: %v", anything) |
%w | Wrap error | fmt.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
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:
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:
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
| Function | Direction | Example |
|---|---|---|
strconv.Itoa(n) | int → string | Itoa(100) → "100" |
strconv.Atoi(s) | string → int | Atoi("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:
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.
The os package provides program-level operations:
fmt.Fprintf(os.Stderr, "error: %v\n", err)
os.Exit(1)os.Stderr is the standard error stream. os.Exit(1) terminates with a non-zero exit code (indicating failure). These appear in main.go for fatal startup errors. In baton connectors, you’d use zap for logging errors rather than writing to stderr directly (as you saw in Level 2).
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!
Next Lesson
Protobuf & gRPC

