CRUD Operations on Google Sheets via REST API
How to create, read, update, and delete rows in Google Sheets using GKit SheetsAPI — with real fetch examples in TypeScript.
Google Sheets as a CRUD database
Most REST APIs talk to Postgres or MongoDB. GKit SheetsAPI talks to a Google Sheet — and it supports the full set of CRUD operations: create, read, update, and delete rows. If your data already lives in a spreadsheet, you get a real REST API without writing a single line of backend code.
This guide walks through every operation with working TypeScript fetch examples.
Setup: your base URL and auth header
Every request goes to:
https://sheetsapi.gkit.mreshank.com/api/spreadsheets/{userKey}/{sheetName}
{userKey}— found in your GKit dashboard after connecting a spreadsheet{sheetName}— the tab name inside your Google Sheet (e.g.Contacts,Products)
All mutating requests (POST, PUT, DELETE) require an API key:
Authorization: Bearer sk_...
Create one in the dashboard under API Keys. Read-only GET requests work without auth on public sheets.
Read all rows
const BASE = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets";
async function listRows(userKey: string, sheet: string) {
const res = await fetch(`${BASE}/${userKey}/${sheet}`, {
headers: { Authorization: `Bearer ${process.env.GKIT_API_KEY}` },
});
const json = await res.json();
// { data: [...], meta: { total: 42, limit: 100, offset: 0 } }
return json;
}The response always includes a meta object so you can implement pagination:
async function listPage(userKey: string, sheet: string, offset = 0) {
const url = new URL(`${BASE}/${userKey}/${sheet}`);
url.searchParams.set("limit", "20");
url.searchParams.set("offset", String(offset));
const res = await fetch(url.toString(), {
headers: { Authorization: `Bearer ${process.env.GKIT_API_KEY}` },
});
return res.json(); // { data: [...], meta: { total, limit, offset } }
}Read a single row
Rows are addressed by their 1-based index — row 1 is the first data row (the header row is row 0 and is never returned as data).
async function getRow(userKey: string, sheet: string, row: number) {
const res = await fetch(`${BASE}/${userKey}/${sheet}/${row}`, {
headers: { Authorization: `Bearer ${process.env.GKIT_API_KEY}` },
});
if (!res.ok) throw new Error(`Row ${row} not found`);
return res.json(); // { name: "Ada", email: "ada@example.com", plan: "pro" }
}Create a row
POST a JSON object whose keys match the column headers in your sheet. GKit appends a new row at the end.
async function createRow(
userKey: string,
sheet: string,
data: Record<string, string>
) {
const res = await fetch(`${BASE}/${userKey}/${sheet}`, {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.GKIT_API_KEY}`,
},
body: JSON.stringify(data),
});
return res.json();
}
// Example
await createRow("uk_abc123", "Contacts", {
name: "Grace Hopper",
email: "grace@example.com",
plan: "free",
});Any keys that don't match a header are silently ignored. Missing keys leave the cell empty.
Update a row
PUT replaces the row at the given index. Supply only the fields you want to change — unchanged columns keep their current values.
async function updateRow(
userKey: string,
sheet: string,
row: number,
data: Record<string, string>
) {
const res = await fetch(`${BASE}/${userKey}/${sheet}/${row}`, {
method: "PUT",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.GKIT_API_KEY}`,
},
body: JSON.stringify(data),
});
return res.json();
}
// Upgrade row 3 to "pro"
await updateRow("uk_abc123", "Contacts", 3, { plan: "pro" });Delete a row
DELETE removes the row entirely. Rows below it shift up, so subsequent operations should re-fetch the index rather than cache it.
async function deleteRow(userKey: string, sheet: string, row: number) {
const res = await fetch(`${BASE}/${userKey}/${sheet}/${row}`, {
method: "DELETE",
headers: { Authorization: `Bearer ${process.env.GKIT_API_KEY}` },
});
if (!res.ok) throw new Error(`Delete failed: ${res.status}`);
return res.status === 204; // true on success
}
await deleteRow("uk_abc123", "Contacts", 3);Putting it together: a typed client
Wrap the four operations in a small utility and you have a fully typed spreadsheet client in under 60 lines:
const BASE = "https://sheetsapi.gkit.mreshank.com/api/spreadsheets";
export class SheetsClient<T extends Record<string, string>> {
constructor(
private userKey: string,
private sheet: string,
private apiKey: string
) {}
private headers() {
return {
"Content-Type": "application/json",
Authorization: `Bearer ${this.apiKey}`,
};
}
async list(): Promise<{ data: T[]; meta: { total: number; limit: number; offset: number } }> {
return fetch(`${BASE}/${this.userKey}/${this.sheet}`, {
headers: this.headers(),
}).then((r) => r.json());
}
async get(row: number): Promise<T> {
return fetch(`${BASE}/${this.userKey}/${this.sheet}/${row}`, {
headers: this.headers(),
}).then((r) => r.json());
}
async create(data: Partial<T>): Promise<T> {
return fetch(`${BASE}/${this.userKey}/${this.sheet}`, {
method: "POST",
headers: this.headers(),
body: JSON.stringify(data),
}).then((r) => r.json());
}
async update(row: number, data: Partial<T>): Promise<T> {
return fetch(`${BASE}/${this.userKey}/${this.sheet}/${row}`, {
method: "PUT",
headers: this.headers(),
body: JSON.stringify(data),
}).then((r) => r.json());
}
async delete(row: number): Promise<boolean> {
const r = await fetch(`${BASE}/${this.userKey}/${this.sheet}/${row}`, {
method: "DELETE",
headers: this.headers(),
});
return r.status === 204;
}
}