We have a simple contact form and, we want to send that FormData
to a Google spreadsheet.
This was the hardest part as we need to navigate a Google GUI that may not be familiar.
Thankfully Theo Ephraim wrote an incredible doc on this here. And if you find it helpful, consider buying him a beer.
Let's assume you've followed the above guide:
Since this is a secret, we want to only expose this to our code via an environment variable.
While working locally, it worked fine for me to copy and paste just the client_email
and private_key
from the json key file into my .env.local
file.
const jwtFromEnv = new JWT({ email: process.env.GOOGLE_SERVICE_CLIENT_EMAIL, key: process.env.GOOGLE_SERVICE_PRIVATE_KEY, scopes: SCOPES, });
But this didn't work when I deployed to Vercel. I needed to base64 encode the entire json file and upload it to the Vercel env vars. I encoded it in the command line with base64
and then decoded in the javascript code like this:
const credential = JSON.parse( Buffer.from(process.env.GOOGLE_SERVICE_KEY as string, 'base64').toString(), ); const jwtFromEnv = new JWT({ email: credential.client_email, key: credential.private_key, scopes: SCOPES, });
Originally, I wanted to run this simple script on an edge runtime, but there was some overhead that I didn't want to take on that extra time for a task I was doing for a client (and the benefit was going to be minor given the UX of the project - aka this was a one-off call that didn't block anything else the user wanted to do next).
If I wanted to explore that again, I'd try to use this as a jumping-off point: https://sdorra.dev/posts/2023-08-03-google-auth-on-the-edge. You would still need to handle the API calls to Google since their node library googleapis
doesn't support edge runtimes yet.
If you're using Next.js, you could leverage their Server Actions feature.
If you're using Remix, you could use their actions feature.
Etc for other frameworks.
We are just gonna make a simple API endpoint that takes a FormData
and writes it to a Google spreadsheet and call that with fetch
.
We are writing a simple POST request endpoint. And using the great google-spreadsheet
library to write to the spreadsheet.
Here is the entire endpoint:
import { JWT } from 'google-auth-library'; import { GoogleSpreadsheet } from 'google-spreadsheet'; const credential = JSON.parse( Buffer.from(process.env.GOOGLE_SERVICE_KEY as string, 'base64').toString(), ); const GOOGLE_SPREADSHEET_ID = process.env.GOOGLE_SPREADSHEET_ID as string; const SCOPES = ['https://www.googleapis.com/auth/spreadsheets']; export async function POST(request: Request) { const { contactReason, name, email, description } = await request.json(); const jwt = new JWT({ email: credential.client_email, key: credential.private_key, scopes: SCOPES, }); const doc = new GoogleSpreadsheet(GOOGLE_SPREADSHEET_ID, jwt); try { await doc.loadInfo(); const sheet = doc.sheetsByIndex[0]; const newRow = await sheet.addRow([ contactReason, name, email, description, new Date().toISOString(), ]); return new Response( JSON.stringify({ contactReason, name, email, description }), ); } catch (err) { console.error(err); return new Response('Unknown error', { status: 500 }); } }
Currently the security on this API endpoint is that it only allows requests from the same-origin
.
Someone could still write a bot to submit the form many times/with trash data and it would be submit successfully. If this is a threat, you'd want to mitigate that with some kind-of CAPTCHA or rate limiting. A really nice CAPTCHA for your users is Cloudflare's Turnstile that doesnt require your users to click all the images of the people wasting their time on with bad website UX.