Skip to main content

ikwukao

Back to Journal

Engineering Journal

Building a Reliable REST API Client in Go for PokedexCLI

How PokedexCLI uses Go's HTTP primitives to communicate with a REST API, decode structured responses, handle failures, and keep network concerns isolated from command logic.

6 min read Go, Golang, REST API, HTTP, API Client, PokedexCLI, Backend Engineering, Networking

Building a Reliable REST API Client in Go for PokedexCLI

A command-line application becomes considerably more interesting when it has to communicate with a remote service.

PokedexCLI does not contain all of its data locally. Much of the information the application needs comes from the Pokémon API, which means the application has to deal with HTTP requests, JSON responses, network failures, pagination, and external service boundaries.

Rather than allowing every command to construct its own HTTP requests, I treated API communication as a dedicated responsibility.

The result is a small but important architectural boundary:

CLI Command
API Client
HTTP
Pokémon API

That separation keeps command implementations focused on application behavior instead of networking details.

Why Build an API Client?

It is possible to make an HTTP request directly from a command handler.

For a small application, that might initially seem reasonable:

resp, err := http.Get(url)

The problem appears when several commands need the API.

Soon, request construction, status-code handling, JSON decoding, and error handling begin appearing in multiple places.

That creates duplicated logic.

A dedicated client provides one place for those concerns.

Instead of commands thinking about HTTP mechanics, they can think in terms of operations such as:

GetLocations()
GetLocationArea()
GetPokemon()

That is a much more useful abstraction for the application.

The API Client as a Boundary

The client sits between the application and the external service.

Application
API Client
     ├── Request construction
     ├── HTTP transport
     ├── Status handling
     ├── JSON decoding
     └── Error propagation
        External API

This boundary has an important property: changes to the external API do not automatically have to propagate through the entire application.

If the API response format changes, the client and its data models are the first places that need attention.

Modeling JSON Responses in Go

One of the strengths of Go for API clients is its straightforward JSON support.

An API response can be represented using structs.

For example:

type LocationArea struct {
    Name string `json:"name"`
    URL  string `json:"url"`
}

The JSON tags define how fields map to the remote representation.

This creates a clean transition:

JSON
Go struct
Application logic

The rest of the application no longer needs to work with raw JSON.

Keeping Network Logic Out of Commands

A command should not have to understand the details of decoding an HTTP response.

A cleaner flow is:

explore command
API client
HTTP request
JSON response
Go model
command output

The command asks for information.

The client retrieves and decodes it.

This makes both components easier to reason about.

HTTP Status Codes Matter

A successful HTTP request does not necessarily mean the operation succeeded.

An HTTP request can complete successfully while the server returns:

  • 400 Bad Request
  • 401 Unauthorized
  • 403 Forbidden
  • 404 Not Found
  • 429 Too Many Requests
  • 500 Internal Server Error

A useful API client therefore needs to distinguish transport success from application-level success.

The general sequence is:

Send request
Did transport succeed?
Check HTTP status
Decode response
Return application data

Skipping status validation can result in confusing downstream failures.

Handling Network Errors

External systems are inherently unreliable.

DNS failures, connection problems, timeouts, interrupted connections, and remote server failures can all occur.

The client should return those errors rather than hiding them.

For example:

resp, err := httpClient.Do(req)
if err != nil {
    return nil, err
}
defer resp.Body.Close()

The important principle is that failures should remain visible to the layer responsible for deciding how they should be presented.

Closing Response Bodies

HTTP response bodies represent resources.

They should be closed when processing is complete:

defer resp.Body.Close()

This is a small implementation detail with significant practical value.

A CLI may make many requests during one interactive session. Consistently releasing resources helps prevent unnecessary connection and resource consumption.

Pagination and URLs

The Pokémon API exposes paginated resources.

That means the API client should not assume that all resources arrive in one response.

A paginated response can conceptually look like:

Current Page
 ├── results
 ├── next
 └── previous

The application can preserve those navigation URLs as part of its state.

This allows commands such as:

map

and:

mapb

to move forward and backward through the remote dataset.

The API client retrieves the requested page; the application state determines where the user currently is.

Avoiding Hidden API Knowledge

A command such as map should not need to know how pagination is encoded by the remote API.

Ideally, the command interacts with a client method and receives structured application data.

This keeps external API details contained.

That matters because external APIs are dependencies, not part of the application’s core domain.

API Clients and Testability

A clean API boundary also improves testing.

If network logic is scattered throughout the application, tests have to understand HTTP implementation details.

If the network boundary is isolated, the rest of the application can be tested independently.

The goal is not to create an enormous mocking framework.

It is simply to keep responsibilities separated enough that tests do not require a real external service for every behavior.

Data Transformation

The API response is not always the exact representation the CLI needs.

The client can decode remote data into Go structures, after which application logic can select the information relevant to the user.

For example:

Remote API response
Go response model
Application representation
CLI output

This avoids coupling presentation directly to the external JSON schema.

Error Propagation

One of the simplest and most effective patterns in Go is explicit error propagation.

data, err := client.GetLocation(url)
if err != nil {
    return err
}

There is no hidden exception system.

The failure is explicit.

That makes control flow easier to follow, particularly in applications where several operations depend on external services.

What I Learned From the API Layer

Building the API client reinforced an important backend engineering principle:

External systems deserve explicit boundaries.

The Pokémon API is outside the application’s control.

It can be slow.

It can fail.

Its response structure can change.

Its availability cannot be assumed.

By keeping it behind a dedicated client, the rest of PokedexCLI can remain focused on the application’s behavior.

Final Thoughts

The API client in PokedexCLI is relatively small, but it represents a pattern that scales well beyond a learning project.

Production backend systems frequently sit between an application and external services.

The same principles apply:

  • isolate network concerns;
  • model external data explicitly;
  • validate responses;
  • propagate errors;
  • release resources;
  • separate remote representations from application behavior.

PokedexCLI provided a compact environment in which to practice those fundamentals.

The code may be small, but the engineering lessons are applicable to considerably larger systems

  • Go HTTP clients
  • REST API design
  • JSON decoding in Go
  • Backend networking
  • API error handling
  • Go application architecture

ikwukao

Backend • Distributed Systems • Platform Engineering

I design and build reliable backend systems, distributed platforms, cloud-native infrastructure, and developer tooling with Go, Python, Linux, Kubernetes, and modern DevOps practices.

Building reliable systems from Nigeria · GMT+1

Explore

Connect

© 2026 Ikwuka Okoye. All rights reserved.

Built with Hugo, Tailwind CSS & Go-inspired simplicity.