🌉 Turning an Idea into Reality: Finding Water Fountains in SF with Google Cloud 💧

Grant Timmerman
8 min readJul 11, 2024

--

http://watersf.com/

Here’s a technical step-by-step guide for how I create websites from nothing.

Tech Stack

To give you a preview, here are some tools and languages I’ll choose:

Tools:

  • Google Cloud: Web Hosting (Cloud Run)
  • GitHub: Source control
  • Cloudflare: CDN service
  • Namecheap: Domain name registration

Languages:

  • Next.js
  • TypeScript
  • Tailwind CSS

Steps

1. Register Domain with NameCheap

Think of a domain name for your site. It may take many searches to find an available domain.

namecheap.com search

It’s pretty simple. For example here’s a good option…

A decent option!

Once you check out, you have your domain!

To hook up to your future site, in the domain nameserver settings, select Custom DNS and add your two Cloudflare URLs:

Set the website’s DNS to point to Cloudflare

Note that these DNS server records may take up to 48 hours to take effect.

(We may change these nameservers later when we setup Cloudflare.)

2. Create GitHub Repo

Create a new GitHub repo to store your source code. Clone the repo.

Initialize a React TS app:

npx create-next-app@latest

Feel free to modify the code as you’d like. I like to move my files to the top-level of the git repo.

Use npm run dev to preview your frontend site!

The stock Next.js site

Now before modifying the site, let’s git commit and git push to GitHub and do a bit more work to deploy it.

3. Create Google Cloud Project

We’ll use Google Cloud for hosting the Node.js server that will serve our application.

Create a new project on Google Cloud at https://console.cloud.google.com:

A basic GCP project.

Next, let’s setup our terminal with gcloud:

brew install --cask google-cloud-sdk
gcloud auth login
gcloud config set project watersfcom

Enable billing if not already enabled (you’ll very likely be within the free tier). You can check if billing is enabled with this command:

gcloud billing projects describe watersfcom

Enable services that will be used for this app:

gcloud services enable artifactregistry.googleapis.com cloudbuild.googleapis.com run.googleapis.com

Create a deploy.sh file in your repo with the following contents:

#!/bin/bash

SERVICE=""
if [[ "${RUN_ENV}" == "PROD" ]]; then
# Disable update check to avoid prompt
gcloud config set component_manager/disable_update_check true
SERVICE="watersf"
else
# Default to staging
SERVICE="watersf-staging"
fi
echo "Deploying to project: ${SERVICE}"

# Build the app
npm run build;

buildStatus=$?

if [[ $buildStatus -eq 0 ]]; then
echo "!!! Build successful !!!"

# Deploys the web app to Cloud Run
gcloud run deploy $SERVICE \
--project watersfcom \
--region us-central1 \
--source . \
--allow-unauthenticated;
else
echo "!!! Build failed. Fix build then redeploy !!!"
fi

I’m using a package.json config like such:

{
"name": "watersf-www",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint",
"stage": "./deploy.sh",
"deploy": "RUN_ENV=PROD ./deploy.sh"
},
"dependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"@typescript-eslint/eslint-plugin": "^7.10.0",
"eslint": "^8",
"eslint-config-next": "14.2.3",
"eslint-plugin-react-refresh": "^0.4.7",
"next": "14.2.3",
"postcss": "^8",
"react": "^18",
"react-dom": "^18",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

Try deploying with npm run deploy (takes a minute). Example logs:

~/Documents/github/grant/watersf git:(main) npm run deploy

> watersf-www@0.1.0 deploy
> ./deploy.sh

Deploying to project: watersf-staging

> watersf-www@0.1.0 build
> next build

▲ Next.js 14.2.3

Creating an optimized production build ...
✓ Compiled successfully
Linting and checking validity of types .
⚠ The Next.js plugin was not detected in your ESLint configuration. See https://nextjs.org/docs/basic-features/eslint#migrating-existing-config

./src/app/layout.tsx
7:14 Warning: Fast refresh only works when a file only exports components. Use a new file to share constants or functions between components. react-refresh/only-export-components

info - Need to disable some ESLint rules? Learn more here: https://nextjs.org/docs/basic-features/eslint#disabling-rules
✓ Linting and checking validity of types
✓ Collecting page data
✓ Generating static pages (5/5)
✓ Collecting build traces
✓ Finalizing page optimization

Route (app) Size First Load JS
┌ ○ / 5.29 kB 92.2 kB
└ ○ /_not-found 871 B 87.8 kB
+ First Load JS shared by all 87 kB
├ chunks/23-0627c91053ca9399.js 31.5 kB
├ chunks/fd9d1056-2821b0f0cabcd8bd.js 53.6 kB
└ other shared chunks (total) 1.86 kB


○ (Static) prerendered as static content

!!! Build successful !!!
This command is equivalent to running `gcloud builds submit --pack image=[IMAGE] .` and `gcloud run deploy watersf-staging --image [IMAGE]`

