DEV Community

Cyber
Cyber

Posted on

πŸ’Ύ Go Sheet DB: I Built a REST API in Go Using Google Sheets as a Database, Then Dockerized It Like I Hate Myself

Welcome to Go Sheet DB (API) β„’ - the only REST API held together by sheer willpower, a Google Sheet, and my irrational desire to do something in Go.

What started as "I should do something in Go" became a full-blown REST API running on Google Sheets as the backend database. No Postgres. No SQLite. Just... Sheets. The kind with rows and columns and sadness.

πŸ‘Ά Why Google Sheets?

Because I didn't want to install a database. And I love bad ideas that technically work.

βœ… Free
βœ… Serverless (in the worst way possible)
βœ… Editable by non-devs (like your boss)
βœ… Feels illegal - but isn't

πŸ§™ Step 1: Set Up Google Sheets API Like It's 2007

  1. Go to the Google Cloud Console
  2. Create a new project
  3. Go to APIs & Services > Library, search for "Google Sheets API", and enable it
  4. Then go to APIs & Services > Credentials > Create Credentials > Service Account
  5. Once your Service account is created, click it email, later go to "Keys" > Add key > Create new key and download config.json
  6. Download the JSON key (put this in config.json)
  7. Share your Google Sheet with the service account email (something like my-api-thingy@your-project.iam.gserviceaccount.com)
  8. Breathe deeply. You're now legally a spreadsheet engineer.

πŸ“„ Step 2: Create Your "Database"

Create a new Google Sheet. Call it something like users_db.

In the first sheet/tab, name the columns:

ID | Name | Email | CreatedAt 
Enter fullscreen mode Exit fullscreen mode

That's your schema. You're welcome.

πŸ’» Step 3: Go Code That Shouldn't Exist But Does

Let's write some Go that connects to this spreadsheet and makes it behave like a proper database.

Install Dependencies

go mod init gosheetdb go get google.golang.org/api/sheets/v4 go get golang.org/x/oauth2/google go get github.com/gin-gonic/gin go install github.com/swaggo/swag/cmd/swag@latest go get github.com/swaggo/gin-swagger go get github.com/swaggo/files 
Enter fullscreen mode Exit fullscreen mode

main.go (All in One Like a Champion)

// @title Go Sheet DB API (aka Go Sh**t DB API) // @version 1.0 // @description Local API that stores users in Google Sheets // @host localhost:8081 // @BasePath / package main import ( "context" "fmt" "log" "net/http" "os" "time" "github.com/gin-gonic/gin" _ "gosheetdb/docs" // Swagger docs generated by swag init "github.com/swaggo/files" "github.com/swaggo/gin-swagger" "golang.org/x/oauth2/google" "google.golang.org/api/option" "google.golang.org/api/sheets/v4" ) var ( srv *sheets.Service sheetID = "1n3Vt0_zLdMspL5VEzbD7SVWgK9186k-GRLv5xcjrFDc" ) // User represents a user stored in Google Sheets type User struct { ID string `json:"id" example:"1234567890"` Name string `json:"name" example:"Cyber"` Email string `json:"email" example:"cyber@spreadsheet.go"` CreatedAt string `json:"createdAt" example:"2025-06-22T15:04:05Z"` } type ErrorResponse struct { Error string `json:"error" example:"some error message"` } func initSheets() { ctx := context.Background() b, err := os.ReadFile("config.json") if err != nil { log.Fatalf("Failed to read config.json: %v", err) } config, err := google.JWTConfigFromJSON(b, sheets.SpreadsheetsScope) if err != nil { log.Fatalf("Failed to parse config: %v", err) } client := config.Client(ctx) srv, err = sheets.NewService(ctx, option.WithHTTPClient(client)) if err != nil { log.Fatalf("Failed to create Sheets client: %v", err) } } // getUsers godoc // @Summary Get all users // @Description Retrieve all users from Google Sheets // @Tags users // @Produce json // @Success 200 {array} User // @Failure 500 {object} ErrorResponse // @Router /users [get] func getUsers(c *gin.Context) { resp, err := srv.Spreadsheets.Values.Get(sheetID, "Users!A2:D").Do() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } var users []User for _, row := range resp.Values { if len(row) < 4 { continue } user := User{ ID: fmt.Sprintf("%v", row[0]), Name: fmt.Sprintf("%v", row[1]), Email: fmt.Sprintf("%v", row[2]), CreatedAt: fmt.Sprintf("%v", row[3]), } users = append(users, user) } c.JSON(http.StatusOK, users) } // addUser godoc // @Summary Add a new user // @Description Append a new user to Google Sheets // @Tags users // @Accept json // @Produce json // @Param user body User true "User to add" // @Success 201 {object} User // @Failure 400 {object} ErrorResponse // @Failure 500 {object} ErrorResponse // @Router /users [post] func addUser(c *gin.Context) { var u User if err := c.BindJSON(&u); err != nil { c.JSON(http.StatusBadRequest, gin.H{"error": "Bad JSON"}) return } u.ID = fmt.Sprintf("%d", time.Now().UnixNano()) u.CreatedAt = time.Now().Format(time.RFC3339) v := &sheets.ValueRange{ Values: [][]interface{}{ {u.ID, u.Name, u.Email, u.CreatedAt}, }, } _, err := srv.Spreadsheets.Values.Append(sheetID, "Users!A:D", v).ValueInputOption("RAW").Do() if err != nil { c.JSON(http.StatusInternalServerError, gin.H{"error": err.Error()}) return } c.JSON(http.StatusCreated, u) } func main() { initSheets() r := gin.Default() // Swagger UI at /swagger/index.html r.GET("/swagger/*any", ginSwagger.WrapHandler(swaggerFiles.Handler)) // API routes r.GET("/users", getUsers) r.POST("/users", addUser) port := os.Getenv("PORT") if port == "" { port = "8081" } r.Run(":" + port) } 
Enter fullscreen mode Exit fullscreen mode
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc source ~/.bashrc swag init 
Enter fullscreen mode Exit fullscreen mode

