Blog Logo

16-Jun-2025 ~ 3 min read

Chaining POST Requests in Go with JSON - part 1


Chaining POST Requests in Go with JSON: Part 1

Sometimes when I’m building backend APIs, I need to receive a JSON request, extract some values, and then pass modified or related data to another service, usually another API I control or an external one. This is especially common in middleware services or proxy layers. I recently worked on a pattern like this, and to make it reusable, I generalized it using placeholders foo and foo_id.

Table of Contents

Code

func fooStatusAPIHandler(w http.ResponseWriter, r *http.Request) {
	println("Received request for foo status API")

	if r.Method != http.MethodPost {
		http.Error(w, "Method not allowed", http.StatusMethodNotAllowed)
		return
	}

	var req struct {
		FooID string `json:"foo_id"`
	}

	if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
		http.Error(w, "Invalid JSON", http.StatusBadRequest)
		return
	}

	postParams := map[string]string{
		"foo_id": req.FooID,
		"url": "https://example.com/foo-backend/",
	}

	postBody, err := json.Marshal(postParams)
	if err != nil {
		http.Error(w, "Failed to marshal post parameters", http.StatusInternalServerError)
		return
	}

	resp, err := http.Post(
		FOO_ANOTHER_ENDPOINT,
		"application/json",
		bytes.NewBuffer(postBody),
	)
	if err != nil {
		http.Error(w, "Backend request failed", http.StatusInternalServerError)
		return
	}
	defer resp.Body.Close()

	w.Header().Set("Content-Type", "application/json")
	w.WriteHeader(resp.StatusCode)
	io.Copy(w, resp.Body)
}

🧩 The Problem

I had an endpoint receiving a POST request containing a JSON body. The client sent an ID, let’s call it foo_id. Based on that, I needed to call another service, passing either the same foo_id or some derived data, and finally, return the response from that backend service to the original client.

🔨 The Solution

Here’s the basic structure of how I handled it:

  1. Accept a POST request with a JSON payload.
  2. Decode the JSON into a simple struct.
  3. Prepare a new JSON body for the backend POST request.
  4. Make the POST request using Go’s http.Post.
  5. Pipe the response back to the original client.

💡 Code Walkthrough

The core struct looks like this:

var req struct {
	FooID string `json:"foo_id"`
}

I decode the incoming JSON into req. Once decoded, I prepare postParams, which could be just a static set of values, or could include the foo_id if needed. Here’s a simple version:

postParams := map[string]string{
	"url": "https://example.com/foo-backend/",
}

Then I encode postParams into JSON and fire off a POST request:

resp, err := http.Post(
	FOO_BACKEND_RENDER_ENDPOINT,
	"application/json",
	bytes.NewBuffer(postBody),
)

Finally, I stream the response back to the original client, setting the appropriate headers and copying the body directly:

io.Copy(w, resp.Body)

🧪 Why This Is Useful

This pattern is extremely flexible. You can use it to:

  • Call third-party APIs after sanitizing or enriching input.

  • Implement internal microservice coordination.

  • Act as a proxy where the logic for forwarding lives in code, not in config.

And because the core logic deals only with JSON decoding, encoding, and HTTP requests, it remains lightweight and maintainable.

🧼 Closing Thoughts

If you find yourself working with layered services, you’ll inevitably need to chain JSON-based HTTP requests. Generalizing your handler logic, like I did here with foo and foo_id, makes it easy to replicate this pattern for various entities without repeating yourself.

Let me know if you’ve built similar request-chaining workflows, or if you see areas where this could be made even cleaner!