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
- Go to the Google Cloud Console
- Create a new project
- Go to APIs & Services > Library, search for "Google Sheets API", and enable it
- Then go to APIs & Services > Credentials > Create Credentials > Service Account
- Once your Service account is created, click it email, later go to "Keys" > Add key > Create new key and download config.json
- Download the JSON key (put this in config.json)
- Share your Google Sheet with the service account email (something like my-api-thingy@your-project.iam.gserviceaccount.com)
- 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
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
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) }
echo 'export PATH="$HOME/go/bin:$PATH"' >> ~/.bashrc source ~/.bashrc swag init
π¦ How to Run It
- Add your real Google Sheet ID where it says sheetID = "..."
- Put your config.json in the root directory
- Run it:
go run main.go
- 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
π 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"]
Fast. Irresponsible. Beautiful.
π§Ό .dockerignore - Hide the Evidence
config.json .git *.log
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
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:
- π¬ Event RSVP Tracking (Without a Real Backend)
- π Internal Tools for Non-Devs
- π§ͺ Prototyping APIs Before You Commit to a Real DB
- π Teaching & Workshops
- π 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)