Porting Google Apps Script to Go: A Case Study

🌐 한국어

I ran a stock trading web console in Google Apps Script (GAS) for years. Free tier, "publish as web app" in one click—hard to beat as a start. But as the bot matured, I hit walls. Eventually, I ported the whole thing to Go. Here's what I learned.

Apps Script Architecture and Its Limits

A GAS web app has exactly two entry points:

function doGet(e)  { /* GET request → HTML response */ }

function doPost(e) { /* POST request → JSON handling */ }

Simple, but for trading automation this simplicity becomes a cage:

  • 6-minute execution limit — any function call over 6 minutes (shorter for triggers) terminates forcibly. You can't run an infinite trading loop.
  • No state persistence — memory dies between calls. State must be read from and written to PropertiesService or Sheets each time.
  • No concurrency control — overlapping triggers easily cause duplicate executions of the same logic.
  • Debugging and versioning friction — local toolchains, git, and testing aren't natural.

Migration Strategy — Routing as the Bridge

Map GAS's two functions directly to Go's net/http routing:

func main() {

    http.HandleFunc("/", handleHome)               // doGet → HTML

    http.HandleFunc("/api", handleAPI)             // doPost → JSON

    http.Handle("/static/", http.StripPrefix("/static/",

        http.FileServer(http.Dir("./static/"))))

    log.Fatal(http.ListenAndServe(":9329", nil))

}

Code that branched on e.parameter.action in GAS now decodes the request body and switches in Go. Keep the action names identical; the frontend doesn't change:

type APIRequest struct {

    Action  string      `json:"action"`

    Payload interface{} `json:"payload"`

}

func handleAPI(w http.ResponseWriter, r *http.Request) {

    if r.Method != http.MethodPost {

        respondWithError(w, "Method not allowed", http.StatusMethodNotAllowed)

        return

    }

    var req APIRequest

    if err := json.NewDecoder(r.Body).Decode(&req); err != nil {

        respondWithError(w, "Invalid JSON format", http.StatusBadRequest)

        return

    }

    switch req.Action {

    case "getParameters":

        handleGetParameters(w, r)

    case "saveParameters":

        handleSaveParameters(w, req.Payload)

    case "startBot":

        handleStartBot(w)

    case "stopBot":

        handleStopBot(w)

    default:

        respondWithError(w, "Unknown action", http.StatusBadRequest)

    }

}

Match the response format too. Keep { status, data, ok, error } structure; the existing JS client just works:

Reclaiming State — A Bot That Actually Runs

The biggest win: the code now owns long-running state directly. In GAS, impossible. In Go, "start the bot and let it run in the background" is natural:

func handleStartBot(w http.ResponseWriter) {

    botInstance.mu.Lock()

    defer botInstance.mu.Unlock()

    ctx, cancel := context.WithCancel(context.Background())

    botInstance.cancelFunc = cancel

    botInstance.IsRunning = true

    go botInstance.TradingEngine.Run(ctx) // background trading loop

    respondWithJSON(w, APIResponse{Status: "started"}, http.StatusOK)

}

Call context.Cancel() and a "stop" button actually halts the loop. Not fake Sheets-polling; a real long-running process. Config lives in local files (or SQLite), not spreadsheets, so queries are instant.

What You Gain

  • 6-minute limit vanishes — infinite trading loops possible
  • In-memory state — no round-trip to external storage per call
  • Single binarygo build output runs anywhere; no external runtime
  • Standard toolchain — git, local debugging, tests, type safety

The Tradeoff

Nothing's free. GAS's "click publish" deployment is gone. You now own the server infrastructure (or a PC that stays on). You wire authentication yourself; Google's identity layer disappears. For small, infrequent tasks, GAS remains sensible. But for a bot that holds state and runs long, the migration cost pays for itself.

This covers architecture migration patterns and does not guarantee investment returns.

댓글

이 블로그의 인기 게시물

한국투자증권 KIS API로 실시간 시세 받기 (WebSocket 실전)

파이썬으로 업비트 API 연동하기 — 시세 조회부터 주문까지 기초

Go로 자동매매 신호봇 프레임워크 설계하기