Your ERP uploads a CSV every night and someone needs to process it. Or a Stripe webhook fires an event and you must update the database in real time. The classic solution? An always-on server that you pay for even when it does nothing.
We, at Meteora Web, work with companies that have batch processes or asynchronous events to handle. Cloud Functions on Google Cloud Platform is the tool we use when code should run only when needed, scale from zero to hundreds of requests, and cost pennies a month. In this guide we cover how to write, deploy, and monitor serverless functions with Python and Node.js, with working code and cost reasoning.
Why Serverless? The Economic Angle
Before writing a single line of code, let's start with the real problem: the client has a process that must run every time something happens. A dedicated server costs tens of euros per month even if it runs only one job per day. With Cloud Functions you pay only for execution time and invocations. The free tier (2 million invocations/month, 400,000 GB-sec) can handle many small businesses at no cost.
We see it every day: an e-commerce generating nightly reports, a booking system sending confirmation emails, a backend processing images uploaded by users. In all these cases, a serverless function is cheaper, easier to maintain, and scales automatically.
Cloud Functions: What They Are and When to Use Them
Cloud Functions is Google Cloud's serverless compute product. You write a function in Python, Node.js, Go, Java, Ruby, PHP, or .NET, deploy it with one command, and Google handles runtime, scaling, security updates. You can trigger it from events (Cloud Storage upload, Pub/Sub message, Firestore change) or via HTTP (lightweight API).
Sponsored Protocol
Our preferred stack: Python for data processing (pandas, numpy) and Node.js for fast APIs and webhooks. But the logic is identical.
When NOT to Use Cloud Functions
A function has a maximum timeout of 60 minutes (2nd gen) and limited resources (up to 16 GB RAM, 4 vCPU). If you have a job that must run for hours or consume hundreds of GB of RAM, go for Cloud Run or Compute Engine. And if you need to respond to hundreds of concurrent requests with ultra-low latency, an always-on service may be better.
Write Your First Function: Python
Let's create an HTTP function that accepts a POST request with JSON, processes it, and returns a result. Real-world example: a webhook that receives an order and saves it to Firestore.
# main.py
import functions_framework
from google.cloud import firestore
@functions_framework.http
def process_order(request):
"""Receives order from webhook and saves it to Firestore"""
request_json = request.get_json(silent=True)
if not request_json or 'order_id' not in request_json:
return {'error': 'Missing order_id'}, 400
db = firestore.Client()
doc_ref = db.collection('orders').document(request_json['order_id'])
doc_ref.set(request_json)
return {'status': 'ok', 'id': request_json['order_id']}, 200
To deploy:
gcloud functions deploy process-order \
--runtime python312 \
--trigger-http \
--allow-unauthenticated \
--region europe-west1
Note: `--allow-unauthenticated` is only for tests or public webhooks. In production, protect with API Key or Identity-Aware Proxy.
Sponsored Protocol
Manage Dependencies
Python dependencies must be listed in requirements.txt:
functions-framework==3.*
google-cloud-firestore==2.*
The functions-framework package is for local testing. Not needed in production, but we include it for consistency.
Write Your First Function: Node.js
Same logic in JavaScript. We use the @google-cloud/functions-framework.
// index.js
const functions = require('@google-cloud/functions-framework');
const {Firestore} = require('@google-cloud/firestore');
functions.http('processOrder', async (req, res) => {
if (req.method !== 'POST') {
res.status(405).send('Method Not Allowed');
return;
}
const { order_id, ...data } = req.body;
if (!order_id) {
res.status(400).json({ error: 'Missing order_id' });
return;
}
const db = new Firestore();
const docRef = db.collection('orders').doc(order_id);
await docRef.set(data);
res.status(200).json({ status: 'ok', id: order_id });
});
package.json:
{
"dependencies": {
"@google-cloud/functions-framework": "^3.0.0",
"@google-cloud/firestore": "^7.0.0"
}
}
Deploy:
gcloud functions deploy process-order \
--runtime nodejs20 \
--trigger-http \
--allow-unauthenticated \
--region europe-west1
Cloud Storage Trigger (Python)
Scenario: a CSV file is uploaded to a bucket, the function reads it and imports into BigQuery.
# import_csv.py
from google.cloud import storage, bigquery
import functions_framework
@functions_framework.cloud_event
def import_csv_from_bucket(cloud_event):
data = cloud_event.data
bucket_name = data['bucket']
file_name = data['name']
if not file_name.endswith('.csv'):
print(f"Ignored {file_name} - not a CSV")
return
client = bigquery.Client()
uri = f"gs://{bucket_name}/{file_name}"
dataset_id = "my_dataset"
table_id = "imported_data"
job_config = bigquery.LoadJobConfig(
source_format=bigquery.SourceFormat.CSV,
skip_leading_rows=1,
autodetect=True,
write_disposition=bigquery.WriteDisposition.WRITE_TRUNCATE
)
load_job = client.load_table_from_uri(uri, f"{dataset_id}.{table_id}", job_config=job_config)
load_job.result() # wait
print(f"Loaded {file_name} into {dataset_id}.{table_id}")
Deploy with bucket trigger:
Sponsored Protocol
gcloud functions deploy import-csv \
--runtime python312 \
--trigger-event-filters="type=google.cloud.storage.object.v1.finalized" \
--trigger-event-filters="bucket=my-data-bucket" \
--region europe-west1
Local Testing & Debugging (Without Spending a Cent)
Test locally before deploying. For Python:
pip install functions-framework
functions-framework --target=process_order --port=8080
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{"order_id":"123"}'
For Node.js:
npx @google-cloud/functions-framework --target=processOrder --port=8080
curl -X POST http://localhost:8080 -H "Content-Type: application/json" -d '{"order_id":"123"}'
You can use Docker to simulate the exact environment, but for 90% of cases the local framework is enough.
Sponsored Protocol
Environment Variables and Secrets
Never hardcode credentials. Use environment variables or Secret Manager.
gcloud functions deploy my-function \
--set-env-vars DATABASE_URL=postgres://... \
--set-secrets MY_API_KEY=my-secret:latest \
--region europe-west1
In Python code access with os.environ.get('DATABASE_URL'). Secrets are mounted as files in /etc/secrets or accessible via library.
Costs and Optimization
An HTTP function with 128 MB RAM and 200 ms response costs about $0.000002 per invocation (after free tier). For an e-commerce with 10,000 orders/month that's 2 cents. But watch out:
- Cold start: the first invocation after inactivity can take 1-2 seconds. To reduce it, set min-instances or use lighter runtimes (Node.js starts faster than Python).
- Timeout: set a reasonable timeout (e.g., 30 seconds for a webhook, 10 minutes for a batch job). Too high values increase risk of unexpected costs if the function loops.
- Memory: use the minimum RAM needed. Python with pandas may need 512 MB, but a simple API works with 128 MB. Each extra GB costs about $0.000006 per 100 ms.
We, at Meteora Web, always recommend enabling budget alerts on GCP to get notifications if costs exceed a threshold. One misconfigured job can generate thousands of invocations in minutes.
Common Errors and Solutions
- Timeout expired: the function has a 60-minute limit (2nd gen) or 9 minutes (1st gen). For longer jobs use Cloud Run or Workflows.
- Missing dependencies: in Python, if you use native libraries (pandas, numpy), make sure they are in requirements.txt and compatible with the environment (many are pre-installed).
- Unclosed connections: always open and close database or API connections. Open connections can exhaust resources. Use
withstatement in Python orfinallyin JS. - Duplicate events: Cloud Functions guarantees at-least-once delivery. Your function must be idempotent (e.g., upsert instead of insert).
Monitoring and Logging
Don't deploy blindly. Use Cloud Logging to see errors in real time. You can view logs in the GCP console or with gcloud functions logs read. Set custom metrics to monitor success (e.g., count processed orders).
Sponsored Protocol
For Python, use the standard Python logger. For Node.js, console.log works. Google Cloud Logging automatically captures stdout and stderr.
In Summary — What to Do Now
- Identify a process that runs periodically or on an event (webhook, upload, nightly job).
- Write a local test function using the appropriate framework (Python or Node.js).
- Deploy with gcloud functions deploy, first with
--no-gen2(use 1st gen for testing), then switch to 2nd gen for production. - Secure the function: for HTTP triggers use API Key or Cloud Endpoints; for event triggers set proper IAM permissions.
- Enable budget alerts and monitor logs during the first 24 hours.
Want to dive deeper into the GCP ecosystem? Read our Pillar Guide on Google Cloud Platform. There you'll find the full picture: Cloud Run, GKE, Cloud Storage, and how to integrate Cloud Functions in real architectures.