πŸ“¦ How to Run It

  1. Add your real Google Sheet ID where it says sheetID = "..."
  2. Put your config.json in the root directory
  3. Run it:
go run main.go 
Enter fullscreen mode Exit fullscreen mode
  1. Test it:
curl -X POST http://localhost:8081/users -H "Content-Type: application/json" -d '{"name": "Cyber", "email": "cyber@spreadsheet.go"}' curl http://localhost:8081/users 
Enter fullscreen mode Exit fullscreen mode

πŸŽ‰ You just built an API powered by a spreadsheet.

πŸ“¦ Step 4. Dockerfile From the Abyss

FROM golang:1.23 WORKDIR /app COPY go.mod go.sum ./ RUN go mod download COPY . . RUN go build -o gosheetdb . CMD ["./gosheetdb"] 
Enter fullscreen mode Exit fullscreen mode

Fast. Irresponsible. Beautiful.

🧼 .dockerignore - Hide the Evidence

config.json .git *.log 
Enter fullscreen mode Exit fullscreen mode

Don't ship your secrets or your entire git history.
You're building a meme, not a monolith.

πŸ§ͺ Run It Locally (For Science)

docker build -t gosheetdb . docker run -p 8081:8081 -v $(pwd)/config.json:/app/config.json gosheetdb 
Enter fullscreen mode Exit fullscreen mode

And just like that, your spreadsheet is now a cloud-native database.
Somewhere, a DBA just felt a cold chill.

πŸ› οΈ Real-Life Uses for Go Sheet DB (Weirdly Viable)

Despite being built on pure tech blasphemy, this API can work for small, chaotic, and oddly practical use cases:

  1. πŸ’¬ Event RSVP Tracking (Without a Real Backend)
  2. πŸ“‹ Internal Tools for Non-Devs
  3. πŸ§ͺ Prototyping APIs Before You Commit to a Real DB
  4. πŸŽ“ Teaching & Workshops
  5. πŸ“ˆ Collecting Feedback, Bug Reports, or Feature Votes

πŸ€” Final Thoughts

Sure, it was a wacky little experiment, but hey, now you know what a REST API in Go might look like, how to sweet-talk the Google Sheets API, and how to shove the whole thing into a Docker container like a tech-savvy magician.

  • I regret everything
  • I also regret nothing
  • It works
  • And that’s the real problem

Want to witness the beautiful chaos for yourself? The full code lives here (handle with care): https://github.com/ppaczk0wsk1/go-sheet-api

Top comments (0)