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