Back to API Recorder

README

API Recorder - Local HTTP Recording & Playback Tool

A simple, professional Go application for recording and replaying HTTP interactions to enable offline development and testing during API migrations. Perfect for AI-assisted porting of services.

🎯 Purpose

This tool solves a specific problem: migrating from CRM v1 (Groovy) to CRM v2 (Go) without constant access to external APIs.

The AI Migration Workflow

  • Record: Capture all HTTP interactions from CRM v1
  • Analyze: AI examines v1 code + API specs + recordings
  • Generate: AI creates functionally identical v2 in a different language
  • Validate: Test v2 against recordings (completely offline!)
  • Key Insight: With recorded HTTP interactions, AI can perfectly replicate API behavior without ever accessing the original external services.

    🚀 Quick Start

    bash
    # 1. Clone the repository
    git clone https://github.com/jonathanleahy/proxyy.git
    cd proxyy
    
    # 2. Run the recorder in record mode
    MODE=record go run main.go
    
    # 3. Configure your CRM to use the proxy (see below)
    
    # 4. Make some API calls to record them
    
    # 5. Switch to playback mode for offline testing
    MODE=playback go run main.go

    📋 Features

    • Zero Code Changes - Integration through configuration only
    • Complete Header Preservation - All headers recorded and matched
    • Simple URL-Based Proxy - Just prefix your API URLs
    • Offline Development - Work without external dependencies
    • Perfect for AI Migration - Provides ground truth for service behavior

    🏗️ Architecture

    shell
    Recording Mode:
    Your App → localhost:8080/proxy?url=https://api.com → External API
                  ↓ (saves request/response)
             ./recordings/
    
    Playback Mode:
    Your App → localhost:8080/proxy?url=https://api.com
                  ↓ (returns recorded response)
             ./recordings/

    🔧 Configuration

    CRM v1 (Groovy) - application.yml

    yaml
    # Original
    external-apis:
      customer:
        base-url: https://api.customer.com
    
    # With recorder - just add proxy prefix
    external-apis:
      customer:
        base-url: http://localhost:8080/proxy?url=https://api.customer.com

    CRM v2 (Go) - .env

    bash
    # Original
    CUSTOMER_API=https://api.customer.com
    
    # With recorder
    CUSTOMER_API=http://localhost:8080/proxy?url=https://api.customer.com

    That's it! No code changes needed.

    📝 How It Works

    Recording Structure

    Each recording captures:
    • HTTP method, URL, headers, and body
    • Complete response with all headers
    • Request/response timing
    • Service identification

    json
    {
      "request": {
        "method": "GET",
        "url": "https://api.customer.com/customers/123",
        "headers": {
          "Authorization": ["Bearer token123"],
          "Content-Type": ["application/json"]
        },
        "body": ""
      },
      "response": {
        "status_code": 200,
        "headers": {
          "Content-Type": ["application/json"]
        },
        "body": "{\"id\":\"123\",\"name\":\"John Doe\"}"
      }
    }

    Matching Logic

    Requests are matched using a SHA256 hash of:
    • HTTP Method
    • URL
    • Headers (excluding volatile headers like Date)
    • Request body
    This ensures the exact same request returns the exact same response.

    🧪 Testing Approach

    TDD Unit Tests

    go
    func TestGenerateHash_SameInputProducesSameHash(t *testing.T) {
        recorder := NewRecorder(Config{})
    
        headers := map[string][]string{
            "Authorization": {"Bearer token"},
            "Content-Type": {"application/json"},
        }
    
        hash1 := recorder.generateHash("GET", "/api/test", headers, []byte("body"))
        hash2 := recorder.generateHash("GET", "/api/test", headers, []byte("body"))
    
        assert.Equal(t, hash1, hash2)
    }
    
    func TestServeRecordedResponse_ReturnsCorrectResponse(t *testing.T) {
        recorder := NewRecorder(Config{Mode: "playback"})
    
        // Add a recording
        interaction := &RecordedInteraction{
            Request: RequestData{
                Method: "GET",
                URL: "https://api.test.com/data",
            },
            Response: ResponseData{
                StatusCode: 200,
                Body: `{"status":"ok"}`,
            },
        }
        hash := "testhash"
        recorder.recordings[hash] = interaction
    
        // Test serving it
        w := httptest.NewRecorder()
        recorder.serveRecordedResponse(w, hash)
    
        assert.Equal(t, 200, w.Code)
        assert.JSONEq(t, `{"status":"ok"}`, w.Body.String())
    }

    BDD Feature Tests

    gherkin
    Feature: HTTP Request Recording
      As a developer
      I want to record HTTP requests and responses
      So that I can replay them offline
    
      Scenario: Recording a GET request
        Given the recorder is in "record" mode
        When I make a GET request to "/proxy?url=https://api.example.com/users/1"
        Then the request should be forwarded to the external API
        And the response should be saved to disk
        And the client should receive the original response
    
      Scenario: Replaying a recorded request
        Given the recorder is in "playback" mode
        And I have a recorded response for GET "/users/1"
        When I make the same request
        Then I should receive the recorded response
        And no external request should be made

    🛠️ API Endpoints

    Proxy Endpoint

    shell
    GET/POST/PUT/DELETE http://localhost:8080/proxy?url={targetUrl}

    Admin Endpoints

    shell
    GET  http://localhost:8081/api/mode          # Get current mode
    POST http://localhost:8081/api/mode          # Set mode {"mode": "record|playback"}
    GET  http://localhost:8081/api/recordings    # List all recordings
    GET  http://localhost:8081/api/recording/{hash}  # Get specific recording

    📂 Project Structure

    shell
    proxyy/
    ├── README.md                    # This file
    ├── main.go                      # Complete recorder implementation
    ├── main_test.go                 # Unit tests
    ├── features/                    # BDD tests
    │   ├── recording.feature
    │   └── playback.feature
    ├── recordings/                  # Stored recordings (auto-created)
    ├── scripts/
    │   ├── start-record.sh
    │   └── start-playback.sh
    └── doc/
        └── api-migration-project.md # Test project demonstrating usage

    💡 AI-Assisted Migration Example

    The power of this approach is that AI can create a functionally identical service in a different language using only:

  • Original Code (CRM v1 in Node.js/Groovy)
  • API Specification (Swagger/OpenAPI)
  • Recorded Interactions (from this tool)
  • The AI never needs access to the actual external APIs! It learns the expected behavior from the recordings and can generate code that behaves identically.

    See doc/api-migration-project.md for a complete example with:

    • Mock external services
    • CRM v1 implementation (Node.js)
    • CRM v2 implementation (Go)
    • Test scripts to validate the migration

    🔍 Environment Variables

    bash
    MODE=record|playback      # Operating mode (default: record)
    STORAGE_PATH=./recordings # Where to store recordings (default: ./recordings)
    PROXY_PORT=:8080         # Proxy port (default: :8080)
    ADMIN_PORT=:8081         # Admin API port (default: :8081)
    DEBUG=true|false         # Enable debug logging (default: false)

    📚 Complete Implementation

    The full implementation is in main.go. Here are the key components:

    Core Types

    go
    type RecordedInteraction struct {
        Request  RequestData  `json:"request"`
        Response ResponseData `json:"response"`
        Metadata Metadata     `json:"metadata"`
    }
    
    type Recorder struct {
        config     Config
        recordings map[string]*RecordedInteraction
        mu         sync.RWMutex
    }

    Main Functions

    • generateHash() - Creates deterministic hash for request matching
    • ProxyHandler() - Routes requests to record or playback
    • recordAndForward() - Records while proxying to external API
    • serveRecordedResponse() - Returns recorded response in playback mode
    • saveRecording() - Persists to disk
    • loadRecording() - Loads from disk

    🧑‍💻 Development Workflow

  • Record Phase
  • bash
       # Start recorder
       MODE=record go run main.go
    
       # Run your integration tests or manual testing
       # All HTTP traffic through the proxy is recorded
       

  • Playback Phase
  • bash
       # Switch to playback
       MODE=playback go run main.go
    
       # Run the same tests - now completely offline
       # Responses come from recordings
       

  • Migration Validation
  • bash
       # Test that v2 behaves identically to v1
       # Using the same recorded responses
       

    🤝 Contributing

    This is a simple, focused tool. Contributions that maintain simplicity while adding value are welcome:

  • Fork the repository
  • Create your feature branch
  • Write tests (TDD style)
  • Ensure all tests pass
  • Submit a pull request
  • 📄 License

    MIT License - See LICENSE file for details

    🔗 Related


    Remember: The goal is simple, professional, testable code that solves a real problem - enabling offline API migration testing and AI-assisted service porting.

    © 2026 Jonathan Leahy · v1.0.9