So you want to build a backend for your mobile app? You could spin up a VM and manage everything yourself, but honestly, that’s a lot of work. Let me show you a simpler way using Google Cloud.

In this tutorial, we’ll build a REST API that’s ready for production. It has authentication, rate limiting, auto-scaling, and a real database.

What we’re building:

Why App Engine?

App Engine is Google’s fully managed serverless platform. You deploy your code and Google handles the rest — servers, load balancing, auto-scaling, SSL certificates. It supports Python, Java, Node.js, Go, and more.

The Standard environment (which we’re using) scales to zero when there’s no traffic, so you only pay when your API is actually being used. It also starts up fast — typically under a second for Python apps.

Why Firestore?

Firestore is a NoSQL document database built for automatic scaling and high performance. Each document is stored as a set of key-value pairs, and documents are organized into collections.

Key features we’re using:

Let’s go!

The Architecture

Before we start, here’s what we’re building:

The request flow is simple:

  1. Your app sends a request with an API key
  2. Cloud Endpoints validates the key
  3. If valid, it forwards to App Engine
  4. Flask handles the request (with rate limiting) and talks to Firestore
  5. Response goes back to your app

What You Need

Before starting, make sure you have:

Step 1: Clone and Setup

First, clone the code:

git clone https://github.com/misskecupbung/gcp-cloud-endpoints-mobile-api.git
cd gcp-cloud-endpoints-mobile-api

Set your project ID:

export PROJECT_ID="your-project-id"
gcloud config set project $PROJECT_ID

# Verify it's set correctly:
gcloud config get-value project

Now run the setup script. This enables all the APIs we need:

./scripts/setup.sh

The script does a few things:

When prompted for a region, pick one close to your users. us-central is the default.

Step 2: Look at the Code

Let’s see what we’re deploying. Open app/main.py

The API has these endpoints:

The health endpoint is public and the rest needs an API key.

This setup uses Firestore instead of in-memory storage, so your data persists across deployments and instance restarts. The health check pings the database to verify connectivity — useful for load balancer health checks and monitoring alerts.

How Firestore Works in the Code

The app uses the google-cloud-firestore Python client. Here’s what happens when you create a user:

db = firestore.Client()
doc_ref = db.collection("users").add(user_data)

Firestore automatically:

For queries, we can filter and order:

query = db.collection("users").order_by("created_at", direction=firestore.Query.DESCENDING)

No need to set up indexes manually for simple queries — Firestore creates single-field indexes automatically.

Step 3: Configure the OpenAPI Spec

Cloud Endpoints uses an OpenAPI spec to define your API and enable API key validation. Update it with your project ID:

sed -i "s/YOUR_PROJECT_ID/$(gcloud config get-value project)/g" openapi.yaml

Take a look at openapi.yaml. The key part is API key authentication:

API Key authentication:

securityDefinitions:
api_key:
type: "apiKey"
name: "key"
in: "query"

This tells Cloud Endpoints to validate the key query parameter against your project’s API keys. Invalid keys get rejected before hitting your app.

Step 4: Deploy

Now let’s deploy:

./scripts/deploy.sh

This script:

  1. Creates app.yaml from the template
  2. Deploys the OpenAPI spec to Cloud Endpoints
  3. Deploys the Flask app to App Engine
  4. Creates an API key for testing

The first deploy takes a few minutes.

When it’s done, you’ll see your API URL: https://YOUR_PROJECT_ID.appspot.com

Verify in Console

Open the App Engine dashboard to confirm your service is running:

https://console.cloud.google.com/appengine

You should see:

Click on the version to see instance details and recent requests.

Step 5: Test Your API

Let’s make sure everything works. Run the test script:

./scripts/test-api.sh

Or test manually. First, get your API key:

export API_HOST="https://$PROJECT_ID.appspot.com"
export API_KEY=$(gcloud services api-keys list --format="value(name)" | head -1 | xargs gcloud services api-keys get-key-string --format="value(keyString)")

Try the health check (no auth needed):

curl "$API_HOST/api/v1/health"

You should see something like:

{
"status": "healthy",
"service": "mobile-api",
"database": "connected"
}

Or via browser

Now create a user:

curl -X POST "$API_HOST/api/v1/users?key=$API_KEY" \
-H "Content-Type: application/json" \
-d '{"name": "John", "email": "john@example.com"}'

And list users:

curl "$API_HOST/api/v1/users?key=$API_KEY"

Nice. Your API is live!

Verify Data in Firestore

Open Firestore to see the user you just created:

https://console.cloud.google.com/firestore

You should see a userscollection with your new document. Click on it to see the fields: name, email, created_at, etc.

Step 6: Check the Monitoring

Let’s look at the various dashboards available.

App Engine Dashboard

https://console.cloud.google.com/appengine

Here you can see:

Cloud Endpoints

Go to Cloud Console > Endpoints:

https://console.cloud.google.com/endpoints

Here you can see:

Click on your service to see more details.

Cloud Logging

https://console.cloud.google.com/logs

Use this query to see your API logs:

resource.type=”gae_app”
resource.labels.module_id="default"

You’ll see request logs from gunicorn and application logs. Look for entries containing user_createdor request to find the structured logs from the app.

Step 7: Test Rate Limiting

The API has built-in rate limiting using Firestore. By default, it allows 100 requests per minute per IP address. Since it uses Firestore, the limit is shared across all App Engine instances.

./scripts/test-rate-limit.sh

We can check from the logs also:

gcloud app logs read --limit=20 --project=vaulted-quarter-489102-h3 2>/dev/null | head -50

The script sends parallel requests to your API. After hitting the limit, you’ll see 429 Too Many Requestserrors.

You can adjust the limit in app/main.py:

# Rate limit config
RATE_LIMIT = 100 # requests allowed
RATE_WINDOW = 60 # per this many seconds

Cleanup

To avoid charges, run the cleanup script:

./scripts/cleanup.sh

This removes:

Resources

Drop a comment below or open an issue on GitHub if you have any questions or feedback.

Thank you and see you!