Building using Buildpacks and deploying container to Cloud Run service [watersf-staging] in project [watersfcom] region [us-central1]
✓ Building and deploying... Done.
✓ Uploading sources...
✓ Building Container... Logs are available at [https://console.cloud.google.com/cloud-build/builds/b781d801-557d-4266-bd5e-e965aaf56827?project=166145790296].
✓ Creating Revision...
✓ Routing traffic...
✓ Setting IAM Policy...
Done.
Service [watersf-staging] revision [watersf-staging-00007-fbr] has been deployed and is serving 100 percent of traffic.
Service URL: https://watersf-staging-fjxvwo57jq-uc.a.run.app

One question you might have is: “How does Cloud Run know how to run this Node application?”. We didn’t even provide a Dockerfile!

Well, that’s where Cloud Run’s integration with Google Cloud Buildpacks comes in! If you run gcloud run deploy with --source, the command will first upload our source code to Cloud Build and build the container. Buildpacks provides a default builder (gcr.io/buildpacks/builder:latest) that automatically detects the runtime (Node.js) and installs the necessary libraries (npm and Node.js), runs npm run build, and saves the container.

You can learn more about GCP buildpacks here and the Node.js builder here.

The logs are pretty useful for understanding what is technically going on!

At the bottom you can see your service at a run.app URL.

4. Setup Cloudflare

For a CDN, we can use Cloudflare for free. This service will act as the doorstep to any requests made to our site.

A basic diagram showing Cloudflare

On cloudflare.com, create a site:

Add a Cloudflare site

Scroll down and choose the free option at the bottom of the pricing page:

Choose Free!

We’ll come back to this later.

5. Cloud Run Domain Mapping

To get our service URL https://watersf-staging-fjxvwo57jq-uc.a.run.app to something more usable like watersf.com, we can use Cloud Run’s nifty domain mapping feature.

Let’s use the command line for extra convenience. First log your current domains:

gcloud domains list-user-verified

Now you’ll need to add your domain you registered to this list.

  1. Go to https://search.google.com/search-console
  2. Click “Add property” and paste in the domain (like watersf.com). You’ll receive a string needed to add to your DNS records.
  3. Create a TXT record in Cloudflare: TXT | watersf.com | google-site-verification=ZM_V3bw_QhSys31bV2ot_C2Q_uS_iO_pXCNls17UshM
  4. Click “Verify” in the Google Search Console.
  5. Verify you completed this step with gcloud domains list-user-verified
Domain Verification with Google Search Console

Add the domain mapping to Cloud Run with gcloud.

You’ll receive a list of DNS records to update:

gcloud beta run domain-mappings create --service watersf --domain watersf.com
...
Creating......done.
Waiting for certificate provisioning. You must configure your DNS records for certificate issuance to begin.
NAME RECORD TYPE CONTENTS
watersf A 216.239.32.21
watersf A 216.239.34.21
watersf A 216.239.36.21
watersf A 216.239.38.21
watersf AAAA 2001:4860:4802:32::15
watersf AAAA 2001:4860:4802:34::15
watersf AAAA 2001:4860:4802:36::15
watersf AAAA 2001:4860:4802:38::15

For me, this Cloudflare configuration looks like this:

DNS records for watersf.com

Important: Use “@” for the “name” field to indicate root / watersf.com.

Additionally, remember those Cloudflare Nameservers we setup in NameCheap awhile ago? If you scroll down a bit on the DNS management page, you’ll see the servers we’ll want to configure for Namecheap:

Cloudflare states nameservers
Update NameCheap nameservers

6. Design the Frontend

All of the above was really just boring setup for scaffolding the actual website. Now for the fun stuff! Let’s create our application.

What is watersf.com? Let’s take the pattern scenario — solution:

Scenario: I am running in San Francisco and I want to find the closest free water fountain near me. Or the nearest restroom.

Solution: I open the watersf app on my phone and press a button to navigate me to the nearest water fountain. In one click!

How?

Well, the SF gov publishes data around public water fountains and restrooms:

Public water fountains in SF

Here’s the data:

We can create an app with 1 button that gets your location and routes you to the nearest water fountain via Google Maps. Or restroom.

There’s even a JSON API: https://data.sfgov.org/resource/wfq4-upmv.json

JSON data with drinking water and restroom data

So… we have the data. We can request the user’s GPS and compare against the data to find restrooms sorted by Euclidean distance (descending). Show those as the results with a link.

What should the UI look like?

Let’s use one of these AI apps be our designer…

Design Requirements

There are two main pages:

HOME PAGE: Show a button for finding water fountain.

RESULT PAGE: A list of results with map links to water fountains.

I used an image generator to create inspiration:

https://designer.microsoft.com/image-creator

The second image with a big button “FIND WATER” on the home page sounds good!

Using water SF in Hayes Valley

Check it out yourself at watersf.com!

On Chrome mobile, you can use the “Add to Homescreen” button to get a quick link. It looks like this for me:

Quick link to water when I’m on a run.

Thanks for reading!

By the way, all of this code is open source here:

https://github.com/grant/watersf

If you enjoyed this, be sure to give a clap 👏 or write a comment ✏️.

--

--