<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Wesley Chun (@wescpy)</title>
    <description>The latest articles on DEV Community by Wesley Chun (@wescpy) (@wescpy).</description>
    <link>https://dev.to/wescpy</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1038293%2F5460cbbf-edfc-4acd-8535-d8dd89554fce.png</url>
      <title>DEV Community: Wesley Chun (@wescpy)</title>
      <link>https://dev.to/wescpy</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/wescpy"/>
    <language>en</language>
    <item>
      <title>Simplifying basic (genAI) web app deployment with serverless</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 01 Dec 2025 10:21:48 +0000</pubDate>
      <link>https://dev.to/gde/simplifying-basic-genai-web-app-deployment-with-serverless-3cc5</link>
      <guid>https://dev.to/gde/simplifying-basic-genai-web-app-deployment-with-serverless-3cc5</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;Writing simple web apps is one thing, but deploying it globally without having to think about servers, VMs (virtual machines), or DNS is another. That's where this post and &lt;em&gt;serverless&lt;/em&gt; come into play. Whether you have a Gemini-powered app or agent, serverless lets you focus on the solution you're building, not what it runs on. In this post, we show you how to deploy a basic web app featuring the Gemini API to serverless on Google Cloud.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" alt="Build with Gemini"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to another installment on the blog covering Google APIs for Python (and sometimes Node.js) developers. Here, I cover stuff you won't find in Google's documentation, all while showing you how to use Google APIs from different product groups for what you build. Here are some of the categories covered so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP (&lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;) (&lt;em&gt;this series&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt; (Drive, Docs, Sheets, Gmail, etc.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Generative AI with &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt; (&lt;em&gt;this series&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Credentials (&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We return to Gemini again in this post but focus less on the AI and more on how to more easily deploy apps to the cloud, whether AI-powered or not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Writing web apps is one thing, but deploying them globally without having to think about servers, VMs (virtual machines), DNS, and scalability is another. That's where this post and &lt;em&gt;serverless&lt;/em&gt; come into play. Whether you have a Gemini-powered app or agent, serverless lets you focus on the solution you're building, not what it runs on or even &lt;em&gt;how&lt;/em&gt; to run it. Is this you?&lt;/p&gt;

&lt;p&gt;There won't be much introductory material as we'll get straight into serverless deployments. For more background, it's wise to point out that this is a follow-up to several posts from this blog, all which make for good reading before proceeding:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;a href="http://bit.ly/3Kqv78c" rel="noopener noreferrer"&gt;post covering a basic genAI web app&lt;/a&gt; whose code was recently updated following advice from...&lt;/li&gt;
&lt;li&gt;&lt;a href="http://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;Another post on upgrading to Gemini 2.5&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://bit.ly/4oYgJmC" rel="noopener noreferrer"&gt;post introducing serverless&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://bit.ly/4pGtBOp" rel="noopener noreferrer"&gt;post to hosting apps today on Google App Engine&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;a href="http://bit.ly/43Y5t1g" rel="noopener noreferrer"&gt;post on modern app-hosting with Google Cloud Run&lt;/a&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Architectural components
&lt;/h2&gt;

&lt;p&gt;There are several large "pieces of the puzzle" to look at, and decisions to be made:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;API platforms &amp;amp; credentials&lt;/strong&gt; -- decide which platform to use when calling the Gemini API then get your credentials and set up your Cloud project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Languages&lt;/strong&gt; -- decide whether you want to run Node.js or Python (should be easy); each language has several options too&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP Serverless platforms&lt;/strong&gt; -- decide which serverless platform you want to run, and by the way, what's serverless anyway?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each has its own set of prerequisites which must be satisfied before running any of the sample apps.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gemini API
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://deepmind.google/models/gemini" rel="noopener noreferrer"&gt;Gemini&lt;/a&gt; is a popular, proprietary, multimodal LLM (large language model), accessed via its &lt;a href="https://gemini.google.com" rel="noopener noreferrer"&gt;public chat interface&lt;/a&gt; or as the LLM for agents. Its API lets developers tap into its capabilities directly from web apps or mobile backends, and the former is the use case we're addressing in this post. One aspect of using the API is unexpected: Google makes the Gemini API available from two different platforms. Each has its own set of use cases as well as pricing differences. Learn about both platforms via the resources in the following table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;th&gt;Use Cases&lt;/th&gt;
&lt;th&gt;Pricing&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://ai.google.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Google AI&lt;/strong&gt;&lt;/a&gt; ("GAI")&lt;/td&gt;
&lt;td&gt;Experimenting, free tier/lower cost, lower barrier-to-entry, hobbyists/students&lt;/td&gt;
&lt;td&gt;&lt;a href="https://ai.google.dev/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;&lt;strong&gt;Vertex AI&lt;/strong&gt;&lt;/a&gt; (Google Cloud/"GCP")&lt;/td&gt;
&lt;td&gt;Production AI workloads, existing GCP customers adding and/or considering AI capabilities&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Since this post is mostly introductory, we'll &lt;strong&gt;use the Gemini API from Google AI&lt;/strong&gt;, however, the current, unified client library lets developers transition to GCP easily, representing a significant improvement from when there were two distinct client libraries, one for each platform. Compare, contrast and learn the differences between all three client libraries (two old and one new) in the &lt;a href="http://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;second post listed above&lt;/a&gt; which aims to help those migrating to Gemini 2.5.&lt;/p&gt;

&lt;p&gt;There's no doubt that old samples live forever online and vibecoding LLMs have been trained on both old and new code, so that post serves as a PSA (public service announcement) to show readers the differences between all three libraries so they can upgrade their own applications or fix code using old libraries generated by LLMs.&lt;/p&gt;

&lt;h4&gt;
  
  
  Requirements(s)
&lt;/h4&gt;

&lt;p&gt;In order to run the sample apps locally or deploy to the cloud, you need to complete the prerequisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Get credentials:&lt;/strong&gt; Go to the &lt;a href="https://makersuite.google.com/app/apikey" rel="noopener noreferrer"&gt;GAI API keys page&lt;/a&gt; and select an existing API key or click "Create API key" to make a new one. Save the API key text string somewhere safe; we'll come back to it shortly.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Get project:&lt;/strong&gt; The "Create API key" dialog also lets you create a new project (or import an existing one), so do that too.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Completely new?&lt;/strong&gt; If you're completely new to GCP, learn more about &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener noreferrer"&gt;developer projects&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij4wyuzcy5nmov9le4ml.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fij4wyuzcy5nmov9le4ml.png" alt="Create API key page"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Google AI create API key page



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3tbqpdms43085qzdhcp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi3tbqpdms43085qzdhcp.png" alt="Create API key dialog"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Google AI create API key dialog



&lt;p&gt; &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Existing GCP users:&lt;/strong&gt; Experienced GCP users may prefer to do the above from the Cloud Console:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://console.cloud.google.com/projectcreate" rel="noopener noreferrer"&gt;Create a new project&lt;/a&gt; or select an existing one.&lt;/li&gt;
&lt;li&gt;Go to the &lt;a href="https://console.cloud.google.com/apis/credentials" rel="noopener noreferrer"&gt;Credentials page&lt;/a&gt; and create an API key or select an existing one.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffhi61ij8ch77xknq7vp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fffhi61ij8ch77xknq7vp.png" alt="Create developer project"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Cloud console create project page



&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtf28g5y6swk5srp1c87.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvtf28g5y6swk5srp1c87.png" alt="Create API key page/dialog"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Cloud console create API key dialog on create credentials page


&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Languages
&lt;/h3&gt;

&lt;p&gt;The code featured in this post is available in various versions of Node.js and Python:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Language&lt;/th&gt;
&lt;th&gt;Version&lt;/th&gt;
&lt;th&gt;Web framework&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;ECMAscript module&lt;/td&gt;
&lt;td&gt;Express&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Node.js&lt;/td&gt;
&lt;td&gt;CommonJS script&lt;/td&gt;
&lt;td&gt;Express&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;td&gt;Flask&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;td&gt;FastAPI&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;All code samples are available in &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;. Regular readers will notice it differs from &lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;&lt;em&gt;this&lt;/em&gt; blog's regular repo&lt;/a&gt;, and there's a reason for this. I spent several years on Google's GCP Serverless team representing those products. During that time, I created a &lt;a href="https://github.com/googlecodelabs/cloud-nebulous-serverless" rel="noopener noreferrer"&gt;repo named (Cloud) Nebulous Serverless&lt;/a&gt; to show developers how they can deploy the same app to all platforms without any code changes.&lt;/p&gt;

&lt;p&gt;That repo held all sample applications I built meeting this criteria. Google archived that repo after my departure, so I'm continuing all work in my personal fork. The samples in this post also meet that criteria, so that's why they're in this repo and not the normal one. (I also removed "Cloud" from the repo name as there are now non-Cloud sample apps in the fork.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Requirements(s)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Setup local environment:&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Clone the code locally with &lt;code&gt;git clone https://github.com/wescpy/nebulous-serverless.git&lt;/code&gt; and go to the app folder in &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem" rel="noopener noreferrer"&gt;multi/webgem&lt;/a&gt; with &lt;code&gt;cd multi/webgem&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; Ensure you have contemporary versions of Node &amp;amp; NPM (recommend 18+), &lt;code&gt;cd nodejs&lt;/code&gt; and install all packages with &lt;code&gt;npm i&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python:&lt;/strong&gt; Ensure you have a contemporary version of Python (recommend 3.9+) and &lt;code&gt;cd python&lt;/code&gt;

&lt;ul&gt;
&lt;li&gt;(optional) &lt;a href="https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments" rel="noopener noreferrer"&gt;Create &amp;amp; activate a virtual environment ("virtualenv") for isolation&lt;/a&gt; with &lt;code&gt;python3 -m venv .venv; source .venv/bin/activate&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;For the commands below, depending on your system configuration, you will use one of (&lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;pip3&lt;/code&gt;, &lt;code&gt;python3 -m pip&lt;/code&gt;), but the instructions are generalized to &lt;code&gt;pip&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;(optional) Update &lt;code&gt;pip&lt;/code&gt; and install &lt;code&gt;uv&lt;/code&gt; with &lt;code&gt;pip install -U pip uv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Decide whether to run the Flask or FastAPI version.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Flask:&lt;/strong&gt; Install all packages: &lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt; (drop &lt;code&gt;uv&lt;/code&gt; if you didn't install it)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;FastAPI:&lt;/strong&gt; Replace Flask files by moving them out of the &lt;code&gt;fastapi&lt;/code&gt; folder: &lt;code&gt;mv fastapi/* .&lt;/code&gt; then install all packages: &lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt; (drop &lt;code&gt;uv&lt;/code&gt; if you didn't install it)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set credentials:&lt;/strong&gt; Previously, you selected a project and created an API key; now save it to one of these files, depending on which language you're using:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js&lt;/strong&gt;: Save as &lt;code&gt;API_KEY = YOUR_API_KEY&lt;/code&gt; to &lt;code&gt;.env&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;:  Save as &lt;code&gt;API_KEY = 'YOUR_API_KEY'&lt;/code&gt; to &lt;code&gt;settings.py&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can model yours after the provided templates, &lt;a href="https://github.com/wescpy/nebulous-serverless/blob/main/multi/webgem/nodejs/.env_TMPL" rel="noopener noreferrer"&gt;&lt;code&gt;.env_TMPL&lt;/code&gt;&lt;/a&gt; or &lt;a href="https://github.com/wescpy/nebulous-serverless/blob/main/multi/webgem/python/settings_TMPL.py" rel="noopener noreferrer"&gt;&lt;code&gt;settings_TMPL.py&lt;/code&gt;&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;code&gt;.env_TMPL&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;API_KEY="YOUR_API_KEY"
GCP_METADATA='{
  "project": "YOUR_GCP_PROJECT",
  "location": "YOUR_GCP_REGION"
}'
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;&lt;code&gt;settings_TMPL.py&lt;/code&gt;&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;GCP_METADATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_GCP_PROJECT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_GCP_REGION&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In addition to &lt;code&gt;API_KEY&lt;/code&gt;, these files also contain &lt;code&gt;GCP_METADATA&lt;/code&gt; in case you migrate to GCP in the future. As mentioned above, you can refer to &lt;a href="http://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;this post&lt;/a&gt; to get an idea of the additional setup and minor code updates you need to make to run your Gemini API-powered apps on Vertex AI.&lt;/p&gt;

&lt;p&gt;Use of &lt;code&gt;settings.py&lt;/code&gt; as a naming convention follows in &lt;a href="https://djangoproject.com" rel="noopener noreferrer"&gt;Django&lt;/a&gt;'s footsteps, but alternatively, you can save it to &lt;code&gt;.env&lt;/code&gt; and integrate use of &lt;a href="https://pypi.org/project/python-dotenv" rel="noopener noreferrer"&gt;&lt;code&gt;python-dotenv&lt;/code&gt;&lt;/a&gt; to more closely mirror Node.&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless platforms
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" alt="Serverless computing with Google"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Google Cloud has several serverless platforms to choose from:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (GAE) -- the "OG" serverless platform that launched back in 2008 &amp;amp; somewhat modernized in 2018; uses customized, proprietary containers, free static file edge-caching, and generous outbound networking free tier&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Cloud Functions&lt;/a&gt; (GCF) -- originally serverless functions to compete with AWS Lambda; latest generation &lt;a href="https://cloud.google.com/blog/products/serverless/google-cloud-functions-is-now-cloud-run-functions" rel="noopener noreferrer"&gt;rebranded as Cloud Run Functions&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; (GCR) -- the latest serverless platform; OCI-compliant containers (&lt;a href="https://docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, &lt;a href="https://buildpacks.io" rel="noopener noreferrer"&gt;Buildpacks&lt;/a&gt;, etc.)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Since Cloud Functions has merged into Cloud Run, this leaves a pair of primary platforms to pick from. As far as deciding between App Engine and Cloud Run, most people would say go with Cloud Run as it is App Engine's next-generation replacement. It's more flexible (has fewer restrictions), supports modern app deployment (containers), and has many new features, including "Jobs," GPUs, and many more.&lt;/p&gt;

&lt;p&gt;See &lt;a href="http://bit.ly/4pGtBOp" rel="noopener noreferrer"&gt;this post to learn more about App Engine&lt;/a&gt;, including what it can still do that Cloud Run can't (yet), and see &lt;a href="http://bit.ly/43Y5t1g" rel="noopener noreferrer"&gt;&lt;em&gt;this&lt;/em&gt; post to learn more about Cloud Run&lt;/a&gt;. Those posts provide much more detail than what's in this post which focuses primarily on deployment instructions. The apps in the repo should be run on both platforms without code changes. See &lt;a href="http://bit.ly/4oYgJmC" rel="noopener noreferrer"&gt;this post to learn more about serverless and these platforms&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Requirements(s)
&lt;/h4&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Choose a platform&lt;/strong&gt; (or experiment and deploy to both).

&lt;ul&gt;
&lt;li&gt;If you pick App Engine, a running application is known as an "app," and you can only have one app in any Cloud project. &lt;a href="https://console.cloud.google.com/appengine" rel="noopener noreferrer"&gt;Enable GAE and create the app in the Cloud console&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If you pick Cloud Run, a running application is known as a "service," and you can have any number of services in a project. If you're new to GCR, your &lt;a href="https://console.cloud.google.com/run" rel="noopener noreferrer"&gt;Cloud Run dashboard&lt;/a&gt; will be empty.&lt;/li&gt;
&lt;li&gt;With Cloud Run, you also have an additional decision: Docker or not? While many developers are familiar with containers and specifying how they should be built via a &lt;code&gt;Dockerfile&lt;/code&gt;, others prefer to avoid Docker or managing &lt;code&gt;Dockerfile&lt;/code&gt;s, and Cloud Run supports both options. Sample &lt;code&gt;Dockerfile&lt;/code&gt;s are provided, but you can delete them before deploying or not have them for your own apps.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;Cost: billing required (but "free?!?")&lt;/strong&gt;&lt;br&gt;
Before deploying to the cloud, a word on cost. While many Google products are free to use, GCP products are not. In order to run the sample apps, you must &lt;a href="http://console.cloud.google.com/billing" rel="noopener noreferrer"&gt;enable billing&lt;/a&gt; backed by a financial instrument like a &lt;a href="https://cloud.google.com/appengine/docs/standard/payment-instrument" rel="noopener noreferrer"&gt;credit card&lt;/a&gt; (&lt;a href="https://support.google.com/paymentscenter/answer/9001356#allowed-methods" rel="noopener noreferrer"&gt;payment method depends on region/currency&lt;/a&gt;). If you're new to GCP, review the &lt;a href="https://cloud.google.com/billing/docs/onboarding-checklist" rel="noopener noreferrer"&gt;billing &amp;amp; onboarding guide&lt;/a&gt;. Deploying and running the sample app(s) in this post &lt;strong&gt;should not incur any cost&lt;/strong&gt; because basic usage falls under various free tiers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;p&gt;Several GCP products (like GAE &amp;amp; GCR) have an &lt;a href="https://cloud.google.com/free/docs/gcp-free-tier#free-tier-usage-limits" rel="noopener noreferrer"&gt;"Always Free" tier&lt;/a&gt;, a free daily or monthly usage quota before incurring charges. See the GCR &lt;a href="https://cloud.google.com/run/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; and &lt;a href="https://cloud.google.com/run/quotas" rel="noopener noreferrer"&gt;quotas&lt;/a&gt; pages for more information. Furthermore, deploying to GCP serverless platforms incur &lt;a href="https://cloud.google.com/run/pricing#source-deployments-and-functions" rel="noopener noreferrer"&gt;minor build and storage costs&lt;/a&gt;. (Also see &lt;a href="https://cloud.google.com/appengine/pricing#pricing-for-related-dynamic_data.site_values.cloud_name-products" rel="noopener noreferrer"&gt;similar content in the GAE docs&lt;/a&gt;.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;&lt;a href="https://cloud.google.com/build/pricing" rel="noopener noreferrer"&gt;Cloud Build&lt;/a&gt; has its own free quota as does &lt;a href="https://cloud.google.com/storage/pricing#cloud-storage-always-free" rel="noopener noreferrer"&gt;Cloud Storage&lt;/a&gt; (GCS), used to store build artifacts. Cloud Build sends application images to the Cloud &lt;a href="https://cloud.google.com/artifact-registry/pricing" rel="noopener noreferrer"&gt;Artifact Registry&lt;/a&gt; (CAR) (or its predecessor), making them accessible to other GCP services. These eat into GCS &amp;amp; CAR (storage) quotas as does &lt;a href="https://cloud.google.com/artifact-registry/pricing#data-transfer" rel="noopener noreferrer"&gt;transferring images&lt;/a&gt; between services &amp;amp; regions. You may be in a region that does &lt;strong&gt;not&lt;/strong&gt; have a free tier however, so monitor your usage to minimize any costs. (Check out storage use and delete old/unwanted build artifacts via the &lt;a href="https://console.cloud.google.com/storage/browser" rel="noopener noreferrer"&gt;GCS browser&lt;/a&gt;.)&lt;/p&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Use the &lt;a href="https://cloud.google.com/products/calculator" rel="noopener noreferrer"&gt;cost calculator&lt;/a&gt; to &lt;a href="https://cloud.google.com/billing/docs/how-to/estimate-costs" rel="noopener noreferrer"&gt;get monthly estimates&lt;/a&gt;. You may qualify for credits to offset GCP costs: If you are a startup, consider the &lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;GCP for Startups&lt;/a&gt; program grants. If you are in education, check out the &lt;a href="https://cloud.google.com/edu" rel="noopener noreferrer"&gt;GCP education programs&lt;/a&gt; for students, faculty, and researchers.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;This sample app is very basic and works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Start with an empty form asking the user for several input items, an image to upload and an LLM prompt:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F501wbannqrftt69qe4cx.png" alt="webgem-empty"&gt;
&lt;/li&gt;
&lt;li&gt;Select an image for Gemini using the normal file-picker:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqk5vdxtnc3yveb0dgjpi.png" alt="webgem-imgpick"&gt;
&lt;/li&gt;
&lt;li&gt;Enter a suitable prompt (or take the default "&lt;code&gt;Describe this image&lt;/code&gt;"):
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5st978noy1jmlluq83pw.png" alt="webgem-imgNpromptSet"&gt;
&lt;/li&gt;
&lt;li&gt;When the submit button is pressed, the button text changes to "Processing...", and once Gemini is done, a thumbnail of the image is provided along with LLM output:
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Feqflv0ja36miegvi85zx.png" alt="webgem-results"&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;I ripped these from the &lt;a href="http://bit.ly/3Kqv78c#app-operation" rel="noopener noreferrer"&gt;original post&lt;/a&gt; where you can get all the details about this app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;The basic web apps in this post are nearly identical to those in that original post. The only differences between &lt;a href="https://github.com/wescpy/google/tree/main/gemini/webgem" rel="noopener noreferrer"&gt;&lt;em&gt;that&lt;/em&gt; post's repo&lt;/a&gt; and &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem" rel="noopener noreferrer"&gt;&lt;em&gt;this&lt;/em&gt; post's repo&lt;/a&gt; are the additional files and packages required to deploy that web app to GCP serverless platforms:&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js: from original web app
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/.env_TMPL" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/.env_TMPL&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;.env&lt;/code&gt; environment settings template&lt;/td&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/package.json&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages&lt;/td&gt;
&lt;td&gt;Node&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/main.js" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/main.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Express.js sample app&lt;/td&gt;
&lt;td&gt;Node (CommonJS script)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/main.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/main.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Express.js sample app&lt;/td&gt;
&lt;td&gt;Node (ECMAScript module)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/templates/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/templates/index.html&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Web template&lt;/td&gt;
&lt;td&gt;Nunjucks (identical to Jinja2)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Node.js: new for serverless deployments
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/app.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/app.yaml&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Config file&lt;/td&gt;
&lt;td&gt;App Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/Dockerfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dockerfile&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;with&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/.dockerignore" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/.dockerignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;.dockerignore&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;with&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/Procfile" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/Procfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Procfile&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;without&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/nodejs/.gcloudignore" rel="noopener noreferrer"&gt;&lt;code&gt;nodejs/.gcloudignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;.gcloudignore&lt;/td&gt;
&lt;td&gt;App Engine &amp;amp; Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Python: from original web app
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/settings_TMPL.py" rel="noopener noreferrer"&gt;&lt;code&gt;python/settings_TMPL.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;settings.py&lt;/code&gt; environment settings template&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/requirements.txt" rel="noopener noreferrer"&gt;&lt;code&gt;python/requirements.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flask 3rd-party packages&lt;/td&gt;
&lt;td&gt;Python&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/main.py" rel="noopener noreferrer"&gt;&lt;code&gt;python/main.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Flask sample app&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/templates/index.html" rel="noopener noreferrer"&gt;&lt;code&gt;python/templates/index.html&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Web template&lt;/td&gt;
&lt;td&gt;Jinja2 (identical to Nunjucks)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/fastapi/requirements.txt" rel="noopener noreferrer"&gt;&lt;code&gt;python/fastapi/requirements.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;FastAPI 3rd-party packages&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/fastapi/main.py" rel="noopener noreferrer"&gt;&lt;code&gt;python/fastapi/main.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;FastAPI sample app&lt;/td&gt;
&lt;td&gt;Python 3&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Python: new for serverless deployments
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Platform&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/app.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;python/app.yaml&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Config file (Flask)&lt;/td&gt;
&lt;td&gt;App Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;python/Dockerfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dockerfile (Flask)&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;with&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/.dockerignore" rel="noopener noreferrer"&gt;&lt;code&gt;python/.dockerignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;.dockerignore&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;with&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/Procfile" rel="noopener noreferrer"&gt;&lt;code&gt;python/Procfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Procfile (Flask)&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;without&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/.gcloudignore" rel="noopener noreferrer"&gt;&lt;code&gt;python/.gcloudignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;.gcloudignore&lt;/td&gt;
&lt;td&gt;App Engine &amp;amp; Cloud Run&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/fastapi/app.yaml" rel="noopener noreferrer"&gt;&lt;code&gt;python/fastapi/app.yaml&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Config file (FastAPI)&lt;/td&gt;
&lt;td&gt;App Engine&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/fastapi/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;python/fastapi/Dockerfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Dockerfile (FastAPI)&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;with&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem/python/fastapi/Procfile" rel="noopener noreferrer"&gt;&lt;code&gt;python/fastapi/Procfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Procfile (FastAPI)&lt;/td&gt;
&lt;td&gt;Cloud Run (&lt;strong&gt;without&lt;/strong&gt; Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Run locally
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;p&gt;Earlier, you changed to the Node folder (with &lt;code&gt;cd nodejs&lt;/code&gt;) and installed all packages (&lt;code&gt;npm i&lt;/code&gt;), so to run the app locally, execute: &lt;code&gt;node main.mjs&lt;/code&gt; (or `node main.js for CommonJS).&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;Earlier, you changed to the Python folder (with &lt;code&gt;cd python&lt;/code&gt;). If you decided to run the Flask version, you would've installed all packages (with &lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt;). If you chose FastAPI instead, you would've replaced the Flask files (with &lt;code&gt;mv fastapi/* .&lt;/code&gt;, then run the same install command. To run the app locally (either version), execute: &lt;code&gt;python main.py&lt;/code&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to the cloud
&lt;/h2&gt;

&lt;p&gt;With cloud deployments, you have many more options, all are listed below along with instructions once you've doublechecked you have a &lt;code&gt;.env&lt;/code&gt; or &lt;code&gt;settings.py&lt;/code&gt; file with your API key.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node.js (ECMAscript module) on App Engine&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;gcloud app deploy&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js (CommonJS script) on App Engine&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;package.json&lt;/code&gt; and change &lt;code&gt;main.mjs&lt;/code&gt; to &lt;code&gt;main.js&lt;/code&gt; globally&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud app deploy&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js (ECMAscript module) on Cloud Run with Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Example:&lt;/strong&gt; &lt;code&gt;gcloud run deploy genai --allow-unauthenticated --source . --region us-west1&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ol&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Node.js (CommonJS script) on Cloud Run with Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;package.json&lt;/code&gt; and change &lt;code&gt;main.mjs&lt;/code&gt; to &lt;code&gt;main.js&lt;/code&gt; globally&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Node.js (ECMAscript module) on Cloud Run without Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Delete &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Node.js (CommonJS script) on Cloud Run without Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Edit &lt;code&gt;package.json&lt;/code&gt; and change &lt;code&gt;main.mjs&lt;/code&gt; to &lt;code&gt;main.js&lt;/code&gt; globally&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python Flask on App Engine&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;gcloud app deploy&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python FastAPI on App Engine&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;mv fastapi/* .&lt;/code&gt; (if you haven't already)&lt;/li&gt;
&lt;li&gt;Edit &lt;code&gt;requirements.txt&lt;/code&gt; and uncomment the line for &lt;code&gt;gunicorn&lt;/code&gt; (it's required)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud app deploy&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python Flask on Cloud Run with Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python FastAPI on Cloud Run with Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;mv fastapi/* .&lt;/code&gt; (if you haven't already)&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python Flask on Cloud Run without Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Delete &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python FastAPI on Cloud Run without Docker&lt;/strong&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;mv fastapi/* .&lt;/code&gt; (if you haven't already)&lt;/li&gt;
&lt;li&gt;Delete &lt;code&gt;Dockerfile&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;, replacing &lt;code&gt;SVC_NAME&lt;/code&gt; with your service name (see Example above)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Here are a couple of results from my deployments to App Engine (&lt;code&gt;*.appspot.com&lt;/code&gt;) and Cloud Run (&lt;code&gt;*.run.app&lt;/code&gt;):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ivos2fjpe4rqdwnyyql.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4ivos2fjpe4rqdwnyyql.png" alt="Web app on App Engine"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Web app sample result on App Engine



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwesnv5kfho4s44am88n.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjwesnv5kfho4s44am88n.png" alt="Web app on Cloud Run"&gt;&lt;/a&gt;&lt;/p&gt;
[IMG] Web app sample result on Cloud Run



&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="http://bit.ly/3Kqv78c" rel="noopener noreferrer"&gt;original post&lt;/a&gt; covered a basic web app using the Gemini API. In this post, we follow-up with that same code sample, but add in the extra files needed to deploy it to the Cloud, specifically on GCP serverless platforms which have the benefit of incurring billing only if your app is getting traffic, unlike on VMs where you're billed 24x7. Learning how to create basic web apps using the Gemini API is useful, but if you want to bring it to the world, deploying to serverless is one way to do it!&lt;/p&gt;

&lt;p&gt;If you found an error in this post, a bug in &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic you want me to cover in the future, drop a note in the comments below or file an issue at the repo. Also check out other posts in the &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini API series&lt;/a&gt; as well as the &lt;a href="https://dev.to/wescpy/series/30098"&gt;GCP serverless series&lt;/a&gt;. Thanks for reading, and I hope to meet you at an upcoming event soon... see the travel calendar at the bottom of my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting site&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PREV POST:&lt;/strong&gt; &lt;a href="http://bit.ly/3Kqv78c" rel="noopener noreferrer"&gt;Basic web apps using the Gemini API&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various links relevant to this post. Also included below are links to numerous amount of GCP serverless content I produced during my time at Google.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/multi/webgem" rel="noopener noreferrer"&gt;Sample in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt; (Python &amp;amp; Node.js)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://github.com/wescpy/google/tree/main/gemini" rel="noopener noreferrer"&gt;Code samples for Gemini posts&lt;/a&gt; (except &lt;em&gt;this one&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini API (Google AI) general
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs" rel="noopener noreferrer"&gt;General GenAI docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/gemini_api_overview" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/sdks" rel="noopener noreferrer"&gt;API SDKs/supported languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/quickstart" rel="noopener noreferrer"&gt;API quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/generative-ai-docs/blob/main/site/en/tutorials/quickstart_colab.ipynb" rel="noopener noreferrer"&gt;Jupyter Notebook QuickStart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing&lt;/a&gt; (free tier available)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/migrate" rel="noopener noreferrer"&gt;Google AI migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/migrate_to_cloud" rel="noopener noreferrer"&gt;GCP Vertex AI for Google AI users&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models" rel="noopener noreferrer"&gt;Developer overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini/flash" rel="noopener noreferrer"&gt;Flash models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/cookbook" rel="noopener noreferrer"&gt;Cookbook repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Gemini API content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/3Kqv78c" rel="noopener noreferrer"&gt;Basic web apps using the Gemini API&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;Gemini 2.5 API missing manual&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Other posts in Gemini API series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GCP serverless platforms
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (GAE)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Google Cloud Functions (Gen1) and Cloud Run Functions (Gen2)&lt;/a&gt; (GCF/GCRF)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://cloud.run" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; (GCR)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GAE content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/4pGtBOp" rel="noopener noreferrer"&gt;Hosting apps today on Google App Engine&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/python-app-engine-jan-2024-deprecation-what-you-need-to-know-4bci"&gt;GAE (Gen1 &amp;amp; early Gen2) 2024 deprecation "what you need to know"&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/lft-aeb" rel="noopener noreferrer"&gt;Rapid cloud development using App Engine for the Cycle Hire Widget app&lt;/a&gt; case study post&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/j553Wf" rel="noopener noreferrer"&gt;Change the world in 10 lines of code!&lt;/a&gt; video&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GCR content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/43Y5t1g" rel="noopener noreferrer"&gt;Modern app-hosting with Google Cloud Run&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;GCR sample weather alerts app &amp;amp; "always-on CPU" use case

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/use-cloud-run-always-cpu-allocation-background-work" rel="noopener noreferrer"&gt;Blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://youtu.be/ul1cGarS23M" rel="noopener noreferrer"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GCR sample YouTube comment tracker app &amp;amp; full design use case

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="http://youtu.be/GyRHRK61qro" rel="noopener noreferrer"&gt;Designing a user interface quickly (frontend/design)&lt;/a&gt; video&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="http://youtu.be/ertbL2Rxbvk" rel="noopener noreferrer"&gt;Design a serverless architecture (backend/server-side)&lt;/a&gt; video&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Migrate GAE apps to GCR...

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="https://goo.gle/3geAqFs" rel="noopener noreferrer"&gt;...with Docker&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;Part 1: &lt;a href="http://youtu.be/uI1mzwtx4ZM?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_mgrcrdckr_sms_201017&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;Video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 1: &lt;a href="http://g.co/codelabs/pae-migrate-rundocker" rel="noopener noreferrer"&gt;Codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="http://goo.gle/3t3uyUN" rel="noopener noreferrer"&gt;...without Docker/with Buildpacks&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="http://youtu.be/VP3g2OZYXPE?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_mgrcrbdpk_sms_201031&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;Video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: &lt;a href="http://g.co/codelabs/pae-migrate-runbldpks" rel="noopener noreferrer"&gt;Codelab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GCP serverless content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://bit.ly/4oYgJmC" rel="noopener noreferrer"&gt;Serverless overview&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://youtu.be/ertbL2Rxbvk?list=PL2pQQBHvYcs0PEecTcLD9_VaLvuhK0_VQ" rel="noopener noreferrer"&gt;How to design serverless apps&lt;/a&gt; video&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Explore GCP serverless platforms with a nebulous sample app&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: &lt;a href="http://youtu.be/gle26fT28Bw?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;Picking the "right" serverless platform&lt;/a&gt; video&lt;/li&gt;
&lt;li&gt;Part 2: Deploy the &lt;strong&gt;same app&lt;/strong&gt; to App Engine, Cloud Functions, or Cloud Run

&lt;ul&gt;
&lt;li&gt;&lt;a href="http://goo.gle/2Y0ph5q" rel="noopener noreferrer"&gt;Blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://youtu.be/eTotLOVR7MQ?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservdeploy_neb_2021xx&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/nebulous-serverless" rel="noopener noreferrer"&gt;Code repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://twitter.com/googledevs/status/1442903087497183233?utm_source=twitter&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebserv_sms_201028&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Social post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-flask?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 2 or 3 sample app locally&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;del&gt;Run Python 2 sample app on GAE codelab&lt;/del&gt; (deprecated: 2.x no longer supported by GAE)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gae3?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 3 sample app on GAE&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcf?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 3 sample app on GCF (Gen1)&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcr2?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 2 sample app on GCR with Docker&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcr3?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 3 sample app on GCR with Docker&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcrbp?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Python 3 sample app on GCR without Docker/with Buildpacks&lt;/a&gt; codelab&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-nodejs?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;Run Node.js sample app on GAE, GCF (Gen1), and GCR&lt;/a&gt; codelab&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/calling-google-apis-serverless-part-i-cloud-apis?utm_source=blog&amp;amp;utm_medium=partner&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007" rel="noopener noreferrer"&gt;How to call Google APIs from GCP serverless platforms&lt;/a&gt; post&lt;/li&gt;

&lt;li&gt;

&lt;a href="http://youtu.be/5qOwYSCb1Gg" rel="noopener noreferrer"&gt;Top 3 pain points for serverless developers&lt;/a&gt; video&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I was a member of the GCP serverless product team 2009-2013 and 2020-2023. While product information is as accurate as I can find or recall, the opinions are my own.&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for &lt;a href="https://linuxjournal.com/article/5948" rel="noopener noreferrer"&gt;Linux Journal&lt;/a&gt; &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with Google integrations, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>ai</category>
      <category>googlecloud</category>
      <category>api</category>
    </item>
    <item>
      <title>Inspiring Google Developers video retrospective</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 06 Oct 2025 23:03:04 +0000</pubDate>
      <link>https://dev.to/gde/inspiring-google-developers-video-retrospective-4c15</link>
      <guid>https://dev.to/gde/inspiring-google-developers-video-retrospective-4c15</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;In this post, rather than focus on a specific code sample or API feature, I take a step back to thank viewers for their kind regards and recollect some of the videos I produced during my time as a Googler. In addition to linking to a body-of-work table of contents, this retrospective features a subset representing favorites and those I feel are more impactful in one way or another.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/wescpy/google/tree/main/videos" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fuf6yh96mtpqxk2o344ub.jpg" alt="YouTube banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction and motivation
&lt;/h2&gt;

&lt;p&gt;Welcome to the place where I help developers (and now vibe-coding LLMs) learn about using Google APIs from Python (and sometimes Node.js), touching multiple API families, "oiling" the squeaky parts to smoothen onboarding friction. I &lt;strong&gt;especially&lt;/strong&gt; like to cover content that's &lt;em&gt;not&lt;/em&gt; in Google's documentation. These are the main topics covered so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Google Cloud/GCP:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;(predictive) AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt;:&lt;/strong&gt; Gmail, Drive, Sheets, etc.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Google Maps Platform/GMP&lt;/a&gt;&lt;/strong&gt;&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt; and generative AI&lt;/strong&gt; ("genAI")&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Credentials:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Old vs. new (client) libraries:&lt;/strong&gt; &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;Python auth&lt;/a&gt;, &lt;a href="https://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;Gemini API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Part of my work in helping developers use APIs like those above includes producing video content along with blog posts and code samples. When developers come up to me to personally thank me for the video content I produced during my time at Google, it's quite humbling but also empowering. Lowering the barrier-to-entry and getting you going is always a key goal.&lt;/p&gt;

&lt;p&gt;While I spent almost equal time on the GCP team as well as on GWS (formerly "G Suite"), I produced more content for the latter product family, and this ratio is reflected across my content. The selection criteria the videos I'd like to highlight include those which stand out to me as far as uniqueness, usefulness, and impact.&lt;/p&gt;

&lt;p&gt;Nearly all were produced before generative AI ("genAI") took over our collective conscience, so this content and any corresponding code samples, codelab tutorials, and/or blog posts can be leveraged for finetuning LLMs (large language models) as well as by software engineers either building MCP (model context protocol) servers or guiding/correcting vibe code generation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Featured videos &amp;amp; content
&lt;/h2&gt;

&lt;p&gt;Most videos are generally part of a related series, whether to introduce API usage of a product family or having a common goal. Still others are one-offs, whether produced or recorded as part of a public presentation. Here are my top 8, each blessed with an "award category," and an honorable mention not produced by the Google developer studio team:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;(BEST "ROAD SHOW") &lt;strong&gt;Creating events in Google Calendar&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(BEST INTERN) &lt;strong&gt;Expediting expense reports with Gmail add-ons&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(BEST "ACTING") &lt;strong&gt;Automating YouTube stats with Google Apps Script&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(BEST "FIGHT [SCENE]") &lt;strong&gt;File Storage in the Cloud&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(MOST "INTEROPERABLE") &lt;strong&gt;Deploy the same app to App Engine, Cloud Functions &amp;amp; Cloud Run&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(MOST INTERESTING PORT) &lt;strong&gt;Migrate App Engine Users service to Cloud Identity Platform&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(MOST VIEWED) &lt;strong&gt;Google Drive API: Uploading &amp;amp; Downloading Files&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(MOST ASPIRATIONAL) &lt;strong&gt;Change the world in 10 lines of code&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;(HONORABLE MENTION) &lt;strong&gt;Building an AI-enhanced enterprise image processing workflow&lt;/strong&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's dive into each:&lt;/p&gt;




&lt;h3&gt;
  
  
  1. (BEST "ROAD SHOW") Creating events in Google Calendar
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/tNo9IoZMelI"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/calendar" rel="noopener noreferrer"&gt;Calendar API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gl/KuYMiq" rel="noopener noreferrer"&gt;Google Calendar API invites users to great experiences&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Imagine building the next-generation travel app where your users book airfare, hotel, and car rental/hire, only to have to wait for the email confirmations from each, then enter the correct information into Google Calendar. Wouldn't it be better if you automated this step for them, and furthermore, updated those entries if/when plans change?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Most of our videos are filmed in a studio with one or more producers guiding us through the process. Instead, we "went on-the-road" for this one, situating ourselves in one of Google's legendary cafes. The extras are colleagues, video production staff or fellow Developer Advocates joining me to show how to have a great experience together, in much the same way the Calendar API lets you give &lt;em&gt;your&lt;/em&gt; users great experiences.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEQUEL:&lt;/strong&gt; "&lt;a href="https://youtu.be/Qd64idiKZWw" rel="noopener noreferrer"&gt;Modifying events with the Google Calendar API&lt;/a&gt;," and an accompanying &lt;a href="http://goo.gl/J2XkXc" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  2. (BEST INTERN) &lt;strong&gt;Expediting expense reports with Gmail add-ons&lt;/strong&gt;
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/9cDvkVCcIWE"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/apps-script" rel="noopener noreferrer"&gt;Google Apps Script&lt;/a&gt; (&lt;a href="https://developers.google.com/workspace/add-ons/gmail" rel="noopener noreferrer"&gt;Gmail add-ons&lt;/a&gt;, &lt;a href="https://developers.google.com/apps-script/reference/spreadsheet" rel="noopener noreferrer"&gt;Spreadsheet Service&lt;/a&gt; [Sheets API])&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gl/KUVCDu" rel="noopener noreferrer"&gt;Gmail Add-ons framework now available to all developers&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CODELAB:&lt;/strong&gt; &lt;a href="http://g.co/codelabs/gmail-add-ons" rel="noopener noreferrer"&gt;Make email more actionable with Google Workspace Add-ons&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOURCE:&lt;/strong&gt; &lt;a href="https://github.com/googleworkspace/gmail-add-on-codelab" rel="noopener noreferrer"&gt;Expense It! -- Gmail Add-ons Codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Gmail add-ons are a way to integrate your application with Gmail without requiring your users to leave Gmail. Imagine being able to process expense reports directly from Gmail while reviewing your confirmation emails. The add-on is able to extract all relevant data for each expense into their own rows in Google Sheets, expediting one of your least favorite chores when traveling for work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Another fun video outside the studio, this one features colleague &lt;a href="https://linkedin.com/in/alison-carroll-5480a9a4" rel="noopener noreferrer"&gt;Alison Carroll&lt;/a&gt; and my summer intern at-the-time, &lt;a href="https://linkedin.com/in/sundar-solai" rel="noopener noreferrer"&gt;Sundar Solai&lt;/a&gt;, "No, not &lt;em&gt;that&lt;/em&gt; Sundar," as he's often quoted. &lt;code&gt;:-)&lt;/code&gt; He appears to be a little nervous in this video but passed with flying colors! More importantly, he built the entire Gmail add-on.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  3. (BEST "ACTING") Automating YouTube stats with Google Apps Script
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/cKIVdzCf8ic"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/apps-script" rel="noopener noreferrer"&gt;Google Apps Script&lt;/a&gt; (&lt;a href="https://developers.google.com/apps-script/advanced/youtube" rel="noopener noreferrer"&gt;YouTube Data API Advanced Service&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gl/nKfBQi" rel="noopener noreferrer"&gt;(A day at the office) Automating YouTube stats with Google Apps Script&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; One of the key reasons why Apps Script exists is to help GWS users automate tasks. Those who produce videos published on YouTube know that tracking metrics is a necessary KPI (key performance indicator) to determine if you're reaching your goals. However, this is a daunting task &lt;em&gt;without&lt;/em&gt; automation, so this video shows how you can fetch the corresponding data for all videos listed in Google Sheets.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Yet another video outside the studio, we wanted to portray how an analyst can quickly get on the "good side" of his boss in an "office space" by automating with Apps Script.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  4. (BEST "FIGHT [SCENE]") File Storage in the Cloud
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/vyIap827rHs"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/drive" rel="noopener noreferrer"&gt;Google Drive API&lt;/a&gt;, &lt;a href="https://cloud.google.com/storage" rel="noopener noreferrer"&gt;Cloud Storage API&lt;/a&gt;, &lt;a href="https://cloud.google.com/persistent-disk" rel="noopener noreferrer"&gt;Cloud Persistent Disk API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Both Google Drive and Cloud Storage are object stores in-the-cloud. That said, how do they differ? Each has more applicable use cases, so this case study looks into when you would use each as well as what Cloud Persistent Disk is used for. &lt;strong&gt;NOTE:&lt;/strong&gt; This video was produced before &lt;a href="https://cloud.google.com/filestore" rel="noopener noreferrer"&gt;Cloud Filestore&lt;/a&gt; launched, and it would've been featured had it existed at the time.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; One of my earliest videos, this is essentially a "Google Drive vs. Cloud Storage" video. The challenge in making this content was that it required the buy-in from both the Google Drive and Cloud Storage teams, which at times didn't see eye-to-eye. To date, this is the &lt;em&gt;only&lt;/em&gt; video ever produced by Google featuring both products receiving sign-off from both PMs (product managers).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  5. (MOST "INTEROPERABLE") Deploy the same app to App Engine, Cloud Functions &amp;amp; Cloud Run
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/eTotLOVR7MQ"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt;, &lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Cloud Functions&lt;/a&gt;, &lt;a href="https://cloud.run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt;, &lt;a href="https://cloud.google.com/translate" rel="noopener noreferrer"&gt;Cloud Translation API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gle/2Y0ph5q" rel="noopener noreferrer"&gt;Exploring serverless with a nebulous app&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CODELAB(s):&lt;/strong&gt; &lt;a href="https://github.com/wescpy/google/tree/main/videos#nebulous-serverless-codelabs-deploy-the-same-app-to-app-engine-cloud-functions-and-cloud-run" rel="noopener noreferrer"&gt;&lt;em&gt;multiple codelabs available&lt;/em&gt;&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOURCE:&lt;/strong&gt; &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/cloud/python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;, &lt;a href="https://github.com/wescpy/nebulous-serverless/tree/main/cloud/nodejs" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; While App Engine was Google's original serverless platform (before that term was even coined), the next generation platforms, Cloud Functions and Cloud Run are similar yet different. Which platform is right for you? Can you make a mistake choosing the "wrong one?" This content serves as a "myth-buster" you can't laterally move across platforms by demonstrating an app that can be deployed across all three. &lt;strong&gt;NOTE:&lt;/strong&gt; This content features the 1st-generation Cloud Functions platform before its 2nd-generation service was combined with Cloud Run and rebranded as "Cloud Run Functions."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Prior to this content, Google had never showcased an application that could be
deployed to all GCP serverless platforms &lt;em&gt;with no code changes&lt;/em&gt;, demonstrating interoperability between them. A side goal of this content was to showcase how to call Google APIs from GCP serverless platforms... in this case, the closest API-equivalent for Google Translate, &lt;em&gt;another&lt;/em&gt; interoperability nod.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  6. (MOST INTERESTING PORT) Migrate App Engine Users service to Cloud Identity Platform
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/Ofo0qMgGesE"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (&lt;a href="https://cloud.google.com/appengine/docs/standard/services/users" rel="noopener noreferrer"&gt;Users API&lt;/a&gt;), &lt;a href="https://cloud.google.com/identity-platform" rel="noopener noreferrer"&gt;Cloud Identity Platform&lt;/a&gt; (&lt;a href="https://firebase.google.com/products/auth" rel="noopener noreferrer"&gt;Firebase Authentication&lt;/a&gt;), &lt;a href="https://cloud.google.com/resource-manager" rel="noopener noreferrer"&gt;Cloud Resource Manager (API)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="https://https://developers.googleblog.com/2023/01/migrating-from-app-engine-users-to-cloud-identity-module-21.html?utm_source=blog&amp;amp;utm_medium=partner&amp;amp;utm_campaign=CDR_wes_aap-serverless_mgridenplat_sms_202119" rel="noopener noreferrer"&gt;Migrate App Engine Users service to Cloud Identity Platform&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CODELAB:&lt;/strong&gt; &lt;a href="http://g.co/codelabs/pae-migrate-idenplat" rel="noopener noreferrer"&gt;Migrate from App Engine Users service to Cloud Identity Platform (Module 21)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOURCE:&lt;/strong&gt; &lt;a href="https://github.com/wescpy/migrate-python2-appengine/tree/master/mod21b-idenplat" rel="noopener noreferrer"&gt;Python 3&lt;/a&gt;, &lt;a href="https://github.com/wescpy/migrate-python2-appengine/tree/master/mod21a-idenplat" rel="noopener noreferrer"&gt;Python 2&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Moving off App Engine's old "Users API" (think early version of &lt;a href="https://developers.google.com/identity/gsi" rel="noopener noreferrer"&gt;Sign-In with Google&lt;/a&gt; for your App Engine apps) isn't that straightforward, especially since there's no GCP equivalent. The closest product in GCP to authentication service is the Cloud Identity Platform, the "enterprise" version of &lt;a href="https://firebase.google.com/products/auth" rel="noopener noreferrer"&gt;Firebase Authentication&lt;/a&gt;. Since neither product have anything in common, I bridged this gap as part of the code sample.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; There is literally no overlap between the App Engine Users API and the GCP version of Firebase Auth, Cloud Identity Platform (GCIP). In order to build this migration sample, I had to shift the functionality from server-side to client-side as well as create a proxy for the App Engine Admin role. So this was the most interesting port, because it was a code migration as well as some new code to make up for missing functionality.&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  7. (MOST VIEWED) Google Drive API: Uploading &amp;amp; Downloading Files
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/-7YH6rdR-tk"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/drive" rel="noopener noreferrer"&gt;Google Drive API&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gl/M3LQFQ" rel="noopener noreferrer"&gt;Google Drive: Uploading &amp;amp; downloading files plus the new v3 API redux&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Almost a decade ago, Google launched the 3rd version of the Drive API. As there were some backwards-incompatible name changes from the v2 API, it was necessary to show developers some of the basics of using v3. I look forward to modernizing the code and making it available soon.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Of all my videos, one of them &lt;em&gt;has&lt;/em&gt; to be the most viewed, and not surprisingly, it's this one, which shows developers how to upload files &lt;em&gt;to&lt;/em&gt; and download files &lt;em&gt;from&lt;/em&gt; Google Drive. So far, it's garnered about a quarter-million views.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SEQUEL:&lt;/strong&gt; "&lt;a href="https://youtu.be/5bifEBJRlaQ" rel="noopener noreferrer"&gt;Introducing Shared (formerly Team) Drives&lt;/a&gt;" and an accompanying &lt;a href="http://goo.gl/OWHzaN" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PREQUEL:&lt;/strong&gt; "&lt;a href="https://youtu.be/Z5G0luBohCg" rel="noopener noreferrer"&gt;Listing your files in Google Drive&lt;/a&gt;," an accompanying &lt;a href="https://developers.googleblog.com/2014/11/launchpad-online-for-developers-getting.html" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;, &lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;, and &lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;source code&lt;/a&gt; (Python)&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  8. (MOST ASPIRATIONAL) Change the world in 10 lines of code
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/eZj7uM9HaMg"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (&lt;a href="https://cloud.google.com/appengine/docs/standard/services/mail/receiving-mail-with-mail-api?tab=python" rel="noopener noreferrer"&gt;Inbound Mail API&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; Another one of my earliest videos, this one highlights one of the lesser-known features of App Engine's original APIs, the inbound Mail API. We all easily understand the how and the why of &lt;em&gt;sending&lt;/em&gt; email from an application, but having an app &lt;em&gt;receive&lt;/em&gt; email (and process it)? Now that's another matter.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; Most people have the ability to use email from their computers or mobile devices. While sending email to &lt;em&gt;people&lt;/em&gt; is something often done, imagine what you could do by sending email to &lt;em&gt;an app&lt;/em&gt;. With the proper authorization, users can effect almost any action they can think of (provided there are apps that process inbound emails).&lt;/li&gt;
&lt;/ul&gt;




&lt;h3&gt;
  
  
  9. (HONORABLE MENTION) Building an AI-enhanced enterprise image processing workflow
&lt;/h3&gt;

&lt;p&gt;

  &lt;iframe src="https://www.youtube.com/embed/4VPpt5JVsj8"&gt;
  &lt;/iframe&gt;


&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;FEATURED:&lt;/strong&gt; &lt;a href="https://developers.google.com/drive" rel="noopener noreferrer"&gt;Google Drive API&lt;/a&gt;, &lt;a href="https://cloud.google.com/storage" rel="noopener noreferrer"&gt;Cloud Storage API&lt;/a&gt;, &lt;a href="https://cloud.google.com/vision" rel="noopener noreferrer"&gt;Cloud Vision API&lt;/a&gt;, &lt;a href="https://developers.google.com/sheets" rel="noopener noreferrer"&gt;Sheets API&lt;/a&gt; (&lt;em&gt;and later&lt;/em&gt;: &lt;a href="https://developers.google.com/maps" rel="noopener noreferrer"&gt;Maps [Static] API&lt;/a&gt;, &lt;a href="https://ai.google.dev/docs/gemini_api_overview" rel="noopener noreferrer"&gt;Gemini API&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;POST:&lt;/strong&gt; &lt;a href="http://goo.gle/3nPxmlc" rel="noopener noreferrer"&gt;Image archive, analysis, and report generation with Google APIs&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CODELAB:&lt;/strong&gt; &lt;a href="http://g.co/codelabs/drive-gcs-vision-sheets" rel="noopener noreferrer"&gt;Image archiving, analysis, and report generation Google Workspace &amp;amp; Google Cloud&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SOURCE:&lt;/strong&gt; &lt;a href="https://github.com/wescpy/analyze_gsimg" rel="noopener noreferrer"&gt;Python&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;WHY:&lt;/strong&gt; With over a decade of experience working on various Google APIs, I wanted to create content leveraging APIs from different product groups to solve possible business problems. I imagine an employee (like myself) with over a decade of Google Drive usage. The "My Drive" page is rife with numerous folders, preventing me from seeing documents I most recently worked on. Those in asset-heavy industries like manufacturing, advertising, architecture, and the media would have even &lt;em&gt;more&lt;/em&gt; "clutter." There must be a way to safely backup these assets and clean up your Google Drive so it becomes useful again, and that's what this code sample does.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;"AWARD:"&lt;/strong&gt; This video &lt;em&gt;should&lt;/em&gt; get "most interoperable" because it demonstrates how to use APIs from both GCP &amp;amp; GWS to solve a potential enterprise problem. Images are downloaded from Google Drive, backed up to Cloud Storage, analyzed with Cloud Vision (and in the future, Google Maps and Gemini), annotated in Google Sheets, including links to the archived locations in Cloud Storage. Detailed records are saved, allowing for asset folders to be deleted, de-cluttering users' Google Drives. However, I didn't get a chance to craft a proper developer video and can only link to the only public recording of me covering this topic. Since that time, I've further enhanced the app by adding the &lt;a href="https://github.com/wescpy/analyze_gsimg#description" rel="noopener noreferrer"&gt;use of the Google Maps Static and Gemini APIs to the original code sample&lt;/a&gt; to bring it into the genAI era. All-in-all, it is now a solution that demonstrates six different Google APIs in a single solution for a potential business problem.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;So there you have it, the top 8 (well, 9) videos I produced to help developers use various Google developer platforms &amp;amp; APIs. I hope to further explore each of them in more detail, including modernizing the code samples for today's developer, whether for custom MCP servers or to calibrate or fix generated vibe code. More information on these videos as well as all of the others I produced during my time at Google can be found in &lt;a href="https://github.com/wescpy/google/tree/main/videos" rel="noopener noreferrer"&gt;the entire video catalogue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If you found an error in this post or have a topic I should cover, drop a note in the comments below or file an issue at the repo. I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources related to this post which you may find useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Miscellaneous
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/videos" rel="noopener noreferrer"&gt;Entire video catalog&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GWS APIs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/videos#google-workspace--gws-gmail-google-drive-docs-sheets-slides-etc" rel="noopener noreferrer"&gt;Video selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/calendar" rel="noopener noreferrer"&gt;Google Calendar API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/drive" rel="noopener noreferrer"&gt;Google Drive API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/sheets" rel="noopener noreferrer"&gt;Google Sheets API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/apps-script" rel="noopener noreferrer"&gt;Google Apps Script&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GCP APIs
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Serverless
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/videos#gcp-serverless-platforms-app-engine-cloud-functions-cloud-run-playlist-tbd" rel="noopener noreferrer"&gt;Video selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Cloud Functions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  (Predictive) AI/ML
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/videos#gcp-general-and-multiple-gcp-products" rel="noopener noreferrer"&gt;Video selection&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/translate" rel="noopener noreferrer"&gt;Cloud Translation API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vision" rel="noopener noreferrer"&gt;Cloud Vision API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Storage
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/storage" rel="noopener noreferrer"&gt;Cloud Storage API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/persistent-disk" rel="noopener noreferrer"&gt;Cloud Persistent Disk&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h4&gt;
  
  
  Other GCP
&lt;/h4&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/identity-platform" rel="noopener noreferrer"&gt;Cloud Identity Platform (GCIP)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/resource-manager" rel="noopener noreferrer"&gt;Cloud Resource Manager API&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for &lt;a href="https://linuxjournal.com/article/5948" rel="noopener noreferrer"&gt;Linux Journal&lt;/a&gt; &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with Google integrations, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>api</category>
      <category>python</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Gmail API developer intro: spotting chatty threads</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Wed, 06 Aug 2025 07:53:08 +0000</pubDate>
      <link>https://dev.to/googleworkspace/gmail-api-developer-intro-spotting-chatty-threads-jnp</link>
      <guid>https://dev.to/googleworkspace/gmail-api-developer-intro-spotting-chatty-threads-jnp</guid>
      <description>&lt;h2&gt;
  
  
  Primer on using the Gmail #API... TL;DR:
&lt;/h2&gt;

&lt;p&gt;Google Workspace (GWS) APIs like Drive, Docs, and Sheets can be useful for processing documents. GWS also does messaging, e.g., Gmail, Chat, and Meet, so let's take a look, starting with Gmail. A decade ago today, I &lt;a href="http://goo.gl/OfCbOz" rel="noopener noreferrer"&gt;posted about the Gmail API&lt;/a&gt; for the first time. It's time to modernize that content &amp;amp; code sample to the current ecosystem so Python &amp;amp; Node.js developers know how to use the API to query inboxes looking for "chatty threads," those that have at least 3 messages. While it's a "Hello World" 102 sample, it gets &lt;em&gt;you&lt;/em&gt; started building automated workflows or creating Gmail-related MCP servers for your AI agentic apps.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f8mmfnxcxtozklil0dj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7f8mmfnxcxtozklil0dj.png" alt="Gmail banner"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction and motivation
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog focused on using Google APIs from Python (and sometimes Node.js) covering different parts of Google's developer ecosystem, from APIs to compute and AI/ML platforms where I "oil" the squeaky parts to smoothen onboarding friction. I &lt;strong&gt;especially&lt;/strong&gt; like to cover content that's &lt;em&gt;not&lt;/em&gt; in Google's documentation. Here are the topics I've covered thus far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP: &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt;: Gmail, Drive, Sheets, etc. (&lt;em&gt;this series&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Generative AI ("genAI") with &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Credentials: &lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Old vs. new (client) libraries: &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;Python auth&lt;/a&gt;, &lt;a href="https://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;Gemini API&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Workspace itself is a platform with many applications (and corresponding APIs). With enough coverage on documents, it's time to take a look at messaging, starting with the &lt;a href="https://developers.google.com/gmail" rel="noopener noreferrer"&gt;Gmail API&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;It was exactly a decade ago to this day when I published the &lt;a href="http://goo.gl/OfCbOz" rel="noopener noreferrer"&gt;original version of this blog post&lt;/a&gt;. To be honest, the code hasn't changed much at all. The client libraries are different along with some minor improvements, but the core application remains intact. Today I'm happy to reprise that sample app, make it available in Node as well as provide modernized Python versions.&lt;/p&gt;

&lt;p&gt;The purpose of the original post was meant to justify why Gmail should have an API. Back then without an API, there was no ability to access user inboxes or Gmail features. The only option for developers at the time was to use standard email protocols like SMTP, IMAP, and POP. Unfortunately, they only operate at the message level. They have no clue about email threads, signatures, search, labels, filters, etc. Some of these motivations are addressed in the &lt;a href="http://youtu.be/UhdiQmS3kDs" rel="noopener noreferrer"&gt;original Gmail API launch video&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updated (permission) scopes
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Scopes background
&lt;/h3&gt;

&lt;p&gt;&lt;em&gt;Permission scopes&lt;/em&gt; are URL-type strings that represent the permissions your app requests from end-users. They're "URLs" not for HTTP communication but rather, are universal representations that get converted to end-user locale-compliant human-friendly strings.&lt;/p&gt;

&lt;p&gt;For example, the Google Drive metadata read-only scope is represented by &lt;code&gt;https://www.googleapis.com/auth/drive.metadata.readonly&lt;/code&gt;. In English-speaking countries, it's rendered to users as &lt;code&gt;See information about your Google Drive files&lt;/code&gt; in OAuth permission request dialogs (which you're probably already familiar with):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtz0gvm7mi71uwt3etd4.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdtz0gvm7mi71uwt3etd4.png" alt="OAuth2 authorization dialog"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] OAuth2 authorization dialog



&lt;p&gt;The text would be different (obviously) if your locale was &lt;em&gt;not&lt;/em&gt; English. &lt;a href="https://dev.to/googleworkspace/getting-started-using-google-apis-workspace-33-2me0#permission-scopes"&gt;More on scopes is found in the OAuth client ID (part 3) post&lt;/a&gt; if you're new to this concept. All scope "URLs" and their human-readable equivalents can be found in the &lt;a href="https://developers.google.com/identity/protocols/oauth2/scopes" rel="noopener noreferrer"&gt;full list of all Google API OAuth2 scopes&lt;/a&gt;. Google also has an &lt;a href="https://developers.google.com/identity/protocols/oauth2/policies" rel="noopener noreferrer"&gt;OAuth2 best practices &amp;amp; policies page&lt;/a&gt; which is worth reviewing as well.&lt;/p&gt;

&lt;h3&gt;
  
  
  Gmail API scopes
&lt;/h3&gt;

&lt;p&gt;A decade ago, there were only seven (7) scopes available for developers. That number has doubled as of the time of this publication. All are &lt;a href="https://developers.google.com/workspace/gmail/api/auth/scopes" rel="noopener noreferrer"&gt;listed in the API documentation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;In addition to &lt;em&gt;new&lt;/em&gt; scopes, Google has since introduced &lt;a href="https://developers.google.com/workspace/guides/configure-oauth-consent#scope_categories" rel="noopener noreferrer"&gt;&lt;em&gt;scope categories&lt;/em&gt;&lt;/a&gt;, indicating the sensitivity level user data accessed by the API. The tiers include (in order of least-to-most sensitive):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Non-sensitive&lt;/li&gt;
&lt;li&gt;Sensitive&lt;/li&gt;
&lt;li&gt;Restricted&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is a sampler of Gmail API scopes (in no particular order) and their sensitivity classifications:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://developers.google.com/workspace/gmail/api/auth/scopes" rel="noopener noreferrer"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fev39anmvaayrjm7irb6f.png" alt="Gmail API OAuth2 scopes"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] Gmail API OAuth2 scopes



&lt;p&gt;As you can expect, the more sensitive the scope, the more restrictive access must be for user safety &amp;amp; security. For developers, this translates to more scrutiny via &lt;a href="https://support.google.com/cloud/answer/13463073" rel="noopener noreferrer"&gt;app verification&lt;/a&gt; should you decide to launch a public app.&lt;/p&gt;

&lt;h2&gt;
  
  
  Updated code samples (from original)
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Current auth library (recommended; Python 2/3)&lt;/li&gt;
&lt;li&gt;Old auth library (most similar to original; Python 2/3)&lt;/li&gt;
&lt;li&gt;Modern Python 3-only (async, type annotations, &lt;code&gt;f&lt;/code&gt;-strings)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Node&lt;/strong&gt;&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Modern JavaScript ECMAscript module (&lt;code&gt;.mjs&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;CommonJS script (&lt;code&gt;.js&lt;/code&gt;)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This table lists and describes each of the samples in the &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail" rel="noopener noreferrer"&gt;repo&lt;/a&gt; along with configuration files for this post.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Sample&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Python&lt;/strong&gt; &lt;em&gt;(current auth libs)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python 2 &amp;amp; 3 combo version using current auth libs (&lt;code&gt;google.auth&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads-3async.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads-3async.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Modern Python 3-only (async, annotated) version using current auth libs (&lt;code&gt;google.auth&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/requirements.txt" rel="noopener noreferrer"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages requirements with current auth libs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;Python&lt;/strong&gt; &lt;em&gt;(old auth libs)&lt;/em&gt;
&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads-old.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Python 2 &amp;amp; 3 combo version using old auth libs (&lt;code&gt;oauth2client&lt;/code&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/requirements-old.txt" rel="noopener noreferrer"&gt;&lt;code&gt;requirements-old.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages requirements with old auth libs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Node&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/gmail_chatty_threads.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;ECMAScript module&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/gmail_chatty_threads.js" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;CommonJS script&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Configuration
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Python &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; file specifies the 3rd-party packages required to run the Python scripts using the current auth library, &lt;code&gt;gmail_chatty_threads.py&lt;/code&gt; and &lt;code&gt;gmail_chatty_threads-3async.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-api-python-client
google-auth-httplib2
google-auth-oauthlib
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;google-api-python-client&lt;/code&gt; is the all-purpose &lt;a href="https://developers.google.com/api-client-library/python" rel="noopener noreferrer"&gt;Google APIs client library for Python&lt;/a&gt;. The &lt;code&gt;google-auth-httplib2&lt;/code&gt; and &lt;code&gt;google-auth-oauthlib&lt;/code&gt; packages are the new/current auth libraries for transport (HTTP) and OAuth2, respectively.&lt;/p&gt;

&lt;h4&gt;
  
  
  Python &lt;code&gt;requirements-old.txt&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/requirements-old.txt" rel="noopener noreferrer"&gt;&lt;code&gt;requirements-old.txt&lt;/code&gt;&lt;/a&gt; file is similar to &lt;code&gt;requirements.txt&lt;/code&gt; except it contains 3rd-party packages for the previous auth library and is for the "old" version of the script, &lt;code&gt;gmail_chatty_threads-old.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-api-python-client
oauth2client
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;google-api-python-client&lt;/code&gt; package is the same Google APIs Python client library as above, while &lt;code&gt;oauth2client&lt;/code&gt; is the older, deprecated auth library. Keep an eye out for the sidebar on &lt;code&gt;oauth2client&lt;/code&gt; coming up soon.&lt;/p&gt;

&lt;h4&gt;
  
  
  Node &lt;code&gt;package.json&lt;/code&gt;
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt; file is the equivalent for Node... 3rd-party packages required to run both scripts, &lt;code&gt;gmail_chatty_threads.mjs&lt;/code&gt; and &lt;code&gt;gmail_chatty_threads.js&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google-cloud/local-auth&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^3.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^144.0.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;@google-cloud/local-auth&lt;/code&gt; package is the current auth library for Node while &lt;code&gt;googleapis&lt;/code&gt; is the &lt;a href="https://github.com/google/google-api-nodejs-client" rel="noopener noreferrer"&gt;Google APIs client library for Node&lt;/a&gt;. There's also &lt;a href="https://developers.google.com/api-client-library/javascript" rel="noopener noreferrer"&gt;one for client-side/front-end Javascript&lt;/a&gt; if you need it, but that isn't part of today's coverage. The version numbers listed are the latest at the time of publication (and subject to change in the repo).&lt;/p&gt;

&lt;h3&gt;
  
  
  Application source code
&lt;/h3&gt;

&lt;h4&gt;
  
  
  Python (current auth)
&lt;/h4&gt;

&lt;p&gt;The main Python version is &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.py&lt;/code&gt;&lt;/a&gt;, modernized from the original yet still Python 2 backwards-compatible. Taking it one chunk at a time:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.auth.transport.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_auth_oauthlib.flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.oauth2.credentials&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Python Standard Library imports are the 3.x &lt;code&gt;print()&lt;/code&gt;-function for 2.x and the &lt;code&gt;os&lt;/code&gt; module because we (the developers) need to manage the OAuth tokens ourselves. (Neither token management code nor this import are necessary when using the old auth library.) The remaining imports are the various Google client libraries needed for OAuth2 and API access.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 &lt;strong&gt;Python 2 and 3 supported&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Most of the world is on Python 3 today, but there are still some with dependencies on 2.x that make migration challenging. This is why I aim to create Python 2-3 compatible samples, to help those continuing to migrate. There's also a modern Python 3-only sample with newer features like &lt;code&gt;async/await&lt;/code&gt;, type annotations, &lt;code&gt;f&lt;/code&gt;-strings, etc. for those who don't care about 2.x support.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/gmail.metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# where to store access &amp;amp; refresh tokens
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_authorized_user_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_client_secrets_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_local_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;GMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gmail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The security code checks credentials from the locally-stored OAuth2 &lt;code&gt;TOKENS&lt;/code&gt; file. If it exists, read the OAuth tokens from it. If they exist but are invalid, use the &lt;em&gt;refresh token&lt;/em&gt; to get a new &lt;em&gt;access token&lt;/em&gt;, necessary for API access.&lt;/p&gt;

&lt;p&gt;If non-existent, build the flow from the client ID &amp;amp; secret in &lt;code&gt;client_secret.json&lt;/code&gt; and the requested permission &lt;code&gt;SCOPES&lt;/code&gt; (the Gmail metadata scope for this sample), and run the flow, resulting in the earlier OAuth2 dialog prompting the user to grant your app permission to access their Gmail (meta)data.&lt;/p&gt;

&lt;h3&gt;
  
  
  Rest of Application
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;threads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;tdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nmsgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nmsgs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payload&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-% 3d msgs: %s&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nmsgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
                    &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;%s...&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;[:&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;],))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The core part of the application scans email threads in your inbox (per &lt;code&gt;userId='me'&lt;/code&gt;) and individual messages in each thread. The first API call gets the latest 100 (default) threads. Don't like the default? Provide a &lt;code&gt;maxResults&lt;/code&gt; parameter (between 1-500) to &lt;code&gt;GMAIL.users().threads().list()&lt;/code&gt; to scan a different number of threads.&lt;/p&gt;

&lt;p&gt;For each thread, make an API call to get its messages, dropping it if there aren't more than 2 messages. Once you have a thread with at least 3 messages, scan its headers looking for a Subject line and save the value. Also drop the thread if the Subject line is blank.&lt;/p&gt;

&lt;p&gt;For all threads with at least 3 messages and a non-empty Subject line, display the number of threads and its Subject line, truncating the latter after 42 characters (and add 3 ellipses) if more than 45 characters long.&lt;/p&gt;

&lt;h4&gt;
  
  
  Python (old auth)
&lt;/h4&gt;

&lt;p&gt;This version is the closest analog to the sample in the &lt;a href="http://goo.gl/OfCbOz" rel="noopener noreferrer"&gt;original post&lt;/a&gt;? The only changes are the &lt;code&gt;import&lt;/code&gt;s, the security code (including the name change from &lt;code&gt;apiclient&lt;/code&gt; to &lt;code&gt;googleapiclient&lt;/code&gt;), and use of the new Gmail metadata scope (vs. the read-only scope). Below is old auth security snippet found in &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads-old.py&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Http&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;oauth2client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;

&lt;span class="c1"&gt;# check credentials from locally-stored OAuth2 tokens file; either
# refresh expired tokens or run flow to get new pair &amp;amp; create API client
&lt;/span&gt;&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/gmail.metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flow_from_clientsecrets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gmail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the script is identical; this one is also Python 2-3 compatible. I recommend using this version only if you have other code relying on the older auth library, otherwise it's always best to use the latest stuff.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;💥 Caveat: &lt;code&gt;oauth2client&lt;/code&gt; deprecated&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The older Python auth library, &lt;code&gt;oauth2client&lt;/code&gt;, was &lt;a href="https://github.com/googleapis/oauth2client/pull/714" rel="noopener noreferrer"&gt;deprecated in 2017&lt;/a&gt;. However the current library &lt;a href="https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html#replacement" rel="noopener noreferrer"&gt;does not support&lt;/a&gt; OAuth token storage, hence why &lt;code&gt;*-old.py&lt;/code&gt; samples like the above are shorter than their modern equivalents. For now, &lt;code&gt;oauth2client&lt;/code&gt; still works, even in maintenance mode, and provides threadsafe and 2.x/3.x-compatible storage of and access to OAuth2 tokens. &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;This post&lt;/a&gt; sheds more light on this change. Google won't provide migration guides showing "before &amp;amp; after," so their "dirty little secret" is one of the reasons why I'm here. It helps both developers and vibecoding LLMs understand this transition so all can produce modern code or migrate/fix old library code.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Around the time of the original post, I produced a video recap of this (version of the) app and motivation behind it; embedding it here in case this context can be useful. (&lt;strong&gt;NOTE:&lt;/strong&gt; There's a special bonus US history lesson embedded at the end... hope you learn something &lt;em&gt;non-technical&lt;/em&gt; from me too!)&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/L6hQCgxgzLI"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h4&gt;
  
  
  Python 3-only
&lt;/h4&gt;

&lt;p&gt;The last version is for those with "no baggage," meaning no dependencies on Python 2 or older auth libraries. It's the modern Python 3 version. Because of the (numerous) type annotations, this is the entire &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/python/gmail_chatty_threads-3async.py" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads-3async.py&lt;/code&gt;&lt;/a&gt; script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;typing&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.auth.transport.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_auth_oauthlib.flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.oauth2.credentials&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;

&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Optional&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/gmail.metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# where to store access &amp;amp; refresh tokens
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_authorized_user_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstAppFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_client_secrets_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_local_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gmail&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;proc_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;process msgs for a thread&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;id&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="nb"&gt;format&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;metadata&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;nmsgs&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;int&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;nmsgs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;messages&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;][&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;payload&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
        &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;
        &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;headers&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]:&lt;/span&gt;
            &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Subject&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                &lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;header&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;value&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
                &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;-&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;nmsgs&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="n"&gt;d&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; msgs: &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
                  &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="mi"&gt;45&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;%s...&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;List&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;Dict&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;Any&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;users&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;userId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;me&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;threads&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
    &lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;Set&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create_task&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;proc_thread&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;thread&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gather&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;tasks&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  JavaScript/Node.js
&lt;/h4&gt;

&lt;p&gt;There are 2 versions of the Node version of the script, &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/gmail_chatty_threads.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.mjs&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wescpy/google/blob/main/apps/gmail/nodejs/gmail_chatty_threads.js" rel="noopener noreferrer"&gt;&lt;code&gt;gmail_chatty_threads.js&lt;/code&gt;&lt;/a&gt;. The only difference between them are the how the 3rd-party packages are "brought into" each script:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/local-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first 4 &lt;code&gt;import&lt;/code&gt;s are for OAuth authorization while the last is for the API client library. You can switch the app to a CommonJS script by swapping out the &lt;code&gt;import&lt;/code&gt;s for the following &lt;code&gt;require()&lt;/code&gt; calls:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/local-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The rest of the application is identical for both versions. (Readers can file a PR if you convert either one [or both] to Typescript.)&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_secret.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/gmail.metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadSavedCredentialsIfExist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorized_user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token_expiry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadSavedCredentialsIfExist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyfilePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The security block of code is slightly longer than for Python as it's broken up into multiple &lt;code&gt;async&lt;/code&gt; functions. The core function is &lt;code&gt;authorize()&lt;/code&gt;, so start with that as it calls all the others as-necessary to check the saved OAuth tokens for validity, refreshing if possible but creating a new flow and getting new credentials if required. Regardless of how valid credentials are obtained, they're returned by &lt;code&gt;authorize()&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;gmailThreads&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GMAIL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;gmail&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authClient&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;===&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GMAIL&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;users&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;threads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;me&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;thread&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;format&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;metadata&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;tdata&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;res&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;nmsgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nmsgs&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;tdata&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;messages&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;msg&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;headers&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Subject&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
          &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;header&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
          &lt;span class="k"&gt;break&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
      &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`-&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nc"&gt;String&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;nmsgs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;padStart&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;3&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt; &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt; msgs: `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
          &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;&amp;lt;&lt;/span&gt; &lt;span class="mi"&gt;45&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="nx"&gt;subject&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;subject&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;42&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;+&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;...&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;gmailThreads&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The main driver gets valid credentials from a call to &lt;code&gt;authorize()&lt;/code&gt; then passes them onto &lt;code&gt;gmailThreads()&lt;/code&gt;. Like the Python version, it fetches the first 100 threads from the user's Gmail inbox then looks for threads with more than 2 messages, looping through those and saving non-empty Subject lines, truncated to 42 characters (plus ellipses) if more than 45 characters long, and displays them to the user.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ ALERT: &lt;strong&gt;Cost: "free" up to certain limits&lt;/strong&gt;&lt;br&gt;
While many Google products &amp;amp; APIs are free to use, not all of them are. While not totally "free," use of GWS APIs is covered by your monthly "subscription," whether you're a &lt;a href="https://gsuite.google.com/pricing" rel="noopener noreferrer"&gt;paid subscriber&lt;/a&gt; or have a free consumer Google account (&lt;a href="https://accounts.google.com/SignUp" rel="noopener noreferrer"&gt;with&lt;/a&gt; or &lt;a href="https://accounts.google.com/SignUpWithoutGmail" rel="noopener noreferrer"&gt;without&lt;/a&gt; Gmail, which is &lt;a href="https://support.google.com/accounts/answer/27441#existingemail" rel="noopener noreferrer"&gt;optional&lt;/a&gt;), meaning a $0USD monthly subscription rate.&lt;/p&gt;

&lt;p&gt;This "free" usage is not unlimited however... stay within the established quotas for each API. As expected, paid subscribers get more quota than free accounts. While not broadly published, you can get an idea of the limits on the &lt;a href="https://developers.google.com/apps-script/guides/services/quotas" rel="noopener noreferrer"&gt;Quotas page for Google Apps Script&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Prerequisites/required setup
&lt;/h2&gt;

&lt;p&gt;Now that you know what the code does, it's time to run it yourself. To do so, perform the required setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Create a new project or reuse an existing one

&lt;ul&gt;
&lt;li&gt;Enable the Gmail API&lt;/li&gt;
&lt;li&gt;Create OAuth client ID &amp;amp; secret credentials&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Install the Google APIs client library&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;Get your project setup. Once that's done, enable the API and create the credentials. You can do it in either order (but can't do either if you don't have a project). Installing the client library can be done before or after specifying a project. Per the sidebar above, use of the Gmail API is "free."&lt;/p&gt;

&lt;p&gt;Below are more specific instructions. Some have alternatives, say command-line vs. console, so choose what you're most comfortable with.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;System requirements&lt;/em&gt;&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;For Python 2 specifically, that means 2.7 only.&lt;/li&gt;
&lt;li&gt;For Python 3, I strongly suggest 3.9 or newer.&lt;/li&gt;
&lt;li&gt;For Node.js, I suggest 16 or newer.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Create a new project&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://console.cloud.google.com/projectcreate" rel="noopener noreferrer"&gt;from the Cloud/developer console&lt;/a&gt; or with &lt;code&gt;gcloud projects create . . .&lt;/code&gt;; or reuse an existing project&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable the Gmail API&lt;/em&gt;&lt;/strong&gt;. Pick your preferred method of these three common ways to enable APIs:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevConsole manually&lt;/strong&gt; -- Enable the API manually from the DevConsole by following these steps:

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="http://console.developers.google.com" rel="noopener noreferrer"&gt;DevConsole&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Library&lt;/strong&gt; tab in the left-nav; search for "Gmail", and enable&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;DevConsole link&lt;/strong&gt; -- You may be new to Google APIs or don't have experience enabling APIs manually in the DevConsole. If this is you...

&lt;ol&gt;
&lt;li&gt;Check out the &lt;a href="https://console.cloud.google.com/apis/library/gmail.googleapis.com" rel="noopener noreferrer"&gt;API listing page&lt;/a&gt; to learn more about the API and enable it from there.&lt;/li&gt;
&lt;li&gt;Alternatively, skip the API info and click &lt;a href="http://console.developers.google.com/start/api?id=gmail" rel="noopener noreferrer"&gt;this link&lt;/a&gt; for the enable button.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Command-line&lt;/strong&gt; (&lt;code&gt;gcloud&lt;/code&gt;) -- Those who prefer working in a terminal can enable APIs with a single command in the &lt;a href="https://cloud.google.com/shell" rel="noopener noreferrer"&gt;Cloud Shell&lt;/a&gt; or locally on your computer if you &lt;a href="https://cloud.google.com/sdk/install" rel="noopener noreferrer"&gt;installed the Cloud SDK&lt;/a&gt; which includes the &lt;code&gt;gcloud&lt;/code&gt; command-line tool (CLI) and initialized its use.

&lt;ol&gt;
&lt;li&gt;If this is you, issue this command to enable the API: &lt;code&gt;gcloud services enable gmail.googleapis.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirm all the APIs you've enabled with this command: &lt;code&gt;gcloud services list&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;em&gt;Create OAuth client ID &amp;amp; secret &lt;a href="https://console.cloud.google.com/apis/credentials" rel="noopener noreferrer"&gt;credentials&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; and save the file to your local filesystem as &lt;code&gt;client_secret.json&lt;/code&gt;. The code samples &lt;strong&gt;will not run&lt;/strong&gt; without this file present.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;em&gt;Install the Google APIs client library&lt;/em&gt;&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Node&lt;/strong&gt;:  Install required packages with:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;npm i&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python 2 or 3 (&lt;em&gt;new auth&lt;/em&gt;):&lt;/strong&gt; In your normal or &lt;code&gt;virtualenv&lt;/code&gt; environment, install the current/new Python auth libraries (most everyone):

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install -Ur requirements.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt; (if you use &lt;code&gt;uv&lt;/code&gt; in a virtualenv)&lt;/li&gt;
&lt;li&gt;Manually install packages by name (see &lt;code&gt;requirements.txt&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python 2 or 3 (&lt;em&gt;old auth&lt;/em&gt;):&lt;/strong&gt; If you have dependencies on the older Python auth libraries and/or still have old code lying around that do (see warning sidebar below), run this instead:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install -Ur requirements-old.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;uv pip install -Ur requirements-old.txt&lt;/code&gt; (if you use &lt;code&gt;uv&lt;/code&gt; in a virtualenv)&lt;/li&gt;
&lt;li&gt;Manually install packages by name (see &lt;code&gt;requirements-old.txt&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;h2&gt;
  
  
  Running the script
&lt;/h2&gt;

&lt;p&gt;The first time you run the script, you have to complete the OAuth authorization flow and give the code permission to access your Gmail metadata. If you're new to this process, see &lt;a href="https://dev.to/googleworkspace/getting-started-using-google-apis-workspace-33-2me0#running-the-script"&gt;this section of the Drive API intro post&lt;/a&gt; because the instructions and user experience are nearly identical.&lt;/p&gt;

&lt;p&gt;When you've given your authorization, you should see the most chatty threads in your inbox. For my Gmail mailing list account, I got the following results from one the Python versions -- all work the same and produce similar output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 gmail_chatty_threads.py
-  9 msgs: Re: How to manipulate PDF documents in Deb...
- 13 msgs: Re: please delete me from your mailing lists
-  6 msgs: Re: Bookworm libc6 (and libc6:i386) update...
- 28 msgs: Re: Is there a POSIX compliant way of turn...
- 10 msgs: Re: problem installing trixie - no EFI
- 22 msgs: Re: Where does pure-ftpd store files when ...
-  9 msgs: Re: Why are bug comment numbers multiples ...
- 34 msgs: SDD partitioning and allocations
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So out of 100 threads, there were only 8 with more than 2 messages, meaning the other 92 didn't have more than a single reply, if any. Congrats, you now know the basics of using the Gmail API and can continue exploring other features of the API, or build something specific for your organization or your customers, including morphing your solution into an MCP server for agentic apps.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;This post introduces developers to the Gmail API, demonstrating code that queries for and displays the "chatty email threads" in the user's inbox. Several versions are available in both Python &amp;amp; Node along with a description of how the code works. These sample apps were based on the original app published in the &lt;a href="http://goo.gl/OfCbOz" rel="noopener noreferrer"&gt;original blog post&lt;/a&gt; from a decade ago.&lt;/p&gt;

&lt;p&gt;All of the code was hand-written by me with the exception of the modern Python 3-only version. I copied the "current auth" version and changed it to use &lt;code&gt;async/await&lt;/code&gt; then added &lt;code&gt;f&lt;/code&gt;-string support. Finally, I prompted the Cursor AI IDE (integrated development environment) to: "Take the code for &lt;code&gt;gmail_chatty_threads-3async.py&lt;/code&gt; and add full type annotations to it," resulting in the version in the repo. I could've done it on my own, but that's tedious, and I'm impatient. &lt;code&gt;:-)&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;If you found an error in this post, a bug in &lt;a href="https://github.com/wescpy/google/tree/main/apps/gmail" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic I should cover, drop a note in the comments below or file an issue at the repo. I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 Service account alternative in API docs&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Around the time of the original post &amp;amp; video, I created an alternative Python version of the app using &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service account auth&lt;/a&gt; which was added to the &lt;a href="https://developers.google.com/workspace/gmail/api/guides/threads" rel="noopener noreferrer"&gt;Threads page&lt;/a&gt; of the documentation. While OAuth client IDs are standard for user permission, service accounts are useful for Workspace administrators performing tasks for multiple GWS domain users without needing to request individual user permission to perform actions on their behalf; this is known as &lt;a href="https://support.google.com/a/answer/162106" rel="noopener noreferrer"&gt;domain-wide delegation&lt;/a&gt; ("DWD"). I'll cover the differences and conversion between user &amp;amp; service account auth in a future post. For now, that alternative sample in the docs (ported to the current auth library) suffices. Impatient? Check &lt;a href="http://goo.gle/3nPxmlc" rel="noopener noreferrer"&gt;this post&lt;/a&gt; and &lt;a href="https://github.com/wescpy/analyze_gsimg" rel="noopener noreferrer"&gt;code repo&lt;/a&gt; which has samples in all 4 combinations (and more): old auth vs. new auth and OAuth client IDs vs. service accounts credentials.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources related to this post which you may find useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/apps/gmail" rel="noopener noreferrer"&gt;From &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/apps" rel="noopener noreferrer"&gt;From &lt;em&gt;all&lt;/em&gt; GWS posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;From &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Related code samples &amp;amp; content
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Original (previous auth) version in &lt;a href="http://goo.gl/OfCbOz" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Current auth version using service accounts in &lt;a href="https://developers.google.com/workspace/gmail/api/guides/threads" rel="noopener noreferrer"&gt;API docs&lt;/a&gt; (&lt;a href="https://github.com/googleworkspace/python-samples/blob/main/gmail/snippet/thread/threads.py" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="http://youtu.be/L6hQCgxgzLI" rel="noopener noreferrer"&gt;YouTube video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gmail API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/gmail/api/auth/scopes" rel="noopener noreferrer"&gt;OAuth2 scopes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/guides/configure-oauth-consent#choose-scopes" rel="noopener noreferrer"&gt;OAuth2 scope categories&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail/api/quickstart/python" rel="noopener noreferrer"&gt;Python QuickStart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail/api/quickstart/js" rel="noopener noreferrer"&gt;Javascript (client-side/front-end)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/gmail/api/quickstart/nodejs" rel="noopener noreferrer"&gt;JS/Node.js (server-side/back-end)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/gmail/api/guides/threads" rel="noopener noreferrer"&gt;Email threads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/gmail/api/reference/rest/v1/users.threads#Thread" rel="noopener noreferrer"&gt;Thread objects&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://youtu.be/UhdiQmS3kDs" rel="noopener noreferrer"&gt;Original Gmail API launch video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GWS APIs &amp;amp; OAuth2 information
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;All GWS APIs &lt;a href="http://developers.google.com/gsuite" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/guides/auth-overview" rel="noopener noreferrer"&gt;GWS auth overview page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/guides/configure-oauth-consent" rel="noopener noreferrer"&gt;OAuth consent screen &amp;amp; scope info&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OAuth2 &lt;a href="https://developers.google.com/identity/protocols/OAuth2" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google APIs client libraries
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library/python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library/javascript" rel="noopener noreferrer"&gt;Javascript (client-side/front-end)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/google-api-nodejs-client" rel="noopener noreferrer"&gt;JS/Node.js (server-side/back-end)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GWS APIs specific use cases

&lt;ul&gt;
&lt;li&gt;Mail merge with the Google Docs API &lt;a href="http://goo.gle/2HZ8K6R" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exporting Google Docs as PDF &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Importing CSV files into Google Sheets &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs intro content &lt;small&gt;(featuring Drive API)&lt;/small&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;Codelab&lt;/a&gt; (hands-on tutorial)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://goo.gl/ZIgf8k" rel="noopener noreferrer"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs general

&lt;ul&gt;
&lt;li&gt;Using OAuth client IDs &amp;amp; GWS APIs &lt;a href="https://dev.to/wescpy/series/25403"&gt;3-part post series&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GWS/G Suite developer overview &lt;a href="http://t.co/XdKEWus0KI" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Accessing GWS/G Suite REST APIs &lt;a href="https://goo.gle/3ateIIQ" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Power your apps with Gmail, Drive, Docs, Sheets, Slides (G Suite/GWS comprehensive developer overview) &lt;a href="http://youtu.be/kkp0aNGlynw" rel="noopener noreferrer"&gt;video&lt;/a&gt; (LONG)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs video series

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/kFMUa6" rel="noopener noreferrer"&gt;Launchpad Online&lt;/a&gt; (GWS &amp;amp; &lt;em&gt;other&lt;/em&gt; Google APIs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/JpBQ40" rel="noopener noreferrer"&gt;GWS/G Suite Dev Show&lt;/a&gt; (only GWS APIs)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Google APIs general

&lt;ul&gt;
&lt;li&gt;Getting started with Google APIs &lt;a href="http://developers.googleblog.com/2014/11/launchpad-online-for-developers-getting.html" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python authorization boilerplate code review &lt;a href="http://goo.gl/KMfbeK" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I was a member of various GWS product teams ~2013-2018. While product information is as accurate as I can find or recall, the opinions are my own.&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>python</category>
      <category>node</category>
      <category>google</category>
    </item>
    <item>
      <title>Gemini 2.5 API Missing Manual: How to get started (or upgrade from Gemini 1.0/1.5)</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Wed, 16 Jul 2025 04:55:48 +0000</pubDate>
      <link>https://dev.to/gde/gemini-25-api-missing-manual-how-to-get-started-or-upgrade-from-gemini-1015-1el6</link>
      <guid>https://dev.to/gde/gemini-25-api-missing-manual-how-to-get-started-or-upgrade-from-gemini-1015-1el6</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;This "missing manual" shows you how to upgrade code from the "old" Gemini 1.0/1.5 days. It's also for those new to the API because it collates various "Hello World!" samples together into one post (&amp;amp; repo), regardless of what platform you use. That's right, Google makes the Gemini API available from &lt;strong&gt;two completely different places&lt;/strong&gt;! This post is both a &lt;em&gt;beginners' guide&lt;/em&gt; &lt;strong&gt;&lt;em&gt;and&lt;/em&gt;&lt;/strong&gt; a &lt;em&gt;migration guide&lt;/em&gt; you won't find in the documentation. Now Google did the right thing by unifying under a single client library for both platforms, and while it's better than two platforms &lt;em&gt;and&lt;/em&gt; two libraries and old samples living forever online, the worse thing is: vibecoding LLMs were trained on all that! This post aims to provide a solid understanding of how the old libraries worked &lt;em&gt;and&lt;/em&gt; how to use the current one. Most importantly, you'll have the knowledge to modernize old code, whether written by humans &lt;em&gt;or&lt;/em&gt; LLM-generated.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE (Aug 2025)&lt;/strong&gt;: This post does not cover &lt;a href="https://blog.google/intl/en-mena/product-updates/explore-get-answers/nano-banana-image-editing-in-gemini-just-got-a-major-upgrade" rel="noopener noreferrer"&gt;&lt;strong&gt;Nano Banana&lt;/strong&gt;, the Gemini 2.5 Flash Image model&lt;/a&gt;, which has advanced features not available in previous or other models. It can effectively "make changes" to existing images, blend artifacts from multiple images, and allow for continuing edits. Code samples and features coming in a future blog post. Developers can access the Gemini 2.5 Flash Image preview model today via &lt;code&gt;gemini-2.5-flash-image-preview&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" alt="Build with Gemini" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog covering Google APIs for Python (and sometimes Node.js) developers. You'll learn how to code a variety of APIs from different product families (see below), for new or existing applications (including MCP servers &amp;amp; agents):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP (&lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt; (Drive, Docs, Sheets, Gmail, etc.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Generative AI with &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt; (&lt;em&gt;this series&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Credentials (&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We return to Gemini again in this post, covering the updated client libraries coinciding with the 2.0 release.&lt;/p&gt;

&lt;h3&gt;
  
  
  Background
&lt;/h3&gt;

&lt;p&gt;Besides model updates, Google tweaked the way developers access the Gemini API since last year, all for the better I think. For long-time API users, we'll "modernize" old library code samples for 2.0/2.5, and for everyone else, we'll get you started using the API with the current library today!&lt;/p&gt;

&lt;p&gt;In the &lt;a href="https://dev.to/wescpy/a-better-google-gemini-api-hello-world-sample-4ddm"&gt;original post&lt;/a&gt;, I griped that Google makes the API accessible from two different platforms:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev" rel="noopener noreferrer"&gt;&lt;strong&gt;Google AI&lt;/strong&gt;&lt;/a&gt; ("GAI")&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;&lt;strong&gt;Vertex AI&lt;/strong&gt;&lt;/a&gt; (Google Cloud/"GCP")&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While &lt;em&gt;that&lt;/em&gt; hasn't changed, it's clear each has a different purpose:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Google AI&lt;/strong&gt;: Experimenting, free tier/lower cost, lower barrier-to-entry, hobbyists/students&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Vertex AI&lt;/strong&gt;: Production AI workloads, existing GCP customers adding/considering AI capabilities&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  What's changed?
&lt;/h3&gt;

&lt;p&gt;So why this post? A couple of things have changed since then:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Updated Gemini models&lt;/li&gt;
&lt;li&gt;Updated (old) and new API client libraries&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Let's start with the updated models, recognizing the elephant in the room is the 2nd one. I'll discuss both briefly then dive into a full migration guide featuring code for both GAI &lt;em&gt;and&lt;/em&gt; GCP using the old/discrete client libraries and also the current/unified client libraries.&lt;/p&gt;

&lt;h4&gt;
  
  
  Updated Gemini models
&lt;/h4&gt;

&lt;p&gt;All models undergo periodic updates, and Gemini is no exception: 1.0 was released at the end of 2023. A few months later, we got 1.5; 2.0 came out just before the end of 2024, and earlier this year, we got the reasoning 2.5 models. Things change quickly in the world of AI, and the API needs to keep up.&lt;/p&gt;

&lt;p&gt;I updated the original post (simple text queries) samples from 1.0 to 1.5 without drama, then &lt;a href="https://dev.to/wescpy/gemini-api-102-next-steps-beyond-hello-world-1pb7"&gt;covered other use cases&lt;/a&gt; like multimodal support, chat (multi-turn conversations), etc. Basic use cases like this only require a change of model name in the samples, so you're good-to-go even when models are updated.&lt;/p&gt;

&lt;p&gt;That let me focus on important &lt;em&gt;new&lt;/em&gt; features of the 2.0 models, like generating &lt;a href="https://dev.to/wescpy/generate-audio-clips-with-gemini-20-flash-from-google-n0g"&gt;audio&lt;/a&gt; and &lt;a href="https://dev.to/wescpy/generating-images-with-gemini-20-flash-from-google-448e"&gt;images&lt;/a&gt;. The 2.0 release coincided with the new consolidated client library. While &lt;em&gt;that&lt;/em&gt; was great, I knew I had to discuss the old libraries and migration at some point, so I "kicked the can down the road," so here we are.&lt;/p&gt;

&lt;h4&gt;
  
  
  Updated (old) and current/new API client libraries
&lt;/h4&gt;

&lt;h5&gt;
  
  
  The good news: unified client library
&lt;/h5&gt;

&lt;p&gt;It's confusing when an API is accessible from different platforms (from the same company no less), but that's where we find ourselves today: a pair of (different) client libraries, twice the number of the code samples, documentation, open source repos, the whole lot. This duality results in a less-than-optimal UX (user experience), especially for&lt;br&gt;
those who aren't aware it's available from two platforms and wondering why a web search or vibecoding requests result in code samples or documentation for one, then the other, all unpredictably if your web query or LLM-prompt isn't specific enough.&lt;/p&gt;



&lt;p&gt;Over time, Google realized maintaining separate libraries wasn't a good idea, requiring near-duplicate engineering effort. The solution is a &lt;strong&gt;single, unified client library&lt;/strong&gt; supporting both GAI &amp;amp; GCP so &lt;strong&gt;&lt;em&gt;all&lt;/em&gt;&lt;/strong&gt; users get a consistent experience. In the end, this is a &lt;em&gt;good&lt;/em&gt; thing, for code reuse &amp;amp; maintenance as well as allowing users to transition between platforms as needed, say when going from GAI for dev to GCP for production. There are still minor differences &lt;em&gt;using&lt;/em&gt; the unified library on both platforms as we'll soon see, but still, it's a great improvement. Thanks Google!&lt;/p&gt;
&lt;h5&gt;
  
  
  The bad news: users uninformed, old library samples linger, etc.
&lt;/h5&gt;

&lt;p&gt;The new library &lt;a href="https://github.com/googleapis/python-genai/commit/40eb42039074596bb39f77496fe58ed863f9c0a8" rel="noopener noreferrer"&gt;went public towards the end of 2024&lt;/a&gt; with the initial docs coming a month later. No public announcements were made for either however. Some words about the new library, deprecation of the old ones, migration guides, timeline, etc., would have been helpful. Users are redirected to the new library docs, but no notice about deprecating the old ones appeared until a &lt;a href="https://github.com/googleapis/python-aiplatform/commit/9b0beae22be2f7618618ef52971f0b3603ae3885" rel="noopener noreferrer"&gt;mid-2025 &lt;code&gt;README&lt;/code&gt; update&lt;/a&gt;. At least, there's &lt;em&gt;some&lt;/em&gt; closure, and those are some of the reasons why this post exists.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;More&lt;/em&gt; bad news: although Google removes old samples from their docs, &lt;em&gt;that&lt;/em&gt; has no effect on what people have written and posted online. Those will live forever. Even worse, those samples were and &lt;strong&gt;are&lt;/strong&gt; used to train the LLMs your favorite vibe-coding tool uses. Old sample code and poorly-trained LLMs worsen the UX and simply cause &lt;a href="https://discuss.ai.google.dev/t/confused-about-google-generative-ai-google-genai-and-all-hosted-repos/79022" rel="noopener noreferrer"&gt;user confusion&lt;/a&gt;. My other reason for this "public service announcement:" to future-train LLMs so they know how to produce modern code samples, and fix old samples written by humans or incorrectly-generated code by (other) LLMs.&lt;/p&gt;

&lt;p&gt;If this story of old vs. new libraries sounds familiar, it's essentially &lt;strong&gt;the exact same topic&lt;/strong&gt; as my &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;post from last month&lt;/a&gt;. The difference? This is &lt;em&gt;another&lt;/em&gt; Google product.&lt;/p&gt;
&lt;h5&gt;
  
  
  Migration guides? Inconsistent. Incomplete. Where?
&lt;/h5&gt;

&lt;p&gt;For &lt;em&gt;that&lt;/em&gt; (auth libraries) deprecation, users have no recourse as there's no migration guide at all (and why that post exists). For &lt;em&gt;this&lt;/em&gt; Gemini API library update, Google didn't announce one but produced &lt;strong&gt;two&lt;/strong&gt; migration guides, and neither are easy to find, so here are some convenience links:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/migrate" rel="noopener noreferrer"&gt;Google AI migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/deprecations/genai-vertexai-sdk" rel="noopener noreferrer"&gt;Vertex AI migration guide&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both guides have "before" and "after" code samples, which is very useful. Unfortunately they're inconsistent and incomplete. Examples (already filed bugs/feedback):&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;GAI 1:&lt;/strong&gt; in the &lt;a href="https://ai.google.dev/gemini-api/docs/migrate#client" rel="noopener noreferrer"&gt;"API access" section&lt;/a&gt; at the top, the "before" sample shows how to request a specific model (&lt;code&gt;'gemini-1.5-flash'&lt;/code&gt;), but where is that in the "after"?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GAI 2:&lt;/strong&gt; the &lt;a href="https://ai.google.dev/gemini-api/docs/migrate#python_4" rel="noopener noreferrer"&gt;Python "Authentication" section&lt;/a&gt; states: &lt;em&gt;The old SDK handled the API client object implicitly&lt;/em&gt;." It should add the you have to pass in the API key explicitly whereas in the current library, it's optional, and you can opt to use &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; instead. The &lt;a href="https://ai.google.dev/gemini-api/docs/migrate#python_6" rel="noopener noreferrer"&gt;Python "Generate content" section&lt;/a&gt; states: &lt;em&gt;Previously, there were no client objects, you accessed APIs directly through &lt;code&gt;GenerativeModel&lt;/code&gt; objects.&lt;/em&gt; So yes, you actually get a &lt;code&gt;genai.Client&lt;/code&gt; object with the new library and make calls with it, the &lt;code&gt;GenerativeModel&lt;/code&gt; objects in the old library serve the same purpose: allowing you to make API calls, meaning this shouldn't have been called out. To better "match" both samples, they should've passed in the API key using the new library and mention the environment variable option. Otherwise, it's not a &lt;em&gt;true&lt;/em&gt; before &amp;amp; after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP 1:&lt;/strong&gt; Only the &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/deprecations/genai-vertexai-sdk#grounding" rel="noopener noreferrer"&gt;Grounding section&lt;/a&gt; shows users how to create a client properly using the new library. I understand not repeating throughout, but if you only want to put it in one place, perhaps somewhere near the top after the "Installation" and not randomly most of the way down the page?&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP 2:&lt;/strong&gt; in the &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/deprecations/genai-vertexai-sdk#context-caching" rel="noopener noreferrer"&gt;Context caching&lt;/a&gt; section, &lt;strong&gt;Create&lt;/strong&gt; and &lt;strong&gt;Get&lt;/strong&gt; in "before" features calls to &lt;code&gt;vertexai.init()&lt;/code&gt; but none of the others. Perhaps list it once in the &lt;strong&gt;Imports&lt;/strong&gt; section or in &lt;em&gt;every section&lt;/em&gt;? (Why be so inconsistent?) That's one thing. Another thing: where are the equivalent "init" calls in the "after" sections? &lt;strong&gt;Create&lt;/strong&gt; has &lt;code&gt;client = genai.Client(http_options=HttpOptions(api_version="v1"))&lt;/code&gt; which is not the same as &lt;code&gt;client = Client(vertexai=True, project=GOOGLE_CLOUD_PROJECT, location=GOOGLE_CLOUD_LOCATION)&lt;/code&gt;, but &lt;em&gt;that's&lt;/em&gt; not in any other other sections either. I think what's currently there (with &lt;code&gt;http_options&lt;/code&gt;) is a mistake and should be the latter. The parameters are completely different.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Those are just the few I found. Having "before" and "after" examples are great, but the examples are inconsistent. They're also incomplete because some have more code than others; they're not giving everyone the whole picture. I'm not asking they be repetitive but provide enough context.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;NOTE: All new &lt;em&gt;human-generated&lt;/em&gt; code&lt;/strong&gt;&lt;br&gt;
&lt;strong&gt;&lt;em&gt;Human-generated&lt;/em&gt;&lt;/strong&gt;: All the code below is new and written personally by me... I have a need to be precise and consistent, so vibe-coding isn't a good use case here. In fact, this post is meant to future-train coding LLMs to not use old libraries and/or self-correct what they output if trained on old code.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;em&gt;Even the "old" code is new&lt;/em&gt;&lt;/strong&gt;: The "old" code below is &lt;em&gt;also&lt;/em&gt; newly-written in case any samples changed from the original post. After all, the old client libraries themselves could have been updated since that post. What you'll find in the "old" samples here is what your code would look like if you coded them today using the final release of the older libraries.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3&gt;
  
  
  Prerequisites
&lt;/h3&gt;

&lt;p&gt;I don't know where all you readers are coming from nor where you run (or want to run) your apps (GAI and/or GCP), so I'm going to provide &lt;em&gt;all&lt;/em&gt; the code and all the instructions. Regardless of which you use (or both), there are a pair of basic requisites:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Set auth credentials&lt;/li&gt;
&lt;li&gt;Install required packages&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;⚠️ Required credentials&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GAI&lt;/strong&gt;: &lt;a href="https://cloud.google.com/docs/authentication/api-keys-use" rel="noopener noreferrer"&gt;API key&lt;/a&gt; is required. Follow the instructions below. The GAI scripts will &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; run without an API key which should be either assigned to the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable or saved to a local file, &lt;code&gt;.env&lt;/code&gt; (Node.js) or &lt;code&gt;settings.py&lt;/code&gt; (Python).&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GCP&lt;/strong&gt;: &lt;a href="https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment" rel="noopener noreferrer"&gt;User auth via ADC&lt;/a&gt; for local dev environments is required to run the old client library code and recommended for current library code. The current library also supports API keys, but user auth/ADC is still recommended (more secure). (For production, use &lt;a href="https://cloud.google.com/docs/authentication#service-accounts" rel="noopener noreferrer"&gt;service accounts&lt;/a&gt;.)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;💰 Cost considerations&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GAI&lt;/strong&gt;: Google AI has a free tier; see its &lt;a href="https://ai.google.dev/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; for more information.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;strong&gt;GCP&lt;/strong&gt;: Vertex AI does &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; have a free tier, and an active billing account is required, so definitely check out its &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/pricing" rel="noopener noreferrer"&gt;pricing page&lt;/a&gt; to find out how much it'll cost to run the GCP scripts.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  Code samples table of contents
&lt;/h2&gt;

&lt;p&gt;Code is available in Python 3 &amp;amp; Node.js, the latter as both ECMAscript modules as well as CommonJS scripts, plus corresponding configuration files.&lt;/p&gt;
&lt;h3&gt;
  
  
  Script applications
&lt;/h3&gt;

&lt;p&gt;To compare like-scripts, the old client library version sits above its current client library equivalent.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Client library&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;ECMAscript&lt;/th&gt;
&lt;th&gt;CommonJS&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GAI&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GenerativeAI (&lt;em&gt;old&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-old.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-old.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-old.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-old.js" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-old.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GenAI (&lt;em&gt;current&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-cur.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-cur.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-cur.js" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-cur.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gai-cur.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-cur.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;GCP&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;td&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;VertexAI (&lt;em&gt;old&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-old.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-old.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-old.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-old.js" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-old.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;GenAI (&lt;em&gt;current&lt;/em&gt;)&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-cur.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-cur.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-cur.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-cur.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/gem25txt-simple-gcp-cur.js" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-cur.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h3&gt;
  
  
  Configuration files
&lt;/h3&gt;

&lt;p&gt;The config files include old &amp;amp; current client library packages and apply to both platforms.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Python&lt;/th&gt;
&lt;th&gt;Node.js&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Packages&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/requirements.txt" rel="noopener noreferrer"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Settings&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/settings_TMPL.py" rel="noopener noreferrer"&gt;&lt;code&gt;settings_TMPL.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25/.env_TMPL" rel="noopener noreferrer"&gt;&lt;code&gt;.env_TMPL&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
&lt;h2&gt;
  
  
  General instructions (GAI &amp;amp; GCP)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Ensure your Node (and NPM) installation is up-to-date (recommend 18+)&lt;/li&gt;
&lt;li&gt;Install all packages (old &amp;amp; new client libraries): &lt;code&gt;npm i&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Ensure your Python (and &lt;code&gt;pip&lt;/code&gt;) installation is up-to-date (3.9+ recommended)&lt;/li&gt;
&lt;li&gt;(optional) &lt;a href="https://packaging.python.org/en/latest/guides/installing-using-pip-and-virtual-environments/#create-and-use-virtual-environments" rel="noopener noreferrer"&gt;Create &amp;amp; activate a virtual environment ("virtualenv") for isolation&lt;/a&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python3 -m venv .myenv; source .myenv/bin/activate&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;For the commands below, depending on your system configuration, you will use one of (&lt;code&gt;pip&lt;/code&gt;, &lt;code&gt;pip3&lt;/code&gt;, &lt;code&gt;python3 -m pip&lt;/code&gt;), but the instructions are generalized to &lt;code&gt;pip&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;(optional) Update &lt;code&gt;pip&lt;/code&gt; and install &lt;code&gt;uv&lt;/code&gt;: &lt;code&gt;pip install -U pip uv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install all packages (old &amp;amp; new client libraries): &lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt; (drop &lt;code&gt;uv&lt;/code&gt; if you didn't install it)&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  GAI-specific instructions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set credentials:&lt;/strong&gt; &lt;a href="https://makersuite.google.com/app/apikey" rel="noopener noreferrer"&gt;Create API key&lt;/a&gt; (or reuse existing one).&lt;/li&gt;
&lt;li&gt;Either save the API key to &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable or copy the template for your language &lt;code&gt;.env_TMPL&lt;/code&gt; (Node) or &lt;code&gt;settings_TMPL.py&lt;/code&gt; (Python) file to &lt;code&gt;.env&lt;/code&gt; (Node) or &lt;code&gt;settings.py&lt;/code&gt; (Python) and assign the key to &lt;code&gt;API_KEY&lt;/code&gt;.

&lt;ul&gt;
&lt;li&gt;If using the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable, simplify the code to not look in &lt;code&gt;.env&lt;/code&gt; or &lt;code&gt;settings.py&lt;/code&gt; before running (use commented-out line). (This only affects scripts named &lt;code&gt;gem25txt-simple-gai-cur.*&lt;/code&gt;.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run any of the scripts, e.g., &lt;code&gt;node gem25txt-simple-gai-old.mjs&lt;/code&gt;, &lt;code&gt;python3 gem25txt-simple-gai-cur.py&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  GCP-specific instructions
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Set credentials:&lt;/strong&gt; &lt;a href="https://cloud.google.com/docs/authentication/set-up-adc-local-dev-environment" rel="noopener noreferrer"&gt;Login with user auth &amp;amp; set ADC for local dev environment&lt;/a&gt;.

&lt;ul&gt;
&lt;li&gt;The new client library supports API keys, so if you prefer this, then follow the GAI instructions above.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Copy the template for your language &lt;code&gt;.env_TMPL&lt;/code&gt; (Node) or &lt;code&gt;settings_TMPL.py&lt;/code&gt; (Python) file to &lt;code&gt;.env&lt;/code&gt; (Node) or &lt;code&gt;settings.py&lt;/code&gt; (Python), respectively, and set the values for &lt;code&gt;YOUR_GCP_PROJECT&lt;/code&gt; and &lt;code&gt;YOUR_GCP_REGION&lt;/code&gt; (more on both later).

&lt;ul&gt;
&lt;li&gt;If using an API key instead of ADC, assign it to &lt;code&gt;API_KEY&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; (Node) or &lt;code&gt;settings.py&lt;/code&gt; (Python), and update the code to use it before running (use commented-out line). (This is only supported by current library samples, e.g., scripts named &lt;code&gt;gem25txt-simple-gcp-cur.*&lt;/code&gt;.)&lt;/li&gt;
&lt;li&gt;As a GCP service, you should use &lt;a href="https://cloud.google.com/docs/authentication#service-accounts" rel="noopener noreferrer"&gt;service accounts&lt;/a&gt; in production. (I don't believe Google has documentation on how to do this, so I may have to do so in an upcoming post. Add a comment below linking to a page in the docs if you know of or find one.)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Run any of the scripts, e.g., &lt;code&gt;node gem25txt-simple-gcp-cur.js&lt;/code&gt;, &lt;code&gt;python3 gem25txt-simple-gcp-old.py&lt;/code&gt;, etc.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;
  
  
  Configuration files
&lt;/h2&gt;
&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;


&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;google-cloud-aiplatform         # GCP: old
google-generativeai             # GAI: old
google-genai                    # GAI &amp;amp; GCP: current
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;requirements.txt&lt;code&gt;&lt;/code&gt;&lt;/code&gt;




&lt;p&gt; &lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_API_KEY&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;GCP_METADATA&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;project&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_GCP_PROJECT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;YOUR_GCP_REGION&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;settings_TMPL.py&lt;code&gt;&lt;/code&gt;&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dependencies&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google-cloud/vertexai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^1.10.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^1.8.0&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google/generative-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^0.24.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;^17.0.1&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;package.json&lt;code&gt;&lt;/code&gt;&lt;/code&gt;





&lt;p&gt; &lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;API_KEY&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"YOUR_API_KEY"&lt;/span&gt;
&lt;span class="nv"&gt;GCP_METADATA&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'{
  "project": "YOUR_GCP_PROJECT",
  "location": "YOUR_GCP_REGION"
}'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;.env_TMPL&lt;code&gt;&lt;/code&gt;&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;For the 3rd-party package files &lt;code&gt;requirements.txt&lt;/code&gt; (Python) and &lt;code&gt;package.json&lt;/code&gt; (Node):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;google-generativeai&lt;/code&gt; -- old GAI client library&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google-cloud-aiplatform&lt;/code&gt; -- old GCP client library&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;google-genai&lt;/code&gt; -- current client library for both GAI &amp;amp; GCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The versions listed above for &lt;code&gt;package.json&lt;/code&gt; are the latest at the time of publication.&lt;/p&gt;

&lt;p&gt;For the metadata files, &lt;code&gt;settings.py&lt;/code&gt; (Python) and &lt;code&gt;.env&lt;/code&gt; (Node) (appended with &lt;code&gt;_TMPL&lt;/code&gt; for "template"):&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;API_KEY&lt;/code&gt; -- the API key string you created; required for GAI and optional for GCP; remove it if not applicable&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;GCP_METADATA&lt;/code&gt; -- only for GCP, so delete entire value if only using GAI&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;YOUR_GCP_PROJECT&lt;/code&gt; -- can be either the project ID or project number but &lt;strong&gt;not&lt;/strong&gt; the project name. See &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects#before_you_begin" rel="noopener noreferrer"&gt;this page in the docs&lt;/a&gt; if you're unfamiliar with these project identifiers. All three values are available on &lt;a href="https://console.cloud.google.com/iam-admin/settings" rel="noopener noreferrer"&gt;this Cloud console page&lt;/a&gt; or via this command: &lt;code&gt;gcloud projects describe PROJECT_ID_OR_NUMBER&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;YOUR_GCP_REGION&lt;/code&gt; -- choose either a &lt;a href="https://cloud.google.com/vertex-ai/docs/general/locations#available-regions" rel="noopener noreferrer"&gt;specific region&lt;/a&gt; or the &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/locations#global-endpoint" rel="noopener noreferrer"&gt;global endpoint&lt;/a&gt;, &lt;code&gt;global&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;For all variables, you obviously must have valid values required by each platform or those corresponding samples won't run.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Comparing code samples
&lt;/h2&gt;

&lt;p&gt;Okay, now that you have a lay of the land, let's compare code samples when migrating from the old client libraries to the current one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;h4&gt;
  
  
  GAI
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Old client library (&lt;code&gt;google-generativeai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;google.generativeai&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;

&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** GenAI text: %r model &amp;amp; prompt %r&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;configure&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gai-old.py&lt;code&gt;&lt;/code&gt;&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current client library (&lt;code&gt;google-genai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;

&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** GenAI text: %r model &amp;amp; prompt %r&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;#GENAI = genai.Client()  # if env var GEMINI_API_KEY set
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gai-cur.py&lt;code&gt;&lt;/code&gt;&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In the old version, &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gai-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-old.py&lt;/code&gt;&lt;/a&gt; imports the &lt;code&gt;google-generativeai&lt;/code&gt; client library which does setup with &lt;code&gt;configure()&lt;/code&gt; then creates a client by instantiating the &lt;code&gt;GenerativeModel&lt;/code&gt; class with the desired model (name). The current library is more flexible. As you can see in &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gai-cur.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-cur.py&lt;/code&gt;&lt;/a&gt;, a general &lt;code&gt;Client&lt;/code&gt; object is instantiated from the replacement GenAI client library. The model isn't passed in until an actual API call. While it sounds like a lot, a side-by-side peek shows the differences are minor:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy37nahk0bxp2nppf2zxg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fy37nahk0bxp2nppf2zxg.png" alt="Python GAI diffs" width="800" height="252"&gt;&lt;/a&gt;&lt;/p&gt;
Diffs between old &amp;amp; new GAI Python client libraries



&lt;h4&gt;
  
  
  GCP
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Old client library (&lt;code&gt;google-cloud-aiplatform&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;vertexai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;init&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GCP_METADATA&lt;/span&gt;

&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** GenAI text: %r model &amp;amp; prompt %r&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;GCP_METADATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;generative_models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerativeModel&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# print(response.text) works in Python but not Node.js
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gcp-old.py&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current client library (&lt;code&gt;google-genai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;GCP_METADATA&lt;/span&gt;  &lt;span class="c1"&gt;# API_KEY if using API key
&lt;/span&gt;
&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** GenAI text: %r model &amp;amp; prompt %r&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;

&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;vertexai&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;GCP_METADATA&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="c1"&gt;# GENAI = genai.Client(api_key=API_KEY)  # use API key from settings.py
# GENAI = genai.Client()  # use API key in GEMINI_API_KEY env var
&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gcp-cur.py&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The differences for the GCP versions are more stark: the old VertexAI client library version &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gcp-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-old.py&lt;/code&gt;&lt;/a&gt; has to be initialized with the GCP project &amp;amp; region along with a chosen model. The current/GenAI version &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gcp-cur.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-cur.py&lt;/code&gt;&lt;/a&gt; via the unified library is &lt;em&gt;identical&lt;/em&gt; to the GAI version if using API key auth. Otherwise, its client requires the GCP project &amp;amp; region, just like the old version plus a &lt;code&gt;vertexai&lt;/code&gt; flag set to &lt;code&gt;True&lt;/code&gt;. The GenAI client library only needs the model when calling the API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14i54a1ime2iplc2omf0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F14i54a1ime2iplc2omf0.png" alt="Python GCP diffs" width="800" height="236"&gt;&lt;/a&gt;&lt;/p&gt;
Diffs between old &amp;amp; new GCP Python client libraries



&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;h4&gt;
  
  
  GAI
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Old client library (&lt;code&gt;google-generativeai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenerativeAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/generative-ai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** GenAI text: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' model &amp;amp; prompt '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenerativeAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;model&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;text&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gai-old.mjs&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;import&lt;/code&gt;s with these &lt;code&gt;require()&lt;/code&gt; calls to convert it to CommonJS (all other lines remain identical to the ECMAscript module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenerativeAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google/generative-ai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;br&gt;
&lt;strong&gt;Current client library (&lt;code&gt;google-genai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** GenAI text: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' model &amp;amp; prompt '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// const GENAI = new GoogleGenAI({}); // use API key in GEMINI_API_KEY env var&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gai-cur.mjs&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;import&lt;/code&gt;s with these &lt;code&gt;require()&lt;/code&gt; calls to convert it to CommonJS (all other lines remain identical to the ECMAscript module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These diffs include a change in client library used/imported as well as the option of setting the API key in the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable instead of in &lt;code&gt;.env&lt;/code&gt; for the current version &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gai-old.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-old.mjs&lt;/code&gt;&lt;/a&gt; vs. the original &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gai-cur.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gai-cur.mjs&lt;/code&gt;&lt;/a&gt;. (The diffs are identical for the CommonJS versions save for the syntactical diffs between the &lt;code&gt;import&lt;/code&gt;s vs. the &lt;code&gt;require()&lt;/code&gt;s.)&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx68s8rme8j4smeoqoww.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fwx68s8rme8j4smeoqoww.png" alt="Node.js/ECMAscript GAI diffs" width="800" height="323"&gt;&lt;/a&gt;&lt;/p&gt;
Diffs between old &amp;amp; new GAI Node.js client libraries



&lt;h4&gt;
  
  
  GCP
&lt;/h4&gt;

&lt;p&gt;&lt;strong&gt;Old client library (&lt;code&gt;google-cloud-aiplatform&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;VertexAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;@google-cloud/vertexai&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** GenAI text: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' model &amp;amp; prompt '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GCP_METADATA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;VERTEXAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;VertexAI&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;VERTEXAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;getGenerativeModel&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gcp-old.mjs&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;import&lt;/code&gt;s with these &lt;code&gt;require()&lt;/code&gt; calls to convert it to CommonJS (all other lines remain identical to the ECMAscript module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;VertexAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/vertexai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current client library (&lt;code&gt;google-genai&lt;/code&gt;)&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.5-flash&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** GenAI text: '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' model &amp;amp; prompt '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;GCP_METADATA&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;vertexai&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="c1"&gt;// const GENAI = new GoogleGenAI({ apiKey: process.env.API_KEY });  // use API key from .env&lt;/span&gt;
&lt;span class="c1"&gt;// const GENAI = new GoogleGenAI({}); // use API key in GEMINI_API_KEY env var&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
&lt;code&gt;gem25txt-simple-gcp-cur.mjs&lt;/code&gt;





&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Replace the &lt;code&gt;import&lt;/code&gt;s with these &lt;code&gt;require()&lt;/code&gt; calls to convert it to CommonJS (all other lines remain identical to the ECMAscript module):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Similar diffs here: update client library from the old version &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gcp-old.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-old.mjs&lt;/code&gt;&lt;/a&gt; and the option of using &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; in the current version &lt;a href="https://github.com/wescpy/gemini/gem25/gem25txt-simple-gcp-cur.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem25txt-simple-gcp-cur.mjs&lt;/code&gt;&lt;/a&gt;. Both require the GCP project &amp;amp; region available via &lt;code&gt;CP_METADATA&lt;/code&gt; in &lt;code&gt;.env&lt;/code&gt; (then &lt;code&gt;CONFIG&lt;/code&gt;) when &lt;em&gt;not&lt;/em&gt; using an API key.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nnd2024hp13sabj9dw7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5nnd2024hp13sabj9dw7.png" alt="Node.js/ECMAscript GAI diffs" width="800" height="304"&gt;&lt;/a&gt;&lt;/p&gt;
Diffs between old &amp;amp; new GCP Node.js client libraries



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Wrap-up
&lt;/h2&gt;

&lt;p&gt;This is a beefy amount of content. Here's some of the good and bad news, and why this post exists.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; Google makes Gemini available via API.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; Google makes the Gemini API available from 2 different platforms, Google AI (GAI) and Vertex AI (GCP). As such, there are 2 different client libraries, docs, and code samples, confusing users.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; Google decides to unify to a single client library, giving users consistency to move across both platforms more easily.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; Google will remove all traces of the old library from their docs, doesn't announce anything to anyone other than pointing to the new library's docs.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; Google produces a migration guide to move users from the old libraries to current/new combined client library.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; There are &lt;strong&gt;two&lt;/strong&gt; migration guides, Google doesn't announce either, both are hard-to-find, and Google originally didn't announce any deprecate plans/dates.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; Google eventually announces a deprecation via a &lt;code&gt;README&lt;/code&gt; update in the repo.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Bad:&lt;/strong&gt; While old code is removed from Google's docs, other old samples live online forever and all old samples were used to train LLMs. Searching for Gemini API code may lead you to old code. Similarly, vibecoding LLMs may generate old code they were trained on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; This post has full before-and-after code samples for both GAI &amp;amp; GCP platforms and are available in both Python and Node.js (ECMAscript modules and CommonJS scripts). Hoping future-trained LLMs produce more concurrent code or can help automate migrating code using old libraries to the current one.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Good:&lt;/strong&gt; Google has provided (system) prompts you can use to guide your vibecoding LLMs to use the correct libraries, available in both &lt;a href="https://github.com/googleapis/python-genai/blob/main/codegen_instructions.md" rel="noopener noreferrer"&gt;Python&lt;/a&gt; and &lt;a href="https://github.com/googleapis/js-genai/blob/main/codegen_instructions.md" rel="noopener noreferrer"&gt;Node.js&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various links relevant to this post:&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem25" rel="noopener noreferrer"&gt;Sample in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt; (Python &amp;amp; Node.js)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/gemini" rel="noopener noreferrer"&gt;Code samples for Gemini posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini API (Google AI) general
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs" rel="noopener noreferrer"&gt;General GenAI docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/gemini_api_overview" rel="noopener noreferrer"&gt;API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/sdks" rel="noopener noreferrer"&gt;API SDKs/supported languages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/quickstart" rel="noopener noreferrer"&gt;API quickstart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/generative-ai-docs/blob/main/site/en/tutorials/quickstart_colab.ipynb" rel="noopener noreferrer"&gt;Jupyter Notebook QuickStart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing&lt;/a&gt; (free tier available)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/migrate" rel="noopener noreferrer"&gt;Google AI migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/migrate_to_cloud" rel="noopener noreferrer"&gt;GCP Vertex AI for Google AI users&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  API specific feature guides
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/image-generation" rel="noopener noreferrer"&gt;Image generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/live" rel="noopener noreferrer"&gt;Get started with Live API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/speech-generation#javascript" rel="noopener noreferrer"&gt;Speech generation (text-to-speech)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Cloud/GCP Vertex AI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;Vertex AI home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-5-flash" rel="noopener noreferrer"&gt;Gemini 2.5 Flash on Vertex AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/model-versions" rel="noopener noreferrer"&gt;Model versions &amp;amp; lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/gemini-experimental" rel="noopener noreferrer"&gt;All available Google models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/deprecations/genai-vertexai-sdk" rel="noopener noreferrer"&gt;Vertex AI migration guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/start/quickstarts/quickstart-multimodal" rel="noopener noreferrer"&gt;QuickStart page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GoogleCloudPlatform/python-docs-samples/tree/main/generative_ai" rel="noopener noreferrer"&gt;QuickStart code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/overview#whats_the_difference_from_google_ai_gemini_api" rel="noopener noreferrer"&gt;Google AI for GCP Vertex AI users&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini 2.0 &amp;amp; 2.5 models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025" rel="noopener noreferrer"&gt;2.5 launch announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models" rel="noopener noreferrer"&gt;Developer overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini/flash" rel="noopener noreferrer"&gt;Flash models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/cookbook" rel="noopener noreferrer"&gt;Cookbook repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Generative AI and Gemini resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini" rel="noopener noreferrer"&gt;Gemini home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://developers.googleblog.com/en/gemini-is-now-accessible-from-the-openai-library/" rel="noopener noreferrer"&gt;Gemini accessible from OpenAI Library&lt;/a&gt; (post)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://discuss.ai.google.dev/t/confused-about-google-generative-ai-google-genai-and-all-hosted-repos/79022" rel="noopener noreferrer"&gt;Confused about @google/generative-ai, @google/genai, and all hosted repos forum post&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini API post series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for &lt;a href="https://linuxjournal.com/article/5948" rel="noopener noreferrer"&gt;Linux Journal&lt;/a&gt; &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with Google integrations, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>node</category>
      <category>gemini</category>
      <category>ai</category>
    </item>
    <item>
      <title>OAuth client IDs dirty little secrets: old vs. new Python auth libraries</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 16 Jun 2025 02:57:52 +0000</pubDate>
      <link>https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7</link>
      <guid>https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;Looking for example code online or vibe-coding with codegen AI tools and discover samples vary wildly for the &lt;strong&gt;same&lt;/strong&gt; Google API? The dirty little secret is that Google has &lt;em&gt;two&lt;/em&gt; auth libraries for Python, an "OG" and its replacement, and snippets for both live forever online, causing developer &lt;em&gt;and&lt;/em&gt; LLM confusion. Google wants you to only use new/current stuff, so &lt;strong&gt;side-by-side examples in the docs using both old &amp;amp; new&lt;/strong&gt; (for compare/contrast or porting/migration) isn't happening. (No new library launch post nor migration guide either.) This issue affects APIs that don't have product client libraries, &lt;strong&gt;primarily Workspace/GWS APIs&lt;/strong&gt;, but also YouTube and some Cloud/GCP APIs. Keep reading to learn more....&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyyg1qd1sooo1dat9pj8.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnyyg1qd1sooo1dat9pj8.png" alt="Old and new auth" width="800" height="532"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] Old &amp;amp; new security mechanisms side-by-side (source: Gemini 2.0 Flash Experimental)



&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog for developers using Google APIs. Whether it's &lt;a href="https://dev.to/wescpy/series/23343"&gt;Workspace/GWS&lt;/a&gt;, Cloud/GCP (&lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;), &lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;, GenAI with &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;, or boilerplate like credentials (see below), there's something for everyone. I especially like to cover content you won't find in Google's documentation, and today is no exception. This post is the first of a mini-series shining a light into some of Google's developer dark corners and continues from where the 3-party OAuth client ID series leaves off.&lt;/p&gt;

&lt;p&gt;Numerous Google APIs require developers to implement auth security code while others provide higher-level libraries that abstract auth away from you. APIs that use the lower-level &lt;a href="https://developers.google.com/api-client-library" rel="noopener noreferrer"&gt;Google APIs client library&lt;/a&gt; for &lt;a href="https://developers.google.com/api-client-library/python" rel="noopener noreferrer"&gt;Python&lt;/a&gt; are the ones affected and what this post focuses on. These primarily include GWS APIs (e.g., Drive, Sheets, Gmail, etc) but also affect &lt;a href="https://developers.google.com/youtube" rel="noopener noreferrer"&gt;YouTube&lt;/a&gt;, other older APIs, and surprisingly, a subset of GCP APIs that &lt;strong&gt;do not&lt;/strong&gt; provide a &lt;a href="https://github.com/googleapis/google-cloud-python" rel="noopener noreferrer"&gt;Cloud client library&lt;/a&gt;. (That's right, not all of them do!)&lt;/p&gt;

&lt;p&gt;There &lt;em&gt;really&lt;/em&gt; aren't any "dirty secrets" in this post, however Google does not acknowledge nor mention the previous library which had been around the past dozen years. Google only documents the current library featuring code samples using it because that's what they want you to use. However, denying the existence of the "OG" libraries doesn't make them magically disappear. Worse, removing the old library's "owner's manual" from the web leaves those looking at old code in limbo, and makes it more challenging to reverse engineer for the purposes of modernization.&lt;/p&gt;

&lt;p&gt;Old code samples, especially those &lt;em&gt;not&lt;/em&gt; written by Google, live forever online, so this post is meant to "ground" &lt;strong&gt;you&lt;/strong&gt; and to future-finetune LLMs to produce consistent output and internalize the differences between both libraries, for both command-line interfaces (CLIs) as well as web apps. (CLIs are covered in this post; I'll do web apps in an upcoming post.)&lt;/p&gt;

&lt;p&gt;As discussed in other posts in this blog, there are three different types of user credentials when accessing Google APIs:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;Service accounts&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This change only applies to credentials used for authorized access, meaning OAuth client IDs and service accounts. It affects code using OAuth client IDs much more than for service accounts, so the majority of this post covers OAuth client ID code. Service accounts are addressed towards the end though.&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/googleapis/oauth2client" rel="noopener noreferrer"&gt;&lt;code&gt;oauth2client&lt;/code&gt;&lt;/a&gt; library was &lt;a href="https://github.com/googleapis/oauth2client/pull/714" rel="noopener noreferrer"&gt;deprecated in 2017&lt;/a&gt; in favor of replacements, &lt;a href="https://github.com/googleapis/google-auth-library-python" rel="noopener noreferrer"&gt;&lt;code&gt;google-auth&lt;/code&gt;&lt;/a&gt; (includes &lt;code&gt;google.auth&lt;/code&gt; and &lt;code&gt;google.oauth2&lt;/code&gt;) and &lt;a href="https://github.com/googleapis/google-auth-library-python-oauthlib" rel="noopener noreferrer"&gt;&lt;code&gt;google_auth_oauthlib&lt;/code&gt;&lt;/a&gt;. While there's no post or other public announcement on the deprecation, the &lt;code&gt;google-auth&lt;/code&gt; documentation &lt;a href="https://google-auth.readthedocs.io/en/latest/oauth2client-deprecation.html" rel="noopener noreferrer"&gt;cites some reasoning&lt;/a&gt; behind it.&lt;/p&gt;

&lt;p&gt;One of the biggest differences in code is that the new/current libraries do not (yet?) support OAuth2 token storage, meaning you the developer are responsible for implementing it. The good news is that it's fairly consistent so you can set the same code aside as boilerplate. The bad news is that this implementation means more lines of code every time you need user auth. At the time of this writing, &lt;code&gt;oauth2client&lt;/code&gt; still functions properly, even in maintenance mode, providing automated, threadsafe, and Python 2/3-compatible storage of and access to OAuth2 tokens for your apps. Okay, let's dive in.&lt;/p&gt;

&lt;h2&gt;
  
  
  Client library installation
&lt;/h2&gt;

&lt;p&gt;Regardless of whether you use the old or new auth libraries, the &lt;a href="https://github.com/googleapis/google-api-python-client" rel="noopener noreferrer"&gt;Google API client library for Python (&lt;code&gt;google-api-python-client&lt;/code&gt;)&lt;/a&gt; is required. Optionally, instead of using &lt;code&gt;pip&lt;/code&gt; directly to install the packages, you can use &lt;a href="https://github.com/astral-sh/uv" rel="noopener noreferrer"&gt;&lt;code&gt;uv&lt;/code&gt;&lt;/a&gt;, a much faster alternative. To do so, update &lt;code&gt;pip&lt;/code&gt; and install &lt;code&gt;uv&lt;/code&gt; with this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install -U pip uv&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt; or &lt;code&gt;python3 -m pip&lt;/code&gt; depending on your Python installation)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  OLD
&lt;/h3&gt;

&lt;p&gt;In your system or &lt;code&gt;virtualenv&lt;/code&gt; environment, update &lt;code&gt;pip&lt;/code&gt; and install the API client library and the OG auth libraries with this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip install -U pip google-api-python-client oauth2client&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If using &lt;code&gt;uv&lt;/code&gt;, use this instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uv pip install -U google-api-python-client oauth2client&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expect the typical install output.&lt;/p&gt;

&lt;p&gt;To confirm all required packages have been installed correctly, run this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python -c "import googleapiclient, httplib2, oauth2client"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No errors and no output means the installation was successful!&lt;/p&gt;

&lt;h3&gt;
  
  
  NEW
&lt;/h3&gt;

&lt;p&gt;In your system or &lt;code&gt;virtualenv&lt;/code&gt; environment, update &lt;code&gt;pip&lt;/code&gt; and install the API client library and the new/current auth libraries with this command:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;pip install -U pip google-api-python-client google-auth-httplib2 google-auth-oauthlib&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If using &lt;code&gt;uv&lt;/code&gt;, use this instead:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;uv pip install -U google-api-python-client google-auth-httplib2 google-auth-oauthlib&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Expect the typical install output.&lt;/p&gt;

&lt;p&gt;To confirm all required packages have been installed correctly, run this one:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;python -c "import googleapiclient, google.auth, google.oauth2, google_auth_oauthlib"&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;No errors and no output means the installation was successful!&lt;/p&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;The code discussed here is independent of any specific APIs thus can be used as boilerplate. It's also 2.x/3.x-compatible. I'll now show code, discuss differences, then provide entire examples.&lt;/p&gt;

&lt;p&gt;The shortest code samples using the boilerplate that make the most sense to demo are featured as part of the &lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;GWS APIs intro codelab&lt;/a&gt;, showcasing the &lt;a href="http://developers.google.com/drive" rel="noopener noreferrer"&gt;Drive API&lt;/a&gt;. All the Drive API documentation has switched to the current library, but I have both old and new examples in one of my repos, so let's look at the diffs between &lt;a href="https://github.com/wescpy/gsuite-apis-intro/blob/master/python/drive_list.py" rel="noopener noreferrer"&gt;&lt;code&gt;python/drive_list.py&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://github.com/wescpy/gsuite-apis-intro/blob/master/python/drive_list-new.py" rel="noopener noreferrer"&gt;&lt;code&gt;python/drive_list-new.py&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Imports
&lt;/h3&gt;

&lt;h4&gt;
  
  
  OLD
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Http&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;oauth2client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  NEW
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os.path&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.auth.transport.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.oauth2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_auth_oauthlib.flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python standard library &lt;code&gt;import&lt;/code&gt;s come first followed by 3rd-party package imports. In both examples, the 3.x &lt;code&gt;print()&lt;/code&gt; function is made available for compatibility.&lt;/p&gt;

&lt;p&gt;The remaining imports  block imports all the packages necessary to talk to Google APIs, including the client library (&lt;code&gt;googleapiclient&lt;/code&gt;) along with the security libraries, old or new. These imports &lt;strong&gt;&lt;em&gt;only target&lt;/em&gt;&lt;/strong&gt; CLIs -- web apps are covered in a future post.&lt;/p&gt;

&lt;p&gt;Regardless of whether your app is a CLI script or web app, the interface end-users see is the same: asking them for the permissions (the "authorization" or &lt;em&gt;authz&lt;/em&gt;) for your app to access their data:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7m2l5r1lfaq3nyf2o3u.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj7m2l5r1lfaq3nyf2o3u.png" alt="OAuth2 permissions authz prompt dialog" width="462" height="980"&gt;&lt;/a&gt;&lt;/p&gt;
OAuth2 permissions/scope flow end-user authz prompt dialog



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;As mentioned already, the old library features built-in OAuth token storage while the current does not, so &lt;code&gt;os.path&lt;/code&gt;' file utilities are needed if you want to implement storage. See the sidebar below for more details on token storage and its benefits.&lt;/p&gt;

&lt;h4&gt;
  
  
  📝 &lt;strong&gt;Implement OAuth token storage&lt;/strong&gt;
&lt;/h4&gt;

&lt;blockquote&gt;
&lt;p&gt;In short, old auth implements token storage while new/current auth does not. This means that if you want it, you have to implement it on your own. Why bother? The consequence of &lt;strong&gt;&lt;em&gt;not&lt;/em&gt;&lt;/strong&gt; implementing token storage is that your users will have to see that OAuth2 permission dialog above &lt;em&gt;every time they try to access an API&lt;/em&gt; with an expired &lt;em&gt;access token&lt;/em&gt;. (Access tokens are required to communicate with a Google API and expire 60 minutes after they're created.)&lt;/p&gt;

&lt;p&gt;The key benefit to implementing this storage is that when end-users run your app calling an API with an expired access token, the stored &lt;em&gt;refresh token&lt;/em&gt; is used to request a new (valid) access token from Google servers, all without troubling your users with yet-another permissions request. Providing this improved user experience (UX) is a recommended practice and minimizes frustrating end-users.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Security
&lt;/h3&gt;

&lt;p&gt;The greatest differences between using the old and current auth libraries takes place in the OAuth flow part of the boilerplate.&lt;/p&gt;

&lt;h4&gt;
  
  
  OLD
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/drive.metadata.readonly&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flow_from_clientsecrets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;DRIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drive&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This snippet...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sets permission(s) requested from user... in this case, just read-only scope for user's Drive metadata (&lt;code&gt;SCOPES&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Sets file used for OAuth token storage, instantiating a &lt;code&gt;oauth2client.file.Storage&lt;/code&gt; object (&lt;code&gt;store&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Attempts to retrieve any stored credentials (&lt;code&gt;creds&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Validates whether credentials exist &lt;em&gt;and&lt;/em&gt; are valid&lt;/li&gt;
&lt;li&gt;If credentials do not exist or exist but are invalid

&lt;ul&gt;
&lt;li&gt;Builds OAuth &lt;code&gt;flow&lt;/code&gt; from client ID &amp;amp; secret pair along with requested scopes&lt;/li&gt;
&lt;li&gt;Runs OAuth flow just built (familiar dialog shown earlier); library stores latest tokens (from Google)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Create Drive API v3 client passing in an Http() communication object (messages to be signed with valid credentials)&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  NEW
&lt;/h4&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/drive.metadata.readonly&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_authorized_user_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_client_secrets_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_local_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;DRIVE&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;drive&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v3&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Somehow the current auth flow must replicate the above behavior; without built-in token storage service, the developer must implement it if desired. This snippet...&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Sets permission(s) requested from user... in this case, just read-only scope for user's Drive metadata (&lt;code&gt;SCOPES&lt;/code&gt;; same as old auth)&lt;/li&gt;
&lt;li&gt;Sets file used for OAuth token storage (&lt;code&gt;storage.json&lt;/code&gt; in both cases; &lt;code&gt;TOKENS&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Defaults to no credentials (&lt;code&gt;creds&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Checks to see if token storage file exists; if so, attempts to retrieve any stored (access) token (&lt;code&gt;creds&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Validates whether credentials exist &lt;em&gt;and&lt;/em&gt; are valid.&lt;/li&gt;
&lt;li&gt;If credentials exist and are valid (inverse of old auth code)

&lt;ol&gt;
&lt;li&gt;If credentials have expired but refresh token available...

&lt;ul&gt;
&lt;li&gt;Requests new access token from Google&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Otherwise...

&lt;ul&gt;
&lt;li&gt;Builds OAuth &lt;code&gt;flow&lt;/code&gt; from client ID &amp;amp; secret pair along with requested scopes&lt;/li&gt;
&lt;li&gt;Runs OAuth flow just built (familiar dialog shown earlier)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;Regardless how updated tokens were retrieved (refreshed or new via flow), replace any existing storage file with new tokens&lt;/li&gt;

&lt;li&gt;Create Drive API v3 client passing invalid credentials (differs slightly from old auth)&lt;/li&gt;

&lt;/ol&gt;

&lt;h3&gt;
  
  
  Application
&lt;/h3&gt;

&lt;p&gt;Now you (and future LLMs trained on this post) understand the differences between using the old Python auth library as well as the current one. Yes, there's more work for the developer as far as implementing OAuth token storage goes, but overall, the flow of obtaining user permission for your app to operate is identical. Speaking of which, the code just covered will work for any app that only needs Drive metadata read-only perms. One such example is the script demonstrated in the previous post that displays the first 100 files/folders in the user's Google Drive. Here is the 3-line app unchanged from before:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;files&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;DRIVE&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;files&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;files&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;[])&lt;/span&gt;
&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;files&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;  &lt;span class="c1"&gt;# 4 fields returned: mimeType, kind, id, name
&lt;/span&gt;    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;name&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;mimeType&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Not mentioned&lt;/strong&gt; in the previous post is an explanation of why it displays the first &lt;em&gt;100&lt;/em&gt; files/folders (fewer if you don't have that many) in the requesting user's Drive. Simply put, the default is 100 if no &lt;code&gt;pageSize&lt;/code&gt; value is provided to the &lt;code&gt;files().list()&lt;/code&gt; method. (NOTE: For long-time developers, &lt;code&gt;pageSize&lt;/code&gt; was named &lt;code&gt;maxResults&lt;/code&gt; in Drive API v2).&lt;/p&gt;

&lt;p&gt;All other details are covered in the &lt;a href="https://dev.to/googleworkspace/getting-started-using-google-apis-workspace-33-2me0"&gt;previous post&lt;/a&gt; along with sample output, Node.js versions, and even the video I produced that walks through the code. The Node.js and full versions of old and new scripts can be found in the repo at &lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;https://github.com/wescpy/gsuite-apis-intro&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;While the code samples featured here use Drive, a GWS API, GWS APIs are the primary API family affected by the existence both libraries, but &lt;em&gt;it's not the only API family&lt;/em&gt;. You'll also come across this when using YouTube APIs, some (older or newer) GCP APIs, and other, older Google APIs. At some point, I'll do a follow-up to this post demonstrating code using other API families.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Command-line scripts vs. web apps vs. mobile backends&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Command-line scripts aren't going to be the most widely implemented type of application. It's more likely you want to add GWS API usage to a web or mobile app. The calls will differ, but the OAuth flow will be similar. Google has a specific page in their docs for &lt;a href="https://developers.google.com/identity/protocols/oauth2/web-server" rel="noopener noreferrer"&gt;managing the OAuth flow process for web apps&lt;/a&gt; as well as &lt;a href="https://developers.google.com/identity/protocols/oauth2/native-app" rel="noopener noreferrer"&gt;one for mobile apps&lt;/a&gt;. Not to be lazy, but I'm &lt;em&gt;very&lt;/em&gt; likely to do a follow-up post to this one that focuses on the web app version(s).&lt;/p&gt;

&lt;p&gt;To make these calls as part of a mobile backend, yet another common use case, developers can use of service accounts, because there is no end-users to ask for permissions, or, they &lt;em&gt;can&lt;/em&gt; do that but cause the mobile app to launch a web page so the user can provide perms, then close the temporary mobile browser tab and resume operation back in the mobile app.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Now you (and future LLMs via training from this post) know why code online showing up to use OAuth client ID credentials vary so much, and why you don't get an explanation from Google: they expect all old samples to disappear from the Internet and for everyone to follow what's in the current documentation. Unfortunately real life doesn't work that way.&lt;/p&gt;

&lt;p&gt;If you've made it this far, you'll know whether Python samples you found online or get while vibe-coding are based on the old or current auth libraries. You also have the knowledge to modernize any old code you come across.  Both code samples can be found in the &lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;'s open source &lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Found errors or have suggestions on future content? Leave a comment below, and if your organization needs help integrating Google technologies via its APIs, reach out to me by submitting a request at &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;https://cyberwebconsulting.com&lt;/a&gt;. Also see the additional resources linked below.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;PREV POST: Using Google Workspace APIs &amp;amp; OAuth client IDs &lt;a href="https://dev.to/googleworkspace/getting-started-using-google-apis-workspace-33-2me0"&gt;part 3/3&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various links to content relevant to this post.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;Sample in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/apps" rel="noopener noreferrer"&gt;Other GWS API samples&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/analyze_gsimg/tree/master/alt" rel="noopener noreferrer"&gt;GWS &lt;strong&gt;and&lt;/strong&gt; GCP API samples using old (&amp;amp; new) auth&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other resources relevant to this post
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;"Listing your files/folders on Google Drive" &lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"Listing your files/folders on Google Drive" &lt;a href="http://goo.gl/ZIgf8k" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python authorization boilerplate (old auth library) code review &lt;a href="http://goo.gl/KMfbeK" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GWS APIs &amp;amp; OAuth client IDs &lt;a href="https://dev.to/wescpy/series/25403"&gt;post series&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GWS APIs specific use cases

&lt;ul&gt;
&lt;li&gt;Building a basic Markdown-to-Google Docs converter &lt;a href="https://dev.to/googleworkspace/building-a-basic-markdown-to-google-docs-converter-1220"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exporting Google Docs as PDF &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Importing CSV files into Google Sheets &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Mail merge with the Google Docs API &lt;a href="http://goo.gle/2HZ8K6R" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs general

&lt;ul&gt;
&lt;li&gt;GWS/G Suite developer overview &lt;a href="http://t.co/XdKEWus0KI" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Accessing GWS/G Suite REST APIs &lt;a href="https://goo.gle/3ateIIQ" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Power your apps with Gmail, Drive, Docs, Sheets, Slides (G Suite/GWS comprehensive developer overview) &lt;a href="http://youtu.be/kkp0aNGlynw" rel="noopener noreferrer"&gt;video&lt;/a&gt; (LONG)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs video series

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/kFMUa6" rel="noopener noreferrer"&gt;Launchpad Online&lt;/a&gt; (GWS &amp;amp; &lt;em&gt;other&lt;/em&gt; Google APIs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/JpBQ40" rel="noopener noreferrer"&gt;GWS/G Suite Dev Show&lt;/a&gt; (only GWS APIs)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Google APIs general

&lt;ul&gt;
&lt;li&gt;Getting started with Google APIs &lt;a href="http://developers.googleblog.com/2014/11/launchpad-online-for-developers-getting.html" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>google</category>
      <category>api</category>
      <category>programming</category>
    </item>
    <item>
      <title>Generating images with Gemini 2.0 Flash</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 07 Apr 2025 15:35:00 +0000</pubDate>
      <link>https://dev.to/gde/generating-images-with-gemini-20-flash-from-google-448e</link>
      <guid>https://dev.to/gde/generating-images-with-gemini-20-flash-from-google-448e</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;This post dives into one of the newer capabilities of the Gemini 2.0 Flash model, continuing the conversation from where we left off after looking at &lt;a href="https://dev.to/wescpy/generate-audio-clips-with-gemini-20-flash-from-google-n0g"&gt;its audio generation capabilities&lt;/a&gt;. By the end of &lt;em&gt;this&lt;/em&gt; post, you'll know how to use the Gemini API (via &lt;a href="https://ai.google.dev" rel="noopener noreferrer"&gt;Google AI&lt;/a&gt;) for (simple) &lt;em&gt;image generation&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE (Aug 2025)&lt;/strong&gt;: This post does not cover &lt;a href="https://blog.google/intl/en-mena/product-updates/explore-get-answers/nano-banana-image-editing-in-gemini-just-got-a-major-upgrade" rel="noopener noreferrer"&gt;&lt;strong&gt;Nano Banana&lt;/strong&gt;, the Gemini 2.5 Flash Image model&lt;/a&gt;, which has advanced features not available in previous or other models. It can effectively "make changes" to existing images, blend artifacts from multiple images, and allow for continuing edits. Code samples and features coming in a future blog post. Developers can access the Gemini 2.5 Flash Image preview model today via &lt;code&gt;gemini-2.5-flash-image-preview&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE (Apr 2025)&lt;/strong&gt;: This post made it to Dev.to's &lt;strong&gt;&lt;a href="https://dev.to/devteam/top-7-featured-dev-posts-of-the-week-7on"&gt;Top 7 Featured DEV Posts of the Week&lt;/a&gt;&lt;/strong&gt; around the time of &lt;a href="https://cloud.google.com/next" rel="noopener noreferrer"&gt;Google Cloud NEXT&lt;/a&gt;, so check &lt;a href="https://cloud.google.com/blog/topics/google-cloud-next/google-cloud-next-2025-wrap-up" rel="noopener noreferrer"&gt;this article highlighting the biggest launches&lt;/a&gt;. Many thanks to all readers and DEV editors for this recognition!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NOTE:&lt;/strong&gt; Gemini &lt;strong&gt;2.0&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/generating-images-with-gemini-20-flash-from-google-448e"&gt;image generation&lt;/a&gt; is &lt;strong&gt;not available&lt;/strong&gt; in the newer Gemini &lt;strong&gt;2.5&lt;/strong&gt; models, which focused on adding &lt;a href="https://ai.google.dev/gemini-api/docs/thinking" rel="noopener noreferrer"&gt;&lt;em&gt;reasoning&lt;/em&gt;&lt;/a&gt; capabilities. Gemini 2.5 does have more advanced audio generation than the 2.0 feature described here, and we'll discuss that in an upcoming post. See &lt;a href="https://ai.google.dev/gemini-api/docs/models#model-variations" rel="noopener noreferrer"&gt;this chart&lt;/a&gt; for the capabilities of &lt;em&gt;all&lt;/em&gt; Gemini models. If you're still interested in 2.5, see &lt;a href="https://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;this follow-up post&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" alt="Build with Gemini" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Thanks for stopping by my blog covering how to use Google APIs from Python (and sometimes Node.js). While there's much hype today around &lt;a href="https://cloud.google.com/discover/what-are-ai-agents" rel="noopener noreferrer"&gt;AI agents&lt;/a&gt;, &lt;a href="https://anthropic.com/news/model-context-protocol" rel="noopener noreferrer"&gt;MCP&lt;/a&gt;, and &lt;a href="https://ai.google.dev/gemma/docs/tune" rel="noopener noreferrer"&gt;fine-tuning open models like Gemma&lt;/a&gt;, there are occasions where it's more efficient and cost-effective to access a big name proprietary LLM like Gemini, an all-purpose, multimodal model.&lt;/p&gt;

&lt;p&gt;For image generation, there are even more choices, considering there are dedicated AI programs for this purpose, like &lt;a href="https://midjourney.com" rel="noopener noreferrer"&gt;Midjourney&lt;/a&gt;, &lt;a href="https://openai.com/index/dall-e-3" rel="noopener noreferrer"&gt;DALL-E&lt;/a&gt;, and &lt;a href="https://stability.ai/stable-image" rel="noopener noreferrer"&gt;Stable Diffusion&lt;/a&gt;. The main issue for developers with these is that API access isn't straightforward. There may not be an API, requiring you to use an app, and most importantly, it's unlikely to be free. Even Google's higher-quality &lt;a href="https://deepmind.google/technologies/imagen-3" rel="noopener noreferrer"&gt;Imagen&lt;/a&gt; model isn't free to use.&lt;/p&gt;

&lt;p&gt;Perhaps you're new to AI or wish to experiment with the Gemini API before integrating into an application. Using the Gemini API from Google AI is the best way for you to get started and get familiar with using the API. The &lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;free tier&lt;/a&gt; is also a great benefit. Then you can consider moving any relevant (Gemini API) work over to &lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;Google Cloud/GCP Vertex AI&lt;/a&gt; for production.&lt;/p&gt;

&lt;p&gt;Rather than strictly being an end-user of ChatGPT or Gemini, accessing LLMs programmatically via API allows you to automate processes as well as integrate AI capabilities into your applications. For the purposes of &lt;em&gt;this&lt;/em&gt; post, if you're &lt;em&gt;already&lt;/em&gt; using the Gemini API, whether &lt;a href="https://dev.to/wescpy/a-better-google-gemini-api-hello-world-sample-4ddm"&gt;text input&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/gemini-api-102-next-steps-beyond-hello-world-1pb7"&gt;multimodal input&lt;/a&gt;, or &lt;a href="https://dev.to/wescpy/generate-audio-clips-with-gemini-20-flash-from-google-n0g"&gt;audio generation&lt;/a&gt;, you might as well add &lt;strong&gt;image generation&lt;/strong&gt; to your skillset. And if you're &lt;em&gt;completely new&lt;/em&gt; to AI or &lt;em&gt;accessing Gemini via API&lt;/em&gt;, any of these posts will get you started!&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Although Google published a &lt;a href="https://developers.googleblog.com/en/experiment-with-gemini-20-flash-native-image-generation" rel="noopener noreferrer"&gt;developer blog post announcing&lt;/a&gt; the image generation feature, most of the post oddly focuses on its use within &lt;a href="https://aistudio.google.com" rel="noopener noreferrer"&gt;AI Studio&lt;/a&gt; and barely contains any code. What little code there is shows up at the bottom and is severely lacking for real use. The &lt;a href="https://ai.google.dev/gemini-api/docs/image-generation" rel="noopener noreferrer"&gt;documentation&lt;/a&gt; does a better job and also includes examples of &lt;em&gt;image editing&lt;/em&gt; with Gemini as well as using the more-capable Imagen model.&lt;/p&gt;

&lt;p&gt;The image generation samples in the docs served as motivation for the code featured here. In addition, the samples below also demonstrate one way of requesting generated text along with the image as well as giving developers different options for providing the API key. Before jumping into the code, check these two boxes off:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create API key&lt;/li&gt;
&lt;li&gt;Install required packages&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Create API key
&lt;/h3&gt;

&lt;p&gt;&lt;a href="https://makersuite.google.com/app/apikey" rel="noopener noreferrer"&gt;Create an API key&lt;/a&gt; (if you don't already have one). Then save it via one of these options:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Assign API key to &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable (easiest, and simplifies your code), &lt;strong&gt;or&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Save as &lt;code&gt;API_KEY = 'YOUR_API_KEY'&lt;/code&gt; locally to &lt;code&gt;settings.py&lt;/code&gt; (Python) or &lt;code&gt;.env&lt;/code&gt; (Node.js)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If saving locally, Python developers can also choose to save it to &lt;code&gt;.env&lt;/code&gt; instead of &lt;code&gt;settings.py&lt;/code&gt; but would have to add use of the &lt;code&gt;python-dotenv&lt;/code&gt; package to more closely mirror working in a Node.js environment. There's also the &lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;GCP Secret Manager&lt;/a&gt; as yet another option. Regardless of which technique you use, review the suggestions in the sidebar below to protect it!&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;WARNING: Keep API keys secure&lt;/strong&gt;&lt;br&gt;
Storing API keys in files (or hard-coding them for use in actual code or even assigning to environment variables) is for prototyping and learning purposes only. When going to production, put them in environment variables or in a secrets manager. Files like &lt;code&gt;settings.py&lt;/code&gt; or &lt;code&gt;.env&lt;/code&gt; containing API keys are susceptible. &lt;strong&gt;&lt;em&gt;Under no circumstances&lt;/em&gt;&lt;/strong&gt; should you &lt;a href="https://www.gitguardian.com/glossary/remediate-sensitive-data-leaks-api-keys-hardcoded-source-code" rel="noopener noreferrer"&gt;upload files like those to any public or private repo&lt;/a&gt;, &lt;a href="https://spacelift.io/blog/terraform-secrets" rel="noopener noreferrer"&gt;have sensitive data like that in TerraForm config files&lt;/a&gt;, &lt;a href="https://www.darkreading.com/cloud-security/docker-leaks-api-secrets-private-keys-cybercriminals" rel="noopener noreferrer"&gt;add such files to Docker layers&lt;/a&gt;, etc., as once your API key leaks, everyone in the world can use it.&lt;/p&gt;

&lt;p&gt;If you're new to Google developer tools, &lt;a href="https://dev.to/wescpy/series/25404"&gt;&lt;em&gt;API keys&lt;/em&gt;&lt;/a&gt; are one of the credentials types supported by Google APIs, and they're the &lt;em&gt;only&lt;/em&gt; type supported by Maps APIs. Other credentials types include &lt;a href="https://dev.to/wescpy/series/25403"&gt;&lt;em&gt;OAuth client IDs&lt;/em&gt;&lt;/a&gt;, mostly used by GWS APIs, and &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;&lt;em&gt;service accounts&lt;/em&gt;&lt;/a&gt;, mostly used by Google Cloud (GCP) APIs. While this post doesn't cover Google Maps, the Maps team put together a great &lt;a href="https://developers.google.com/maps/api-security-best-practices#rec-best-practices" rel="noopener noreferrer"&gt;guide on API key best practices&lt;/a&gt;, so check it out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Install required packages
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: &lt;code&gt;pip install -U google-genai pillow&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;) in your system or &lt;code&gt;virtualenv&lt;/code&gt; environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; &lt;code&gt;npm i @google/genai dotenv&lt;/code&gt; (remove &lt;code&gt;dotenv&lt;/code&gt; if API key in &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both versions import the newer, more flexible Gemini API client library (allowing developers to call the Gemini API from both Google AI and GCP Vertex AI platforms).&lt;/p&gt;

&lt;p&gt;The Python version also imports the PIL-compatible &lt;a href="https://pillow.readthedocs.io" rel="noopener noreferrer"&gt;Pillow library&lt;/a&gt; while the Node version imports &lt;code&gt;dotenv&lt;/code&gt; if storing the API key in &lt;code&gt;.env&lt;/code&gt; file.&lt;/p&gt;

&lt;p&gt;If you clone the overall &lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;samples repo&lt;/a&gt; and go to &lt;a href="https://github.com/wescpy/google/blob/main/gemini/images" rel="noopener noreferrer"&gt;the Gemini &lt;code&gt;images&lt;/code&gt; folder&lt;/a&gt;, you can shorten the installation commands, leveraging &lt;code&gt;requirements.txt&lt;/code&gt; (Python) or &lt;code&gt;package.json&lt;/code&gt; (Node.js) which contain the required packages:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Python&lt;/strong&gt;: &lt;code&gt;pip install -Ur requirements.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;) in your system or &lt;code&gt;virtualenv&lt;/code&gt; environment&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Node.js:&lt;/strong&gt; &lt;code&gt;npm i&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Alternatively, if you're a Python developer familiar with the wonders of &lt;code&gt;uv&lt;/code&gt;, run &lt;em&gt;these&lt;/em&gt; commands in your system or &lt;code&gt;virtualenv&lt;/code&gt; environment instead:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Update &lt;code&gt;pip&lt;/code&gt; &amp;amp; &lt;code&gt;uv&lt;/code&gt; (if nec.): &lt;code&gt;pip install -U pip uv&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Install packages: &lt;code&gt;uv pip install -Ur requirements.txt&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  The application
&lt;/h2&gt;

&lt;p&gt;Now let's look at both the Python and Node.js versions of the image generation app.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python
&lt;/h3&gt;

&lt;p&gt;The sample app &lt;a href="https://github.com/wescpy/google/blob/main/gemini/images/gem20-image.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem20-image.py&lt;/code&gt;&lt;/a&gt; asks Gemini to &lt;em&gt;create an image of a cat in a spacesuit driving a moon buggy.&lt;/em&gt; It also asks the model to &lt;em&gt;return a caption for the image&lt;/em&gt; complementing the generated image.&lt;/p&gt;

&lt;h4&gt;
  
  
  The code
&lt;/h4&gt;

&lt;p&gt;Let's jump into the app starting with the imports and constants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;io&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;BytesIO&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;PIL&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;

&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash-exp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;GenerateContentConfig&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;response_modalities&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Image&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Create an image of a cat in a spacesuit driving a moon buggy. &lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; \
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Also return a caption for the image.&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;spacecat.png&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Within the imports, you'll find several key resources that power the app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;io.BytesIO&lt;/code&gt; file-like object to contain the generated image (then saved locally to disk)&lt;/li&gt;
&lt;li&gt;Gemini API &lt;code&gt;genai&lt;/code&gt; client library (and its &lt;code&gt;types&lt;/code&gt; sub-module)&lt;/li&gt;
&lt;li&gt;Pillow PIL-compatible library that does that actual local image save&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The last import brings in the API key from &lt;code&gt;settings.py&lt;/code&gt;. However, if you store it in the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable, you can shorten the app by deleting this pair of lines...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;

&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... and replacing them with this &lt;em&gt;one&lt;/em&gt; line that reads the API key from &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; by default:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After the imports come the constants, outlined in the following table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;CONSTANT&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;MODEL&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini 2.0 Flash (Experimental) model&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;GENAI&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Gemini API client object&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CONFIG&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Model configuration (response modalities)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROMPT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Prompt as described earlier&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FILENAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Filename of generated image to save&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Image generation app constants



&lt;p&gt; &lt;/p&gt;



&lt;p&gt;The rest of the code makes up the heart of the application:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MODEL:&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;PROMPT:&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generate_content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;CAPTION:&lt;/span&gt;&lt;span class="se"&gt;\t&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="n"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;**Caption&lt;/span&gt;&lt;span class="si"&gt;:&lt;/span&gt;&lt;span class="o"&gt;**&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;, &lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="s"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;elif&lt;/span&gt; &lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inline_data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;Image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;BytesIO&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;inline_data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
        &lt;span class="n"&gt;image&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;save&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;IMAGE:&lt;/span&gt;&lt;span class="se"&gt;\t\t&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first part sets up the end-user output, and more importantly, issues the LLM query, passing the API the model requested, prompt, and configuration, including the response modalities. The &lt;code&gt;models.generate_content()&lt;/code&gt; method is called to execute the API request.&lt;/p&gt;

&lt;p&gt;The rest of the code parses the response, extracting the generated image as well as the caption, displaying the latter (after minor cleanup) and saving the former locally to disk with the requested filename.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running the script
&lt;/h4&gt;

&lt;p&gt;Running the script produces an image file along with a generated caption appropriate for the image:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 gem20-image.py
MODEL:      'gemini-2.0-flash-exp'
PROMPT:     'Create an image of a cat in a spacesuit driving a moon buggy.
Also return a caption for the image.

IMAGE:      spacecat.png
CAPTION:    Just another cat-stronaut cruising the lunar
terrain in his purr-fectly engineered moon buggy.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your mileage may vary, but this is the image I got:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78mvqfbtr2mkl2yl0g5x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F78mvqfbtr2mkl2yl0g5x.png" alt="Python spacecat" width="800" height="663"&gt;&lt;/a&gt;&lt;/p&gt;
Space cat driving moon buggy &lt;small&gt;(source: generated by Gemini 2.0 Flash [via &lt;code&gt;gem20-image.py&lt;/code&gt;])&lt;/small&gt;



&lt;p&gt; &lt;/p&gt;



&lt;h3&gt;
  
  
  Node.js
&lt;/h3&gt;

&lt;p&gt;Now let's look at the modern ECMAscript module &lt;a href="https://github.com/wescpy/google/blob/main/gemini/images/gem20-image.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;gem20-image.mjs&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  The code
&lt;/h4&gt;

&lt;p&gt;As with Python, the JS module starts with imports and constants:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;gemini-2.0-flash-exp&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;GoogleGenAI&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;apiKey&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;API_KEY&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;responseModalities&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Text&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Image&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Create an image of a cat in a spacesuit driving a moon buggy.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;
                &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Also return a caption for the image.&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;spacecat.png&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Node app imports the Gemini API client library as well as &lt;code&gt;fs&lt;/code&gt; to save the generated image. If the API key is stored in &lt;code&gt;.env&lt;/code&gt;, import &lt;code&gt;dotenv&lt;/code&gt; to copy those values to environment variables.&lt;/p&gt;

&lt;p&gt;If storing the API key in the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable, simplify the app by deleting the &lt;code&gt;import 'dotenv/config';&lt;/code&gt; line (because the API key will &lt;em&gt;already be&lt;/em&gt; an environment variable [no need to read from &lt;code&gt;.env&lt;/code&gt;]).&lt;/p&gt;

&lt;p&gt;Now for the main part of the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="c1"&gt;// Generate image with Gemini 2&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`MODEL:\t\t'&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\nPROMPT:\t\t'&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;'\n`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;models&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;generateContent&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;contents&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;config&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;

  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;candidates&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;].&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;parts&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`CAPTION:\t&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;trim&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;**Caption:** &lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;image&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;part&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;inlineData&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;Buffer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;image&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;base64&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;buffer&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`IMAGE:\t\t&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;main&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Identical in functionality to the Python version, the first chunk of code creates the API client and calls the API with the model, prompt, and config. The closing &lt;code&gt;for&lt;/code&gt;-loop processes the results: Extract the caption &amp;amp; generated image, saving the latter locally while displaying the cleaned-up caption to the end-user.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running the script
&lt;/h4&gt;

&lt;p&gt;As expected with most LLM results, you're going to get a different caption and generated image... these are what I got:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node gem20-image.mjs
MODEL:      'gemini-2.0-flash-exp'
PROMPT:     'Create an image of a cat in a spacesuit driving a moon buggy.
Also return a caption for the image.'

IMAGE:      spacecat.png
CAPTION:    Just another day at the office for this purr-fessional lunar driver.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here's another take on the "space cat" when I ran &lt;em&gt;this&lt;/em&gt; script (unsure why there's a horizontal white line at the top):&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfmj5ssnyat6t8tx5azk.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjfmj5ssnyat6t8tx5azk.png" alt="Node spacecat" width="800" height="1010"&gt;&lt;/a&gt;&lt;/p&gt;
Space cat driving moon buggy &lt;small&gt;(source: generated by Gemini 2.0 Flash [via &lt;code&gt;gem20-image.mjs&lt;/code&gt;])&lt;/small&gt;



&lt;p&gt; &lt;/p&gt;



&lt;h4&gt;
  
  
  CommonJS version
&lt;/h4&gt;

&lt;p&gt;If you prefer a CommonJS version, &lt;a href="https://github.com/wescpy/google/blob/main/gemini/images/gem20-image.js" rel="noopener noreferrer"&gt;&lt;code&gt;gem20-image.js&lt;/code&gt;&lt;/a&gt;, replace these lines...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="o"&gt;*&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv/config&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;... with the equivalent &lt;code&gt;require()&lt;/code&gt; calls ...&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;dotenv&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;config&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;GoogleGenAI&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google/genai&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you store your API key in the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable, remove &lt;em&gt;all&lt;/em&gt; the &lt;code&gt;dotenv&lt;/code&gt; lines of code (as well as from &lt;code&gt;package.json&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;LLMs usually produce different results each time, and doing so with this version gave me the following caption as well as a different generated image (not shown here):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ node gem20-image.js
MODEL:      'gemini-2.0-flash-exp'
PROMPT:     'Create an image of a cat in a spacesuit driving a moon buggy.
Also return a caption for the image.'

IMAGE:      spacecat.png
CAPTION:    "One small purr for a cat, one giant leap for feline-kind!"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Now that you've experienced a working demo, feel free to update the prompt (and output filename) with whatever you're interested in seeing Gemini create for you, or grab any part of the code to integrate in your own AI applications. I plant the seeds... it's up to all of you to make it grow!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary &amp;amp; next steps
&lt;/h2&gt;

&lt;p&gt;In this post, we continued exploring the capabilities of the Gemini API, this time looking at how to generate images using the 2.0 Flash (Experimental) model from both Python and Node.js. Learn more about image generation in the &lt;a href="https://ai.google.dev/gemini-api/docs/image-generation" rel="noopener noreferrer"&gt;Gemini API docs from Google AI&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;At the time of this writing, there's no Vertex AI docs page covering image generation using Gemini 2.0 Flash (Experimental), only Imagen 3. However, it does appear &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/gemini-experimental" rel="noopener noreferrer"&gt;the model is supported&lt;/a&gt;. Drop a comment below if you're able to get a derivative of the code from this post working on Vertex AI.&lt;/p&gt;

&lt;p&gt;From here, you can check out other posts in this series (see tables at the top or bottom of this post) or continue to explore &lt;a href="https://ai.google.dev/gemini-api/docs/quickstart#what's-next" rel="noopener noreferrer"&gt;other Gemini features&lt;/a&gt;, or "jump ahead" and explore the reasoning capabilities of the &lt;a href="https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025" rel="noopener noreferrer"&gt;Gemini 2.5 models&lt;/a&gt;, to be covered in upcoming posts.&lt;/p&gt;

&lt;p&gt;Drop a comment if you found an error in this post, a bug in &lt;a href="https://github.com/wescpy/google/tree/main/gemini/images" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic you'd like me to cover. For bugs, you can also &lt;a href="https://github.com/wescpy/google/issues" rel="noopener noreferrer"&gt;file an issue at the repo&lt;/a&gt;. I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PREV POST:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/generate-audio-clips-with-gemini-20-flash-from-google-n0g"&gt;&lt;em&gt;Part 4&lt;/em&gt;: Generate audio clips with Gemini 2.0 Flash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various links relevant to this post:&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wescpy/google/blob/main/gemini/images" rel="noopener noreferrer"&gt;Sample in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt; (Python &amp;amp; Node.js)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/gemini" rel="noopener noreferrer"&gt;Code samples for Gemini posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini API (Google AI)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/gemini_api_overview" rel="noopener noreferrer"&gt;API overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/sdks" rel="noopener noreferrer"&gt;API SDKs page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/image-generation" rel="noopener noreferrer"&gt;Image generation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/tutorials/python_quickstart" rel="noopener noreferrer"&gt;QuickStart page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/generative-ai-docs/blob/main/site/en/tutorials/python_quickstart.ipynb" rel="noopener noreferrer"&gt;QuickStart code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/api/python/google/generativeai" rel="noopener noreferrer"&gt;GenAI API reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/pricing" rel="noopener noreferrer"&gt;Gemini API pricing (free &amp;amp; paid tiers)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini 2.0 &amp;amp; 2.5 models
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/the-next-chapter-of-the-gemini-era-for-developers" rel="noopener noreferrer"&gt;2.0 launch announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blog.google/technology/google-deepmind/gemini-model-thinking-updates-march-2025" rel="noopener noreferrer"&gt;2.5 launch announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini-v2" rel="noopener noreferrer"&gt;Developer overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini/flash" rel="noopener noreferrer"&gt;Flash models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/cookbook/tree/main/gemini-2" rel="noopener noreferrer"&gt;Cookbook repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Generative AI and Gemini resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs" rel="noopener noreferrer"&gt;General GenAI docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini" rel="noopener noreferrer"&gt;Gemini home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/#three-gemini-sizes-for-unmatched-versatility" rel="noopener noreferrer"&gt;Gemini models overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini" rel="noopener noreferrer"&gt;Gemini models information (&amp;amp; quotas)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Cloud/GCP Vertex AI
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai" rel="noopener noreferrer"&gt;Vertex AI home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/models/gemini/2-0-flash" rel="noopener noreferrer"&gt;Gemini 2.0 Flash on Vertex AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/learn/model-versions" rel="noopener noreferrer"&gt;All model versions &amp;amp; support lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/multimodal/gemini-experimental" rel="noopener noreferrer"&gt;Supported experimental models&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini API post series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>node</category>
      <category>api</category>
    </item>
    <item>
      <title>Getting started using Google APIs: Service Accounts (Part 1)</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 10 Mar 2025 15:50:00 +0000</pubDate>
      <link>https://dev.to/gde/getting-started-with-google-apis-service-accounts-part-1-2fi0</link>
      <guid>https://dev.to/gde/getting-started-with-google-apis-service-accounts-part-1-2fi0</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;Use of Google APIs requires one of three (3) different credentials types, and which you use depends on both the API as well as who owns the data that API provides access to. API keys and OAuth client IDs are covered elsewhere; this post kicks off our look at &lt;em&gt;service accounts&lt;/em&gt;, the typical credential type used for Google Cloud (GCP) APIs. Lesser known: service accounts are also used to manage Google Workspace (GWS) domains. If using GCP APIs, managing GWS domains or using the &lt;a href="https://github.com/GAM-team/GAM/wiki" rel="noopener noreferrer"&gt;GAM tool&lt;/a&gt; to do so, you're probably in the right place. On the other hand, if you only use other Google APIs (non-Cloud), this content is (likely) optional. After this intro post, you will: 1) know what service accounts are, 2) when they're typically used, 3) what types of service accounts exist, and 4) what they look like.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction and motivation
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog dedicated to using Google APIs from Python (and sometimes Node.js). Regardless of which APIs you care about, I try to put "oil" for any onboarding friction you may encounter. I &lt;strong&gt;especially&lt;/strong&gt; like to cover content that's &lt;em&gt;not&lt;/em&gt; in Google's documentation. Here are some topics covered on the blog so far:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP: &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt;: Gmail, Drive, Docs, Sheets, etc.&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Credentials: &lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, service accounts (&lt;em&gt;this post&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Old vs. new (client) libraries: &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;Python auth&lt;/a&gt;, &lt;a href="https://bit.ly/4kFkmLm" rel="noopener noreferrer"&gt;Gemini API&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Before diving into service accounts, let's do a quick overview of the different credentials types to provide context for where service accounts come into play.&lt;/p&gt;

&lt;h2&gt;
  
  
  Credentials &amp;amp; authorization overview
&lt;/h2&gt;

&lt;p&gt;Google API access has an element of authentication ("authn;" you are who you say you are) and mostly authorization ("authz;" do you have data access via API). Your Google, Gmail, or Workspace account addresses the first: you need to successfully log into your account. The latter is addressed by credentials.&lt;/p&gt;

&lt;p&gt;Google APIs support three different "credentials" types:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;API keys&lt;/li&gt;
&lt;li&gt;OAuth client IDs&lt;/li&gt;
&lt;li&gt;Service accounts&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Which is required by an API depends on who owns the data it accesses, as illustrated in this chart:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Credential type&lt;/th&gt;
&lt;th&gt;Data accessed by API&lt;/th&gt;
&lt;th&gt;Example API families&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;API key&lt;/td&gt;
&lt;td&gt;Public data&lt;/td&gt;
&lt;td&gt;Maps APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;OAuth client ID&lt;/td&gt;
&lt;td&gt;Data owned by (human) users&lt;/td&gt;
&lt;td&gt;GWS APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Service account&lt;/td&gt;
&lt;td&gt;Data owned by apps/projects&lt;/td&gt;
&lt;td&gt;GCP APIs&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Required credential type determined by API data "owner"



&lt;p&gt; &lt;/p&gt;



&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;&lt;/strong&gt; -- for &lt;em&gt;APIs that access public data&lt;/em&gt;, e.g., looking for places on Google Maps, sending a picture to the GCP Vision API, searching YouTube for videos, sending a sentence to the GCP Natural Language API for analysis, and so on. Because all &lt;em&gt;Maps APIs&lt;/em&gt; access public data, expect to only use API keys.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;&lt;/strong&gt; -- for &lt;em&gt;APIs that access data owned by (human) users&lt;/em&gt;. Your app needs a user's permission (in the form of "scopes") to access their documents on Drive, their messages in Gmail, videos in a user's YouTube playlists, and so on. Since Workspace data is almost always personal data, expect to use OAuth client IDs with &lt;em&gt;all GWS APIs&lt;/em&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Service accounts&lt;/strong&gt; -- for &lt;em&gt;APIs that access data owned by an app or project&lt;/em&gt;. For cloud-based apps, there's no human to prompt for permissions, plus the data isn't owned by humans anyway. Authorization is implicitly granted via service account and IAM (identity and access management roles) and public-private key-pairs created with those specific permissions. As you can also guess, "cloud-based" primarily means &lt;em&gt;GCP APIs&lt;/em&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Some Google APIs accept more than one type of credentials.&lt;/strong&gt; For example, while you'd typically use service accounts with the GCP &lt;a href="http://cloud.google.com/vision" rel="noopener noreferrer"&gt;Cloud Vision API&lt;/a&gt;, sending an image (rather than reading a file from someone's Google Drive or a GCP project's Cloud Storage bucket) is considered "public data," so an API key works.&lt;/p&gt;

&lt;p&gt;Another &lt;a href="http://goo.gle/3nPxmlc" rel="noopener noreferrer"&gt;sample app I wrote uses &lt;em&gt;OAuth client IDs&lt;/em&gt; to call the Vision API&lt;/a&gt;. The images are read from a user's Google Drive, so I was already using OAuth client IDs. Adding use of an API key or service account mixes credentials, making the code harder to read/understand. While API keys suffice, &lt;em&gt;stricter&lt;/em&gt; credentials (OAuth client IDs or service accounts) are acceptable for the Vision API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Service accounts
&lt;/h2&gt;

&lt;p&gt;The world of service accounts is vast, and it will take more than one post to present the broad usage available. For this intro post, it's important to keep it at a high-level to introduce the concepts so you know what they are. We can dive deeper in upcoming posts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Service accounts are peo... &lt;em&gt;principals&lt;/em&gt; too
&lt;/h3&gt;

&lt;p&gt;Before taking a step further, I need to clarify that service accounts themselves are just &lt;em&gt;principals&lt;/em&gt;, which you can think of as "users" in the general sense. Most of the time, principals refers to human users whose accounts are identified by email addresses. A service account is also a principal because it is also identified by an email address... it just doesn't belong to a person. You can think of a service account mildly as a "robot account."&lt;/p&gt;

&lt;h3&gt;
  
  
  How service accounts differ from user authz
&lt;/h3&gt;

&lt;p&gt;So where does the "credential" part come into play? As discussed earlier, service accounts are primarily used when running applications that access cloud resources, data owned by a cloud project and not human users.&lt;/p&gt;

&lt;p&gt;When OAuth IDs are involved, you have one or more human developers (mainly you) and one or more human end-users. Whoever is running your code, the owner of the data, must grant the permissions required by your app in order for it to function. Those permissions come in the form of &lt;a href="https://developers.google.com/identity/protocols/oauth2/scopes" rel="noopener noreferrer"&gt;&lt;em&gt;scopes&lt;/em&gt;&lt;/a&gt;. (&lt;a href="https://developers.google.com/workspace/guides/configure-oauth-consent#scope_categories" rel="noopener noreferrer"&gt;Some scopes required more scrutiny&lt;/a&gt; before your app can be made available for users.)&lt;/p&gt;

&lt;p&gt;When you write an app and test it yourself, you play both roles: you're the developer as well as the end-user who owns the data. But we're not talking user-owned data now, so OAuth client IDs aren't going to work. The data lives in the cloud, and there's also no end-user to ask for permissions.&lt;/p&gt;

&lt;p&gt;To summarize, a service account is a principal that is given pre-determined privileges to access the data it needs to access. Rather than permission scopes, service account permissions come in the form of Identity and Access Management &lt;a href="https://cloud.google.com/iam/docs/roles-overview" rel="noopener noreferrer"&gt;(IAM) roles and permissions&lt;/a&gt;. You don't prompt a user for permissions when they (those permissions) have already been granted.&lt;/p&gt;

&lt;p&gt;For additional reading, I recommend &lt;a href="https://cloud.google.com/blog/products/identity-security/how-to-authenticate-service-accounts-to-help-keep-applications-secure" rel="noopener noreferrer"&gt;this Google Cloud blog post&lt;/a&gt; which states two clear points:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"Service accounts represent non-human users and... are intended for scenarios where an application needs to access resources or perform actions under its own identity."&lt;/li&gt;
&lt;li&gt;"Unlike normal users, service accounts cannot authenticate using a password or single sign-on (SSO)."&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Service account types
&lt;/h3&gt;

&lt;p&gt;In total, there are three (3) &lt;a href="https://cloud.google.com/iam/docs/service-account-overview#types" rel="noopener noreferrer"&gt;different types of service accounts&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service account type&lt;/th&gt;
&lt;th&gt;Creator&lt;/th&gt;
&lt;th&gt;Manager&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-types#user-managed" rel="noopener noreferrer"&gt;&lt;em&gt;User-managed service accounts&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;You (developer)&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Created by you with specific permissions for your application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-types#default" rel="noopener noreferrer"&gt;&lt;em&gt;Default service accounts&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;You&lt;/td&gt;
&lt;td&gt;Created by Google with broad permissions for specific platforms&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-types#service-agents" rel="noopener noreferrer"&gt;&lt;em&gt;Service agents&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Google&lt;/td&gt;
&lt;td&gt;Created &amp;amp; managed by Google for internal use only&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;As just mentioned, most of the time, your app will be using user-managed service accounts. Default service accounts are a different beast, "user-managed" but Google-created service accounts for specific platforms, like App Engine or Compute Engine.&lt;/p&gt;

&lt;p&gt;App Engine apps and Compute Engine VMs can call many GCP APIs because default service accounts give broad permissions and are attached to those platforms, simplifying the prototyping experience. While they do their job at getting your app kickstarted, when moving towards production, switch to user-managed service accounts with locked-down permissions, just enough for your app to function correctly and &lt;a href="https://cloud.google.com/iam/docs/best-practices-service-accounts#use-credentials-api" rel="noopener noreferrer"&gt;adhere to the principle&lt;/a&gt; of &lt;a href="https://cloud.google.com/iam/docs/using-iam-securely#least_privilege" rel="noopener noreferrer"&gt;least privilege&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Service agents are "the easiest." They're service accounts you don't need to think about. They're created by Google and only used by Google, custom-made for specific products. They help each GCP product "do their jobs."&lt;/p&gt;

&lt;h3&gt;
  
  
  Service account formats
&lt;/h3&gt;

&lt;p&gt;Now you know what kinds of service accounts there are, how about what they look like? You know they're email addresses, but I have to tell ya: &lt;em&gt;They're not pretty to look at&lt;/em&gt;. Here are some examples:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;code&gt;google@appspot.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;123456789-compute@developer.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;firebase-adminsdk-y7bg@google.iam.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;svcacct1@google.iam.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service-123456789@container-engine-robot.iam.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;service-123456789@serverless-robot-prod.iam.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;123456789@cloudservices.gserviceaccount.com&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Doesn't seem to be clear which ones are user-managed, default, or service agents. While they vary greatly, you'll notice they &lt;strong&gt;all&lt;/strong&gt; have a "domain name" of &lt;code&gt;gserviceaccount.com&lt;/code&gt;, so that's your sign it's &lt;em&gt;some&lt;/em&gt; kind of service account. Most of the time, the ones you make and use will likely resemble the one in the middle, #4. That's an example of a user-managed service account, one created by you. The chart below will help you determine what kind of service accounts you see:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Service account address format&lt;/th&gt;
&lt;th&gt;Service account type description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_ID@appspot.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;App Engine &lt;strong&gt;default service account&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_NUM-compute@developer.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Compute Engine &lt;strong&gt;default service account&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SVC_ACCT@PROJECT_ID.iam.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;User-managed service account&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;firebase-adminsdk-HASH@PROJECT_ID.iam.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Firebase Admin SDK &lt;strong&gt;service agent&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_NUM@cloudservices.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Google APIs &lt;strong&gt;service agent&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;service-PROJECT_NUM@SERVICE.gserviceaccount.com&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;GCP product &lt;strong&gt;service agent&lt;/strong&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;... where ...&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Email address component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SERVICE&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Something that somewhat identifies the GCP service your app is using&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SVC_ACCT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Service account name you chose&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_ID&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project ID (not to be confused with project number)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJECT_NUM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project number (not to be confused with project ID)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;HASH&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Short, random hash value&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] GCR free domain name components 



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The first pair are default service accounts. These are the service accounts described earlier that are automatically-created providing broad permissions to help your App Engine or Compute Engine apps get off the ground quicker, enabling those apps to call many GCP APIs. User-managed service accounts will be the ones you use the most. The final three are service agents, created by Google and only "for internal use" by the GCP project they were created for.&lt;/p&gt;

&lt;p&gt;All projects have names, IDs, and numbers. You choose the first. Project IDs are strings that uniquely identify a project while project numbers are Google-generated numeric values, also uniquely identifying projects. Any confusion between these three (3) can be clarified with &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects#before_you_begin" rel="noopener noreferrer"&gt;this page in the documentation&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Using service accounts with Workspace
&lt;/h2&gt;

&lt;p&gt;While most service account usage takes place in GCP, e.g., applications running on GCP compute platforms that call GCP APIs, or accessing data in GCP data products, there is a use case for using service accounts with GWS. When managing many end-users in Workspace domains, domain administrators often have to perform maintenance tasks on behalf of their users. For this purpose, they can use service accounts with &lt;a href="https://support.google.com/a/answer/162106" rel="noopener noreferrer"&gt;domain-wide delegation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Doing so allows, "internal and third-party apps to access ... users’ Google Workspace data, bypassing end user consent. To do this,... create a service account in the Google Cloud console and delegate domain-wide authority to the account in [the] Google Admin console." Without requiring permissions from every user in the domain, admins can get important tasks completed in a timely fashion. Obviously, this grants broad permissions to admins. With great power comes great responsibility, so admins are advised to adhere to &lt;a href="https://support.google.com/a/answer/14437356" rel="noopener noreferrer"&gt;domain-wide delegation best practices&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and next steps
&lt;/h2&gt;

&lt;p&gt;This introductory post on service accounts covered basics such as how service accounts differ from other Google API credentials types like API keys and OAuth client IDs. They access private data requiring authz but where you have data that live in the cloud, and belong to a Cloud project rather than a human user, and where there are no human users to request authz anyway. They are a principal but are given pre-determined Cloud IAM permissions.&lt;/p&gt;

&lt;p&gt;There are different types of service accounts, including default service accounts and service agents. However, the primary type of service account you interact with are user-managed service accounts. Each of these service account types come in various formats.&lt;/p&gt;

&lt;p&gt;While service accounts are typically used for apps or data that live in GCP, service accounts may also be useful for GWS domain administrators who have to perform maintenance tasks on behalf of end-users. In such cases, service accounts with domain-wide delegation can complete those tasks without requiring user consent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;Now that you know what service accounts are, when you'd typically use them, what types of service accounts exist, and what they look like, I'll show you how to &lt;em&gt;use&lt;/em&gt; them in upcoming posts. If you found an error in this post or would like me to cover a specific topic &lt;em&gt;besides service accounts&lt;/em&gt; in future posts, let me know in the comments below. I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;NEXT POST:&lt;/strong&gt; Getting started using Google APIs: Service Accounts (Part 2: How to use [&lt;em&gt;TBD&lt;/em&gt;])&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ALTERNATIVE 1:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/series/25404"&gt;Getting started using Google APIs: API Keys series&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;ALTERNATIVE 2:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/series/25403"&gt;Getting started using Google APIs: OAuth client IDs series&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources relevant to this post which you may find useful.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/docs/authentication#service-accounts" rel="noopener noreferrer"&gt;Quick intro to service accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-overview" rel="noopener noreferrer"&gt;Full overview of service accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-types" rel="noopener noreferrer"&gt;Types of service accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/docs/service-account-creds" rel="noopener noreferrer"&gt;Service account credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/docs/best-practices-for-managing-service-account-keys" rel="noopener noreferrer"&gt;Best Practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/iam/docs/using-iam-securely#least_privilege" rel="noopener noreferrer"&gt;Principle of least privilege&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/identity-security/how-to-authenticate-service-accounts-to-help-keep-applications-secure" rel="noopener noreferrer"&gt;Service accounts blog post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/a/answer/162106" rel="noopener noreferrer"&gt;GWS domain-wide delegation with service accounts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://support.google.com/a/answer/14437356" rel="noopener noreferrer"&gt;Domain-wide delegation best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/GAM-team/GAM/wiki" rel="noopener noreferrer"&gt;GAM GWS domain admin management tool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>googlecloud</category>
      <category>api</category>
      <category>webdev</category>
      <category>programming</category>
    </item>
    <item>
      <title>Building a basic Markdown-to-Google Docs converter</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 10 Feb 2025 16:48:00 +0000</pubDate>
      <link>https://dev.to/googleworkspace/building-a-basic-markdown-to-google-docs-converter-1220</link>
      <guid>https://dev.to/googleworkspace/building-a-basic-markdown-to-google-docs-converter-1220</guid>
      <description>&lt;h2&gt;
  
  
  Primer on using the Docs #API... TL;DR:
&lt;/h2&gt;

&lt;p&gt;Other Google Workspace (GWS) API posts featured on this blog &lt;em&gt;seem&lt;/em&gt; to be about Docs &amp;amp; Sheets (well, their APIs), but the code samples are almost always file-oriented operations, like &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;exporting Google Docs as PDF&lt;/a&gt; or &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;importing CSV files into Google Sheets&lt;/a&gt;. File-related activity generally points to the &lt;strong&gt;Google Drive API&lt;/strong&gt; instead of "editor" APIs. This post breaks free from that pattern, kicking off a Markdown-to-Google Docs converter; yes, using the Docs (not Drive) API! From this post, readers will know &lt;em&gt;how to programmatically create new Google Docs, write text to them, and format text in them&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ha5q59y9q2aeu5hon9l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7ha5q59y9q2aeu5hon9l.png" alt="Markdown to Google Docs conversion" width="800" height="533"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction and motivation
&lt;/h2&gt;

&lt;p&gt;You've stumbled upon the blog focused on showing Python (and sometimes Node.js) developers how to use different parts of Google's developer ecosystem, from APIs to compute and AI/ML platforms where I provide the "oil" to smoothen your onboarding friction. I &lt;strong&gt;especially&lt;/strong&gt; like to cover content that's &lt;em&gt;not&lt;/em&gt; in Google's documentation. Pick any topic, and I probably have you covered:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP (&lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt; (Gmail, Drive, Docs, Sheets, etc.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Nuts 'n bolts like &lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt; and &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt; (the latter primarily for GWS APIs)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I realized I have a "problem" with most of my GWS content: they &lt;em&gt;only cover the Drive API&lt;/em&gt;. Boring! Workspace has so many others, I'm not doing any of &lt;em&gt;you&lt;/em&gt; any favors if I don't explore &lt;strong&gt;other&lt;/strong&gt; GWS APIs, causing me to "randomly" choose the &lt;a href="https://developers.google.com/docs" rel="noopener noreferrer"&gt;Google Docs API&lt;/a&gt; for this post.&lt;/p&gt;

&lt;h2&gt;
  
  
  Application: inspiration &amp;amp; motivation
&lt;/h2&gt;

&lt;p&gt;The sample app is inspired by a pair of projects familiar to me. A while back, I created a tool that ingests &amp;amp; parses the outline of a presentation in a Markdown-like syntax, converting it into a Microsoft PowerPoint presentation. You could even set the template and kick off the slide show! That script was soon joined by others for Microsoft Word, Excel, Outlook, and covered in Chapter 7 ("Programming Microsoft Office") of &lt;a href="http://amzn.com/dp/0132678209" rel="noopener noreferrer"&gt;one of my Python books&lt;/a&gt;. (To fans and readers: Yes, I know I need to work on a new edition and get the code into GitHub!)&lt;/p&gt;

&lt;p&gt;After &lt;a href="http://goo.gl/o6EFwk" rel="noopener noreferrer"&gt;launching the Google Slides API&lt;/a&gt; years later, I wanted to create a similar tool for Slides, however a colleague beat me to it with &lt;a href="https://github.com/googleworkspace/md2googleslides" rel="noopener noreferrer"&gt;&lt;code&gt;md2googleslides&lt;/code&gt;&lt;/a&gt;, a well-forked &amp;amp; starred project that supports full Markdown and a variety of content. A huge app like that would be overwhelming for short posts like these, so I settled on a very rudimentary, the most basic, subset of Markdown, supporting only underscores (&lt;code&gt;_&lt;/code&gt;) for &lt;em&gt;italics&lt;/em&gt; and asterisks (&lt;code&gt;*&lt;/code&gt;) for &lt;strong&gt;bold&lt;/strong&gt;:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Take a file formatted with any of those directives, parse out the text from the Markdown, generate a Google Doc document, insert the text, then format it per the markup directives. (Other incarnations of Markdown or Wiki formatting use a single symbol [either one] for italics and double for bold, however I opted for a simpler implementation for regex purposes.)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;While you &lt;em&gt;can&lt;/em&gt; &lt;a href="https://workspaceupdates.googleblog.com/2022/03/compose-with-markdown-in-google-docs-on.html" rel="noopener noreferrer"&gt;compose in Markdown with Docs&lt;/a&gt; as well as &lt;a href="https://workspaceupdates.googleblog.com/2024/07/import-and-export-markdown-in-google-docs.html" rel="noopener noreferrer"&gt;import &amp;amp; export Markdown in Docs&lt;/a&gt;, you don't want to &lt;em&gt;really&lt;/em&gt; use the UI (user interface) to process hundreds or thousands of generated Markdown files. To truly automate and scale beyond "one file at a time," you need API power.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 NOTE: &lt;strong&gt;Alternate version uses older Python (OAuth2) auth library&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;There is an alternate version of the Python app in this post: the main one uses the current &lt;a href="https://github.com/googleapis/google-auth-library-python" rel="noopener noreferrer"&gt;Google auth library for Python&lt;/a&gt; while the alternative (named &lt;code&gt;*-old.py&lt;/code&gt;) utilizes the &lt;a href="https://github.com/googleapis/oauth2client" rel="noopener noreferrer"&gt;older auth library&lt;/a&gt; which was &lt;a href="https://github.com/googleapis/oauth2client/pull/714" rel="noopener noreferrer"&gt;deprecated in 2017&lt;/a&gt; but is still widely used (and unfortunately, with many samples online). It serves as a migration tool to help remaining users upgrade and dive deeper into this issue in a &lt;a href="https://dev.to/googleworkspace/oauth-client-ids-dirty-little-secrets-old-new-python-auth-libraries-4mb7"&gt;related post&lt;/a&gt;, calling Google's lack of proper deprecation a "dirty little secret." &lt;code&gt;:-)&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 NOTE: &lt;strong&gt;Code sample Python 2 and 3 compatible&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;In a similar vein to help remaining Python 2 users upgrade &amp;amp; migrate to Python 3, the sample scripts are 2.x/3.x-compatible. However, this means you won't find 3-only features like &lt;code&gt;f&lt;/code&gt;-strings, type annotations, or &lt;code&gt;async/await&lt;/code&gt;. If a reader wants to submit a pure Python 3 version, I'll look at PR requests.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;p&gt;The sample app comes in &lt;em&gt;four (4)&lt;/em&gt; flavors, a pair each in Python and Node.js, &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs" rel="noopener noreferrer"&gt;all accessible in the repo folder&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Imports
&lt;/h4&gt;

&lt;p&gt;The &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs/python/md2docs.py" rel="noopener noreferrer"&gt;&lt;code&gt;md2docs.py&lt;/code&gt;&lt;/a&gt; import code at the top look like this:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os.path&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.auth.transport.requests&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Request&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google.oauth2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google_auth_oauthlib.flow&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The standard library imports bring Python 3's &lt;code&gt;print()&lt;/code&gt; function to Python 2 (ignored in 3.x), filesystem access via &lt;code&gt;os&lt;/code&gt;, and &lt;code&gt;re&lt;/code&gt; for regular expressions. Those are followed by importing the Google API and auth client libraries.&lt;/p&gt;

&lt;p&gt;For developers still using the older auth library, here are the equivalent imports in &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs/python/md2docs-old.py" rel="noopener noreferrer"&gt;&lt;code&gt;md2docs-old.py&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;__future__&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;print_function&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;googleapiclient&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;httplib2&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Http&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;oauth2client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
Switching to JavaScript, below are the equivalent lines for the Node.js ES module, &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs/nodejs/md2docs.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;md2docs.mjs&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:fs/promises&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;node:process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/local-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you prefer CommonJS, that file is &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs/nodejs/md2docs.js" rel="noopener noreferrer"&gt;&lt;code&gt;md2docs.js&lt;/code&gt;&lt;/a&gt;, and &lt;em&gt;those&lt;/em&gt; lines look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;promises&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;path&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;process&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;authenticate&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@google-cloud/local-auth&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;googleapis&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like their Python twins, these employ packages for filesystem operations as well as Google security and API access. This is where the differences end between the JavaScript versions... the remainder of the code is identical across both ES modules and CommonJS.&lt;/p&gt;

&lt;h4&gt;
  
  
  Security
&lt;/h4&gt;

&lt;p&gt;Next are the constants and security code:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quickbrownfox.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# change to your own Markdown file
&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;
&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/documents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;TOKENS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exists&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_authorized_user_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expired&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;refresh&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Request&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
    &lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;InstalledAppFlow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;from_client_secrets_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_local_server&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;TOKENS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;w&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;to_json&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;span class="n"&gt;DOCS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;credentials&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The old auth version is a bit simpler to use because &lt;em&gt;that&lt;/em&gt; library manages the OAuth token storage (so you don't have to):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;quickbrownfox.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;  &lt;span class="c1"&gt;# change to your own Markdown file
&lt;/span&gt;
&lt;span class="n"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;https://www.googleapis.com/auth/documents&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;store&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;file&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Storage&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;storage.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;invalid&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;flow&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;flow_from_clientsecrets&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;client_secret.json&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;creds&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;tools&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run_flow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;flow&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;store&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;DOCS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;discovery&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;build&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;docs&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;creds&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nc"&gt;Http&lt;/span&gt;&lt;span class="p"&gt;()))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Beyond this, both Python versions are identical. The JavaScript code operates in a similar way as in Python but does it slightly differently: each piece of functionality is broken up into its own function with improved constants &amp;amp; naming:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;quickbrownfox.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;  &lt;span class="c1"&gt;// change to your own Markdown file&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;client_secret.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;path&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;join&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;cwd&lt;/span&gt;&lt;span class="p"&gt;(),&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;storage.json&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://www.googleapis.com/auth/documents&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;loadSavedCredentialsIfExist&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;credentials&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;fromJSON&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;saveCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;installed&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;keys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;web&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;authorized_user&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;key&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;client_secret&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;refresh_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;access_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;token_expiry&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;token_expiry&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;TOKEN_STORE_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;loadSavedCredentialsIfExist&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
  &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;authenticate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;scopes&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;SCOPES&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;keyfilePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;CREDENTIALS_PATH&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;credentials&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;saveCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The constant at the top points to the &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs/nodejs/quickbrownfox.md" rel="noopener noreferrer"&gt;&lt;code&gt;quickbrownfox.md&lt;/code&gt; data file&lt;/a&gt;, but its contents are arbitrary. Feel free to either change the content or point the code to your &lt;em&gt;own&lt;/em&gt; Markdown file. For demonstration's sake, &lt;code&gt;quickbrownfox.md&lt;/code&gt; as provided contains this Markdown data:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;The _quick, brown_ fox jumped over the *lazy dogs*.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The security code is required because the end-user must grant &lt;strong&gt;their permission&lt;/strong&gt; for &lt;strong&gt;your code&lt;/strong&gt; to access &lt;strong&gt;their data&lt;/strong&gt;; in this case, creating documents on their behalf. Authorization ("authz") requires you to create a client ID &amp;amp; secret pair, then download the file from your Google developer project.&lt;/p&gt;

&lt;p&gt;Complete info on this code segment and how "to do" authz for GWS APIs can be found in the &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client ID 3-part series&lt;/a&gt; (primarily the last post), so I won't repeat it here. Check it out if you're new or need a refresher. After this code review, I'll provide setup steps for running the samples but check those posts for all the details.&lt;/p&gt;

&lt;p&gt;The Python security snippets end with &lt;code&gt;DOCS&lt;/code&gt;, a Docs API client object or "service endpoint" to use for API calls. (In most official Google code samples, rather than a descriptive constant such as &lt;code&gt;DOCS&lt;/code&gt;, you'll find &lt;code&gt;service&lt;/code&gt; used as the variable name and now know why.) For JavaScript, rather than a global variable, the main driver manages it, passing it to each function that makes an API call.&lt;/p&gt;

&lt;h4&gt;
  
  
  Read and parse Markdown
&lt;/h4&gt;

&lt;p&gt;The next step is to read and parse the Markdown file content.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;read_parse_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;read&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[]&lt;/span&gt;
    &lt;span class="n"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;re&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;findall&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;r&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;(([_*])([^\2]+?)\2)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;match&lt;/span&gt;
        &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;find&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;
        &lt;span class="n"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;bold&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;dl&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;*&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="k"&gt;else&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;italic&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
        &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;append&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="n"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;read_parse_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;contentBuf&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;readFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;contentBuf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toString&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;utf-8&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;matchAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;(([&lt;/span&gt;&lt;span class="sr"&gt;_*&lt;/span&gt;&lt;span class="se"&gt;])([^\2]&lt;/span&gt;&lt;span class="sr"&gt;+&lt;/span&gt;&lt;span class="se"&gt;?)\2)&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;matches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;dl&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pt&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;match&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;indexOf&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="nx"&gt;content&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;md&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;pt&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kd"&gt;var&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;dl&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;*&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;bold&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;italic&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;([&lt;/span&gt; &lt;span class="nx"&gt;action&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both samples are nearly line-by-line identical, so let's dive into them together. The &lt;code&gt;read_parse_md()&lt;/code&gt; function is as advertised: it reads and parses the Markdown file, converting the markup into formatting &lt;code&gt;actions&lt;/code&gt; to request via the Docs API.&lt;/p&gt;

&lt;p&gt;A regular expression ("regex") is used to pattern-match strings that need to be &lt;em&gt;italicized&lt;/em&gt; with underscores, e.g., &lt;code&gt;_sample_&lt;/code&gt;, and those to be &lt;strong&gt;bolded&lt;/strong&gt;, e.g., &lt;code&gt;*sample*&lt;/code&gt;. A single pass through the &lt;code&gt;content&lt;/code&gt; string takes place, converting each stylization request to an API "styling action."&lt;/p&gt;

&lt;p&gt;The regex may be a bit obfuscated to look at, but it basically works like this: look for an opening &lt;code&gt;_&lt;/code&gt; or &lt;code&gt;*&lt;/code&gt; and cache that symbol as &lt;code&gt;\2&lt;/code&gt; (the 2nd pattern match). Upon discovering a &lt;code&gt;match&lt;/code&gt;, seek its closing twin symbol with the minimal characters grabbed [in a non-greedy (&lt;code&gt;?&lt;/code&gt;) way], and save both the entire marked-up (&lt;code&gt;md&lt;/code&gt;) and plain text (&lt;code&gt;pt&lt;/code&gt;) strings plus the delimiter (&lt;code&gt;dl&lt;/code&gt;). For "&lt;code&gt;_sample_&lt;/code&gt;", &lt;code&gt;md&lt;/code&gt; would be &lt;code&gt;_sample_&lt;/code&gt;, &lt;code&gt;dl&lt;/code&gt; is &lt;code&gt;_&lt;/code&gt;, &lt;code&gt;pt&lt;/code&gt; is &lt;code&gt;sample&lt;/code&gt;, and &lt;code&gt;action&lt;/code&gt; is set to &lt;code&gt;italic&lt;/code&gt;. The table below illustrates all these values:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Regex&lt;/th&gt;
&lt;th&gt;Regex match&lt;/th&gt;
&lt;th&gt;Saved-to variable&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;th&gt;Example&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;((...\2)&lt;/code&gt; (outermost &lt;code&gt;()&lt;/code&gt;)&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\1&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"MarkDown:" &lt;code&gt;md&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Entire (single) pattern-matched string&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_sample_&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;([_*])&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\2&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"DeLimiter:" &lt;code&gt;dl&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Markdown directive&lt;/td&gt;
&lt;td&gt;&lt;code&gt;_&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;([^\2]+?)&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;&lt;code&gt;\3&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;"Plain Text:" &lt;code&gt;pt&lt;/code&gt;
&lt;/td&gt;
&lt;td&gt;Pattern-matched string with delimiters stripped&lt;/td&gt;
&lt;td&gt;&lt;code&gt;sample&lt;/code&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Regex patterns &amp;amp; matches



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The easiest way to "decipher" the match numbers is &lt;code&gt;\1&lt;/code&gt; is the first left parenthesis found in the regex pattern, &lt;code&gt;\2&lt;/code&gt; is the 2nd, and so on.&lt;/p&gt;

&lt;p&gt;To tell the Docs API to stylize any piece of text, it must know &lt;em&gt;where&lt;/em&gt; the text is, meaning your requests have to provide the &lt;strong&gt;location&lt;/strong&gt; as well as the action. These are raw offsets: a start index (&lt;code&gt;i&lt;/code&gt;) and an end index (&lt;code&gt;j&lt;/code&gt;). All style requests are processed in this manner, saving (&lt;code&gt;action&lt;/code&gt;, &lt;code&gt;i&lt;/code&gt;, &lt;code&gt;j&lt;/code&gt;) into the &lt;code&gt;actions&lt;/code&gt; array.&lt;/p&gt;

&lt;p&gt;At the end, any markup will be stripped out and raw &lt;code&gt;content&lt;/code&gt; "reduced" to plain text. The &lt;code&gt;actions&lt;/code&gt; array has all the "instructions" required for stylizing via the API.&lt;/p&gt;

&lt;h4&gt;
  
  
  Creating and writing to a Google Doc
&lt;/h4&gt;

&lt;p&gt;Armed with all the content and instructions, we need code to create a new, empty Google Doc and write the data string into it. There is one function for each task:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Function&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;create_doc()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Create new/empty Google Doc and return its file ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;write_text()&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Write text into Doc identified by file ID&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;create_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;title&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;}).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;documentId&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;insertText&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;location&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;index&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;text&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;}}]&lt;/span&gt;
    &lt;span class="n"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;batchUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;create_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fname&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rsp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;create&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;fname&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;data&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;insertText&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;location&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;index&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;text&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="p"&gt;];&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batchUpdate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A document name is all that's needed to create a new Google Doc; in this case, it's the Markdown filename with the &lt;code&gt;.md&lt;/code&gt; file extension removed. The &lt;code&gt;create_doc()&lt;/code&gt; function issues the request and returns the newly-created document's (Google Drive) file ID.&lt;/p&gt;

&lt;p&gt;Updating a Google Doc requires one or more &lt;a href="https://developers.google.com/docs/api/reference/rest/v1/documents/request" rel="noopener noreferrer"&gt;requests&lt;/a&gt;. In this case, it's an &lt;a href="https://developers.google.com/docs/api/reference/rest/v1/documents/request#InsertTextRequest" rel="noopener noreferrer"&gt;&lt;code&gt;InsertTextRequest&lt;/code&gt;&lt;/a&gt; which requires, at minimum, two things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The &lt;code&gt;text&lt;/code&gt; to insert&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;location&lt;/code&gt; (index offset)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;In a new document, inserting at index 1 (not 0) is the right place. The Node.js versions of these functions differ only in that the API client object &lt;code&gt;DOCS&lt;/code&gt; is passed into each call whereas the same global variable is used in Python.&lt;/p&gt;

&lt;h4&gt;
  
  
  Formatting text in a Google Doc
&lt;/h4&gt;

&lt;p&gt;The last piece of the puzzle is a function that formats text in a Google Doc; that's &lt;code&gt;format_text()&lt;/code&gt; is for:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;format text with requested Markdown styling&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="n"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[{&lt;/span&gt;
        &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;updateTextStyle&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;range&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;startIndex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;endIndex&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;textStyle&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;fields&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;j&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
    &lt;span class="n"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;batchUpdate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;body&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;requests&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
            &lt;span class="n"&gt;documentId&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;fields&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;execute&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[];&lt;/span&gt;
  &lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;push&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;updateTextStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;range&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;startIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;i&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;endIndex&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;j&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
        &lt;span class="na"&gt;textStyle&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt; &lt;span class="p"&gt;]:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="na"&gt;fields&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;style&lt;/span&gt;
      &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt;

  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;documents&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;batchUpdate&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
      &lt;span class="na"&gt;requestBody&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;requests&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;requests&lt;/span&gt; &lt;span class="p"&gt;},&lt;/span&gt;
      &lt;span class="na"&gt;documentId&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It takes the document's file ID (&lt;code&gt;docs_id&lt;/code&gt;) and the stylization &lt;code&gt;actions&lt;/code&gt; (array), and builds an equivalent array of &lt;a href="https://developers.google.com/docs/api/reference/rest/v1/documents/request#UpdateTextStyleRequest" rel="noopener noreferrer"&gt;&lt;code&gt;updateTextStyle&lt;/code&gt; API requests&lt;/a&gt;, meaning a bold or italicize command plus the location of the affected text.&lt;/p&gt;

&lt;p&gt;If it "seems" like &lt;code&gt;requests&lt;/code&gt; is an array of JSON objects, you're spot on. If you could dump its contents after processing &lt;code&gt;actions&lt;/code&gt;, it would look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="err"&gt;'updateTextStyle':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'fields':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'italic'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="err"&gt;'range':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'endIndex':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;17&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'startIndex':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;4&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="err"&gt;'textStyle':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'italic':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}}},&lt;/span&gt;&lt;span class="w"&gt;
 &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'updateTextStyle':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'fields':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'bold'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="err"&gt;'range':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'endIndex':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;47&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;'startIndex':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;37&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
                      &lt;/span&gt;&lt;span class="err"&gt;'textStyle':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="err"&gt;'bold':&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;True&lt;/span&gt;&lt;span class="p"&gt;}}}]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The last thing it does is pass the array along with the file ID to the API which executes the requests. This is a very rudimentary processor of the most basic Markdown directives. The point is to get you started on using the Docs API, not writing a fully-featured Markdown parser, which you can pick up as an exercise if desired.&lt;/p&gt;

&lt;h4&gt;
  
  
  Main driver
&lt;/h4&gt;

&lt;p&gt;While all these functions are great, a "main" driver is required to tie everything together. Here they are respectively:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;read_parse_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** Parsed Markdown file %r &amp;amp; style actions&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;docs_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;.md&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;''&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="c1"&gt;# remove MD file ext
&lt;/span&gt;    &lt;span class="n"&gt;docs_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;create_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs_fn&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** Created %r (ID: %s)&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;docs_fn&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** Inserted %r into document&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt; &lt;span class="o"&gt;%&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** Completed Markdown formatting in document:&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;md2docs&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;authClient&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;DOCS&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;google&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;docs&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;version&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;v1&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;auth&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;authClient&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt; &lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;read_parse_md&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** Parsed Markdown file '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' &amp;amp; style actions`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs_fn&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.md&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;create_doc&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docs_fn&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** Created '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;docs_fn&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;' (ID: &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;)`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;write_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`** Inserted '&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;replace&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sr"&gt;/&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sr"&gt;/g&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="se"&gt;\\&lt;/span&gt;&lt;span class="s2"&gt;n&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)}&lt;/span&gt;&lt;span class="s2"&gt;' into document`&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;format_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;DOCS&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;docs_id&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;** Completed Markdown formatting in document:&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;actions&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="nf"&gt;authorize&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;then&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;md2docs&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="k"&gt;catch&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both snippets are nearly identical in functionality, with the only differences being:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;In JS, the &lt;code&gt;DOCS&lt;/code&gt; API client object is local to this block where Python uses a global.&lt;/li&gt;
&lt;li&gt;The last line of JS code runs &lt;code&gt;authorize()&lt;/code&gt; and passes the returned &lt;code&gt;authClient&lt;/code&gt; object to this main &lt;code&gt;md2docs()&lt;/code&gt; function which can catch &amp;amp; log an exception.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Regardless of which language you're using, the recipe of this app is as follows:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Read and parse the Markdown contents&lt;/li&gt;
&lt;li&gt;Create a new, empty Google Doc&lt;/li&gt;
&lt;li&gt;Write the plain text string into the Doc&lt;/li&gt;
&lt;li&gt;Make all the stylization requests in the Doc&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Prerequisites/required setup
&lt;/h2&gt;

&lt;p&gt;When running the scripts, the output follows that exact recipe. However, before you can run anything, take these required steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Create a new project&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://console.cloud.google.com/projectcreate" rel="noopener noreferrer"&gt;from the Cloud/developer console&lt;/a&gt; or with the &lt;code&gt;gcloud projects create . . .&lt;/code&gt; command; alternatively, reuse an existing project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable the Google Docs API&lt;/em&gt;&lt;/strong&gt;. Pick your preferred method of these three common ways to enable APIs:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevConsole manually&lt;/strong&gt; -- Enable the API manually from the DevConsole by following these steps:

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="http://console.developers.google.com" rel="noopener noreferrer"&gt;DevConsole&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Library&lt;/strong&gt; tab in the left-nav; search for "Docs", and enable&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;DevConsole link&lt;/strong&gt; -- You may be new to Google APIs or don't have experience enabling APIs manually in the DevConsole. If this is you...

&lt;ol&gt;
&lt;li&gt;Check out the &lt;a href="https://console.cloud.google.com/apis/library/docs.googleapis.com" rel="noopener noreferrer"&gt;API listing page&lt;/a&gt; to learn more about the API and enable it from there.&lt;/li&gt;
&lt;li&gt;Alternatively, skip the API info and click &lt;a href="http://console.developers.google.com/start/api?id=docs" rel="noopener noreferrer"&gt;this link&lt;/a&gt; for the enable button.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Command-line&lt;/strong&gt; (&lt;code&gt;gcloud&lt;/code&gt;) -- Those who prefer working in a terminal can enable APIs with a single command in the &lt;a href="https://cloud.google.com/shell" rel="noopener noreferrer"&gt;Cloud Shell&lt;/a&gt; or locally on your computer if you &lt;a href="https://cloud.google.com/sdk/install" rel="noopener noreferrer"&gt;installed the Cloud SDK&lt;/a&gt; which includes the &lt;code&gt;gcloud&lt;/code&gt; command-line tool (CLI) and initialized its use.

&lt;ol&gt;
&lt;li&gt;If this is you, issue this command to enable the API: &lt;code&gt;gcloud services enable docs.googleapis.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirm all the APIs that have been enabled (by you or by default) with: &lt;code&gt;gcloud services list&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;em&gt;Create OAuth client ID &amp;amp; secret &lt;a href="https://console.cloud.google.com/apis/credentials" rel="noopener noreferrer"&gt;credentials&lt;/a&gt;&lt;/em&gt;&lt;/strong&gt; and download to your local filesystem as &lt;code&gt;client_secret.json&lt;/code&gt;. The code samples &lt;strong&gt;will not run&lt;/strong&gt; without this file present.&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;&lt;em&gt;Install Google APIs client library&lt;/em&gt;&lt;/strong&gt;:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;NodeJS (16+):&lt;/strong&gt; Install required packages with this command:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;npm i&lt;/code&gt; (uses &lt;code&gt;package.json&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Or install the packages manually: &lt;code&gt;npm i googleapis @google-cloud/local-auth&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python 2 or 3 (&lt;em&gt;new auth&lt;/em&gt;):&lt;/strong&gt; In your normal or &lt;code&gt;virtualenv&lt;/code&gt; environment, run this command if using the current Python auth libraries (most everyone):

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install -r requirements.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Or install the packages manually: &lt;code&gt;pip install -U pip google-api-python-client google-auth-httplib2 google-auth-oauthlib&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Python 2 or 3 (&lt;em&gt;old auth&lt;/em&gt;):&lt;/strong&gt; If you have dependencies on the older Python auth libraries and/or still have old code lying around that do (see earlier sidebar), run this command to ensure you have the latest/last versions of these libraries:

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;pip install -r requirements-old.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Or install the packages manually: &lt;code&gt;pip install -U pip google-api-python-client oauth2client&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;For Python specifically, 2.x means 2.7, and if you're already planning to migrate to 3.x, you should &lt;strong&gt;definitely&lt;/strong&gt; not be using &lt;em&gt;anything&lt;/em&gt; older. For 3.x, it should work for nearly all releases, but 3.9 or newer are recommended.&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;Once you've done all of the above, you're ready to go. Oh, a quick word about costs running this app: &lt;strong&gt;there should be none&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ ALERT: &lt;strong&gt;Cost: "free" up to certain limits&lt;/strong&gt;&lt;br&gt;
While many Google products &amp;amp; APIs are free to use, not all of them are. While not totally "free," use of GWS APIs is covered completely by your monthly "subscription," whether you're a &lt;a href="https://gsuite.google.com/pricing" rel="noopener noreferrer"&gt;paid subscriber&lt;/a&gt; or have a free consumer Google account (&lt;a href="https://accounts.google.com/SignUp" rel="noopener noreferrer"&gt;with&lt;/a&gt; or &lt;a href="https://accounts.google.com/SignUpWithoutGmail" rel="noopener noreferrer"&gt;without&lt;/a&gt; Gmail, which is &lt;a href="https://support.google.com/accounts/answer/27441#existingemail" rel="noopener noreferrer"&gt;optional&lt;/a&gt;), meaning a $0USD monthly subscription rate.&lt;/p&gt;

&lt;p&gt;This "free" usage is not unlimited however... stay within the established quotas for each API. As expected, paid subscribers get more quota than free accounts. While not broadly published, you can get an idea of the limits on the &lt;a href="https://developers.google.com/apps-script/guides/services/quotas" rel="noopener noreferrer"&gt;Quotas page for Google Apps Script&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Running the script
&lt;/h2&gt;

&lt;p&gt;Since we know the "recipe" of how this app operates, running them produce expected results, similar to what you see below, with some slight differences in the objects rendered by each language:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Python&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python md2docs.py
&lt;span class="k"&gt;**&lt;/span&gt; Parsed Markdown file &lt;span class="s1"&gt;'quickbrownfox.md'&lt;/span&gt; &amp;amp; style actions
&lt;span class="k"&gt;**&lt;/span&gt; Created &lt;span class="s1"&gt;'quickbrownfox'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ID: bfENGvI9vCE2cabBCqv3Hq6qvFoL0nfDPfQZeZLY6ubQ&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt; Inserted &lt;span class="s1"&gt;'The quick, brown fox jumped over the lazy dogs.\n'&lt;/span&gt; into document
&lt;span class="k"&gt;**&lt;/span&gt; Completed Markdown formatting &lt;span class="k"&gt;in &lt;/span&gt;document: &lt;span class="o"&gt;[(&lt;/span&gt;&lt;span class="s1"&gt;'italic'&lt;/span&gt;, 4, 17&lt;span class="o"&gt;)&lt;/span&gt;, &lt;span class="o"&gt;(&lt;/span&gt;&lt;span class="s1"&gt;'bold'&lt;/span&gt;, 37, 47&lt;span class="o"&gt;)]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;JavaScript&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;node md2docs.mjs
&lt;span class="k"&gt;**&lt;/span&gt; Parsed Markdown file &lt;span class="s1"&gt;'quickbrownfox.md'&lt;/span&gt; &amp;amp; style actions
&lt;span class="k"&gt;**&lt;/span&gt; Created &lt;span class="s1"&gt;'quickbrownfox'&lt;/span&gt; &lt;span class="o"&gt;(&lt;/span&gt;ID: ATjQg2PqFQxqmiRNIi1TL9gjXBFWV7cw0i4ytGtddt7K&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;**&lt;/span&gt; Inserted &lt;span class="s1"&gt;'The quick, brown fox jumped over the lazy dogs.\n'&lt;/span&gt; into document
&lt;span class="k"&gt;**&lt;/span&gt; Completed Markdown formatting &lt;span class="k"&gt;in &lt;/span&gt;document: &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'italic'&lt;/span&gt;, 4, 17 &lt;span class="o"&gt;]&lt;/span&gt;, &lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="s1"&gt;'bold'&lt;/span&gt;, 37, 47 &lt;span class="o"&gt;]&lt;/span&gt; &lt;span class="o"&gt;]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;If you could freeze the app and open the Google Doc at the exact moment after the plain text is added to the Doc, it would look something like this:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qtqlmwyyer6lp37izec.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7qtqlmwyyer6lp37izec.png" alt="Plain text string inserted into Google Doc" width="770" height="541"&gt;&lt;/a&gt;&lt;/p&gt;
Plain text string inserted into Google Doc



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;However, unless you pause execution, it's an unlikely screenshot because formatting comes quickly thereafter, meaning you'll see the final result instead:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk1lokm8xejpv386kpnu.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdk1lokm8xejpv386kpnu.png" alt="Stylization-Formatted string in Google Docs" width="770" height="541"&gt;&lt;/a&gt;&lt;/p&gt;
Stylization-formatted string in Google Doc



&lt;h2&gt;
  
  
  Summary and next steps
&lt;/h2&gt;

&lt;p&gt;Now you know how to create a crudely-simplistic Markdown parser using regexes, but more importantly, learned how to use the Google Docs API to:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Create a new Google Doc&lt;/li&gt;
&lt;li&gt;Write text to a Google Doc&lt;/li&gt;
&lt;li&gt;Format text in a Google Doc&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;While the sample serves as a way to kickstart a very modest Markdown parser, the main goals are for readers to become familiar with the Docs API, which hasn't been around for &lt;em&gt;that&lt;/em&gt; long (launched in 2017). If you wanted to support more complete Markdown syntax, you'd consider a package like &lt;a href="https://pypi.org/project/markdown2" rel="noopener noreferrer"&gt;&lt;code&gt;markdown2&lt;/code&gt;&lt;/a&gt; for Python or &lt;a href="https://showdownjs.com" rel="noopener noreferrer"&gt;Showdown&lt;/a&gt; for Node.js.&lt;/p&gt;

&lt;p&gt;If you pair the sample script demonstrated in this post and combine it with the sample in the &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;exporting Google Docs as PDF files&lt;/a&gt; post, you'll have a solution that takes auto-generated Markdown files, produces formatted Google Docs, and exports corresponding PDFs for your organization or your customers.&lt;/p&gt;

&lt;p&gt;You could also take things a step &lt;strong&gt;further&lt;/strong&gt;: take valuable corporate data sitting in a massive set of CSV files, &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;import them into Google Sheets&lt;/a&gt;, then &lt;a href="https://dev.to/wescpy/series/27183"&gt;leverage the Gemini API&lt;/a&gt; to produce summaries of the content, write out a collection of Google Docs, then produce PDFs for your boss/management, or perhaps even paying customers.&lt;/p&gt;

&lt;p&gt;Those are just some ideas. The point of this imagination is to inspire you with what's possible, and what kinds of solutions you can build. I'll cover other GWS APIs in future posts. For fun, I'm also going to do a "codegen" (code generation) post where I ask LLMs ("large language models") like ChatGPT &amp;amp; Gemini to generate an app like this, discuss what I get, and compare/contrast.&lt;/p&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;If you found an error in this post, a bug in &lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic I should cover, drop a note in the comments below or file an issue at the repo. I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources related to this post which you may find useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/apps/md2docs" rel="noopener noreferrer"&gt;From &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/apps" rel="noopener noreferrer"&gt;From &lt;em&gt;all&lt;/em&gt; GWS posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;From &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Docs API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/docs" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/docs/api/auth" rel="noopener noreferrer"&gt;OAuth scopes&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/docs/api/quickstart/python" rel="noopener noreferrer"&gt;Python QuickStart&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/docs/api/quickstart/js" rel="noopener noreferrer"&gt;Javascript (client-side/front-end)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/docs/api/quickstart/nodejs" rel="noopener noreferrer"&gt;JS/Node.js (server-side/back-end)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GWS APIs &amp;amp; OAuth2 information
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;All GWS APIs &lt;a href="http://developers.google.com/gsuite" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/guides/auth-overview" rel="noopener noreferrer"&gt;GWS auth overview page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/workspace/guides/configure-oauth-consent" rel="noopener noreferrer"&gt;OAuth consent screen &amp;amp; scope info&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;OAuth2 &lt;a href="https://developers.google.com/identity/protocols/OAuth2" rel="noopener noreferrer"&gt;documentation&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google APIs client libraries
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library/python" rel="noopener noreferrer"&gt;Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/api-client-library/javascript" rel="noopener noreferrer"&gt;Javascript (client-side/front-end)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/google-api-nodejs-client" rel="noopener noreferrer"&gt;JS/Node.js (server-side/back-end)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GWS APIs specific use cases

&lt;ul&gt;
&lt;li&gt;Mail merge with the Google Docs API &lt;a href="http://goo.gle/2HZ8K6R" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exporting Google Docs as PDF &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Importing CSV files into Google Sheets &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;post&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs intro content &lt;small&gt;(featuring Drive API)&lt;/small&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://g.co/codelabs/gsuite-apis-intro" rel="noopener noreferrer"&gt;Codelab&lt;/a&gt; (hands-on tutorial)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/gsuite-apis-intro" rel="noopener noreferrer"&gt;Repo&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="http://goo.gl/ZIgf8k" rel="noopener noreferrer"&gt;Video&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs general

&lt;ul&gt;
&lt;li&gt;Using OAuth Client IDs &amp;amp; GWS APIs &lt;a href="https://dev.to/wescpy/series/25403"&gt;3-part post series&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GWS/G Suite developer overview &lt;a href="http://t.co/XdKEWus0KI" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Accessing GWS/G Suite REST APIs &lt;a href="https://goo.gle/3ateIIQ" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt; (open to all but originally for students)&lt;/li&gt;
&lt;li&gt;Power your apps with Gmail, Drive, Docs, Sheets, Slides (G Suite/GWS comprehensive developer overview) &lt;a href="http://youtu.be/kkp0aNGlynw" rel="noopener noreferrer"&gt;video&lt;/a&gt; (LONG)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;GWS APIs video series

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/kFMUa6" rel="noopener noreferrer"&gt;Launchpad Online&lt;/a&gt; (GWS &amp;amp; &lt;em&gt;other&lt;/em&gt; Google APIs)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/JpBQ40" rel="noopener noreferrer"&gt;GWS/G Suite Dev Show&lt;/a&gt; (only GWS APIs)&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;Google APIs general

&lt;ul&gt;
&lt;li&gt;Getting started with Google APIs &lt;a href="http://developers.googleblog.com/2014/11/launchpad-online-for-developers-getting.html" rel="noopener noreferrer"&gt;post &amp;amp; video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Python authorization boilerplate code review &lt;a href="http://goo.gl/KMfbeK" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;a href="https://stackoverflow.com/a/38406284/305689" rel="noopener noreferrer"&gt;Import &amp;amp; export MIMEtypes&lt;/a&gt; (StackOverflow)&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I was a member of various GWS product teams ~2013-2018. While product information is as accurate as I can find or recall, the opinions are my own.&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>google</category>
      <category>webdev</category>
      <category>api</category>
    </item>
    <item>
      <title>Guide to modern app-hosting without servers on Google Cloud (Run)</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Tue, 14 Jan 2025 02:20:47 +0000</pubDate>
      <link>https://dev.to/gde/guide-to-modern-app-hosting-without-servers-on-google-cloud-37n8</link>
      <guid>https://dev.to/gde/guide-to-modern-app-hosting-without-servers-on-google-cloud-37n8</guid>
      <description>&lt;h2&gt;
  
  
  An in-depth intro to deploying apps on Cloud Run #serverless... TL;DR:
&lt;/h2&gt;

&lt;p&gt;If you're looking to put code online today, you have many options, with virtual machines (VMs) or Kubernetes as the best choice for always-active apps with constant load. Apps with spiky, viral, unpredictable, and even &lt;strong&gt;no&lt;/strong&gt; traffic &lt;a href="https://dev.to/wescpy/a-broader-perspective-of-serverless-1md1"&gt;tend to do better on &lt;em&gt;serverless&lt;/em&gt; platforms&lt;/a&gt;, those where you don't need to know about nor manage servers. As far as app-hosting goes, Google Cloud (GCP) has App Engine and Cloud Run. &lt;a href="https://dev.to/wescpy/hosting-apps-in-the-cloud-with-google-app-engine-3fn"&gt;App Engine is introduced elsewhere&lt;/a&gt;; this post explores its modern, contemporary sibling, Cloud Run.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" alt="Serverless computing with Google" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] Serverless computing with Google



&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog focused on showing Python &amp;amp; Node.js developers how to integrate with Google technologies through its APIs and platforms. Here, you'll find content on GCP (&lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;, Workspace (GWS; &lt;a href="https://dev.to/googleworkspace/export-google-docs-as-pdf-without-the-docs-api-9o4"&gt;Docs/Drive&lt;/a&gt;, &lt;a href="https://dev.to/googleworkspace/import-csv-to-google-sheets-without-the-sheets-api-20g1"&gt;Sheets/Drive&lt;/a&gt;), &lt;a href="https://dev.to/wescpy/series/29655"&gt;Maps&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;, or &lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;, not to mention credentials basics like &lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt; and &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt; (primarily to access GWS APIs).&lt;/p&gt;

&lt;p&gt;Before diving into this long-ish post, stop if you have apps getting constant traffic with predictable loads, because those app-hosting workloads generally do better with VMs on Kubernetes. Serverless tends to be a better fit for apps with unpredictable traffic such as social media, gaming, mobile backend services, startups/go-to-market solutions, student coursework or capstone projects, university research, enterprise intranet sites, and new apps or prototypes. If your apps are in the latter group, keep reading.&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Cloud Run
&lt;/h2&gt;

&lt;p&gt;If &lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (GAE) is the "OG" serverless platform, &lt;a href="http://cloud.run" rel="noopener noreferrer"&gt;Cloud Run&lt;/a&gt; (GCR) is its logical successor, crafted for today's modern app-hosting needs. GAE was the 1st generation of Google serverless platforms. It has since been joined, about a decade later, by 2nd generation services, GCR and Cloud Functions (GCF). GCF is somewhat out-of-scope for this post so I'll cover that another time.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 &lt;strong&gt;Google Cloud Functions is now Cloud Run functions&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The 2nd generation GCF product was &lt;a href="https://cloud.google.com/blog/products/serverless/google-cloud-functions-is-now-cloud-run-functions" rel="noopener noreferrer"&gt;rebranded as Cloud Run functions&lt;/a&gt; in 2024, so expect to see this change if you start digging around for GVF documentation.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;There is &lt;a href="https://dev.to/wescpy/hosting-apps-in-the-cloud-with-google-app-engine-3fn"&gt;another post dedicated to introducing GAE&lt;/a&gt;, demonstrating how to deploy a &lt;a href="https://github.com/wescpy/google/tree/main/cloud/appengine" rel="noopener noreferrer"&gt;"Hello World!" sample app&lt;/a&gt; to that platform, so check it out if interested. In &lt;em&gt;this&lt;/em&gt; post, we're going to deploy the same app (but) to GCR, and the cumulative knowledge gives you the ability to deploy apps to &lt;em&gt;both&lt;/em&gt; platforms.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ ALERT: &lt;strong&gt;Cost: billing required (but "free?!?")&lt;/strong&gt;&lt;br&gt;
While many Google products are free to use, GCP products are not. In order to run the sample apps, you must &lt;a href="http://console.cloud.google.com/billing" rel="noopener noreferrer"&gt;enable billing&lt;/a&gt; and a billing account backed by a financial instrument like a &lt;a href="https://cloud.google.com/appengine/docs/standard/payment-instrument" rel="noopener noreferrer"&gt;credit card&lt;/a&gt; (&lt;a href="https://support.google.com/paymentscenter/answer/9001356#allowed-methods" rel="noopener noreferrer"&gt;payment method depends on region/currency&lt;/a&gt;). If you're new to GCP, review the &lt;a href="https://cloud.google.com/billing/docs/onboarding-checklist" rel="noopener noreferrer"&gt;billing &amp;amp; onboarding guide&lt;/a&gt;. That said, deploying and running the sample app(s) in this post &lt;strong&gt;should not incur any cost&lt;/strong&gt; because basic usage falls under the free tiers described below along with other important notes about billing:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Several GCP products (like GCR) have an &lt;a href="https://cloud.google.com/free/docs/gcp-free-tier#free-tier-usage-limits" rel="noopener noreferrer"&gt;"Always Free" tier&lt;/a&gt;, a free daily or monthly usage quota before incurring charges. See the GCR &lt;a href="https://cloud.google.com/run/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; and &lt;a href="https://cloud.google.com/run/quotas" rel="noopener noreferrer"&gt;quotas&lt;/a&gt; pages for more information. Furthermore, deploying to GCP serverless platforms incur &lt;a href="https://cloud.google.com/run/pricing#source-functions" rel="noopener noreferrer"&gt;minor build and storage costs&lt;/a&gt;. (Also see &lt;a href="https://cloud.google.com/appengine/pricing#pricing-for-related-google-cloud-trusted-cloud-by-s3ns-t-systems-sovereign-cloud-products" rel="noopener noreferrer"&gt;similar content in the GAE docs&lt;/a&gt;.)&lt;/li&gt;
&lt;li&gt;The &lt;a href="https://cloud.google.com/build/pricing" rel="noopener noreferrer"&gt;Cloud Build&lt;/a&gt; system has its own free quota as does &lt;a href="https://cloud.google.com/storage/pricing#cloud-storage-always-free" rel="noopener noreferrer"&gt;Cloud Storage&lt;/a&gt; (GCS), used to store build artifacts and built container images. The images themselves are also sent to the &lt;a href="https://cloud.google.com/artifact-registry/pricing" rel="noopener noreferrer"&gt;Cloud Artifact Registry&lt;/a&gt; (CAR) making them accessible to other GCP services. They eat into GCS &amp;amp; CAR (storage) quotas as does &lt;a href="https://cloud.google.com/artifact-registry/pricing#data-transfer" rel="noopener noreferrer"&gt;transferring images&lt;/a&gt; between services &amp;amp; regions. You may be in a region that does &lt;strong&gt;not&lt;/strong&gt; have a free tier however, so monitor your usage to minimize any costs.&lt;/li&gt;
&lt;li&gt;Use the &lt;a href="https://cloud.google.com/products/calculator" rel="noopener noreferrer"&gt;cost calculator&lt;/a&gt; to &lt;a href="https://cloud.google.com/billing/docs/how-to/estimate-costs" rel="noopener noreferrer"&gt;get monthly estimates&lt;/a&gt;. You may qualify for credits to offset GCP costs: If you are a startup, consider the &lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;GCP for Startups&lt;/a&gt; program grants. If you are in education, check out the &lt;a href="https://cloud.google.com/edu" rel="noopener noreferrer"&gt;GCP education programs&lt;/a&gt; for students, faculty, and researchers.&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Deploying apps to GAE and GCR are similar, and the only differences lie with the configuration files. Both Python &amp;amp; Node.js apps can be found in &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun" rel="noopener noreferrer"&gt;this repo&lt;/a&gt;. I'll walk through each app, run it locally, then deploy to GCR after some setup, starting with Python.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python" rel="noopener noreferrer"&gt;Python repo&lt;/a&gt; features the main app source (&lt;code&gt;main.py&lt;/code&gt;), a Python config file (&lt;code&gt;requirements.txt&lt;/code&gt;), and the rest, config files for GCR/GCP:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Info&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/main.py" rel="noopener noreferrer"&gt;&lt;code&gt;main.py&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(n/a)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Main Python application file&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/requirements.txt" rel="noopener noreferrer"&gt;&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pip.pypa.io/en/stable/reference/requirements-file-format" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/build/concepts/dockerfile" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Docker instructions on how to build container and start application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/Procfile" rel="noopener noreferrer"&gt;&lt;code&gt;Procfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://devcenter.heroku.com/articles/procfile" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Instructions on how to start application (without Docker)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/.dockerignore" rel="noopener noreferrer"&gt;&lt;code&gt;.dockerignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/build-cloud/optimization/#dockerignore-files" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Filter files that should not go into container image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/.gcloudignore" rel="noopener noreferrer"&gt;&lt;code&gt;.gcloudignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Filter files that should not be uploaded to GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] GCR Python repo files



&lt;p&gt; &lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 &lt;strong&gt;Sample app Python 2 &amp;amp; 3 compatible&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;The sample app is Python 2 &amp;amp; 3 compatible. While &lt;a href="http://python.org/doc/sunset-python-2" rel="noopener noreferrer"&gt;Python 2 has been sunset&lt;/a&gt; by the community, many users have dependencies that haven't migrated to Python 3 (yet) or have apps they can't migrate for other reasons. GCR is one of the few remaining ways to deploy 2.x apps. The sample defaults to 3.x but contains a commented out directive to use a 2.x base image.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Application files
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;requirements.txt&lt;/code&gt; file is to specify 3rd-party libraries. The only package listed is the Flask micro web framework, just like the &lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/python3/requirements.txt" rel="noopener noreferrer"&gt;GAE app's&lt;code&gt;requirements.txt&lt;/code&gt;&lt;/a&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="c1"&gt;#gunicorn&lt;/span&gt;
&lt;span class="s"&gt;flask&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;However, here and commented out but &lt;em&gt;not&lt;/em&gt; available in the GAE app's &lt;code&gt;requirements.txt&lt;/code&gt;, is &lt;a href="https://gunicorn.org" rel="noopener noreferrer"&gt;Green Unicorn&lt;/a&gt; (&lt;code&gt;gunicorn&lt;/code&gt;), an HTTP web server. GAE is pure PaaS (Platform-as-a-Service), but GCR, along with any container-based cloud service, is one step down, in-between PaaS and IaaS (Infrastructure-as-a-Service).&lt;/p&gt;

&lt;p&gt;As a lower-level service, GCR doesn't provide a web server like GAE does... you have to bring your own. For the purposes of the demo, the Flask development server suffices, but Gunicorn can be easily selected once &lt;em&gt;your&lt;/em&gt; app makes its way towards production.&lt;/p&gt;

&lt;p&gt;Now let's look at &lt;code&gt;main.py&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# local-only
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's line-by-line identical to the &lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/python3/main.py" rel="noopener noreferrer"&gt;GAE &lt;code&gt;main.py&lt;/code&gt;&lt;/a&gt;: The Flask library is imported, and the Flask app is instantiated. The only route is &lt;code&gt;/&lt;/code&gt;, returning &lt;code&gt;'Hello World!'&lt;/code&gt; for &lt;code&gt;GET&lt;/code&gt; requests. The last few lines run the Flask development server in debug mode on port 8080 if the &lt;code&gt;PORT&lt;/code&gt; environment variable isn't set. It's only started when running &lt;code&gt;main.py&lt;/code&gt; as a script (per the &lt;code&gt;if&lt;/code&gt; block). (When starting a real server, this entire block is ignored.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Running locally with Flask (optional)
&lt;/h4&gt;

&lt;p&gt;It's a good idea to test apps locally before deploying to the cloud, so let's install any required packages. In your regular or &lt;a href="https://docs.python.org/tutorial/venv" rel="noopener noreferrer"&gt;virtual environment&lt;/a&gt; (&lt;code&gt;virtualenv&lt;/code&gt;), execute: &lt;code&gt;pip install -r requirements.txt&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt; if you also have Python 2 installed). Fresh installs result in output like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;pip3 &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
Collecting flask &lt;span class="o"&gt;(&lt;/span&gt;from &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt &lt;span class="o"&gt;(&lt;/span&gt;line 2&lt;span class="o"&gt;))&lt;/span&gt;
  Downloading flask-3.1.0-py3-none-any.whl.metadata &lt;span class="o"&gt;(&lt;/span&gt;2.7 kB&lt;span class="o"&gt;)&lt;/span&gt;
Collecting Werkzeug&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;3.1 &lt;span class="o"&gt;(&lt;/span&gt;from flask-&amp;gt;-r requirements.txt &lt;span class="o"&gt;(&lt;/span&gt;line 2&lt;span class="o"&gt;))&lt;/span&gt;
  Downloading werkzeug-3.1.3-py3-none-any.whl.metadata &lt;span class="o"&gt;(&lt;/span&gt;3.7 kB&lt;span class="o"&gt;)&lt;/span&gt;
Collecting Jinja2&amp;gt;&lt;span class="o"&gt;=&lt;/span&gt;3.1.2 &lt;span class="o"&gt;(&lt;/span&gt;from flask-&amp;gt;-r requirements.txt &lt;span class="o"&gt;(&lt;/span&gt;line 2&lt;span class="o"&gt;))&lt;/span&gt;
  Downloading jinja2-3.1.5-py3-none-any.whl.metadata &lt;span class="o"&gt;(&lt;/span&gt;2.6 kB&lt;span class="o"&gt;)&lt;/span&gt;
&lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
Downloading jinja2-3.1.5-py3-none-any.whl &lt;span class="o"&gt;(&lt;/span&gt;134 kB&lt;span class="o"&gt;)&lt;/span&gt;
Downloading werkzeug-3.1.3-py3-none-any.whl &lt;span class="o"&gt;(&lt;/span&gt;224 kB&lt;span class="o"&gt;)&lt;/span&gt;
Downloading MarkupSafe-3.0.2-cp313-cp313-macosx_10_13_universal2.whl &lt;span class="o"&gt;(&lt;/span&gt;14 kB&lt;span class="o"&gt;)&lt;/span&gt;
Installing collected packages: MarkupSafe, itsdangerous, click, blinker, Werkzeug, Jinja2, flask
Successfully installed Jinja2-3.1.5 MarkupSafe-3.0.2 Werkzeug-3.1.3 blinker-1.9.0 click-8.1.8 flask-3.1.0 itsdangerous-2.2.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With dependencies installed, start the Flask "devserver" with &lt;code&gt;python main.py&lt;/code&gt; (or &lt;code&gt;python3&lt;/code&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;python3 main.py
 &lt;span class="k"&gt;*&lt;/span&gt; Serving Flask app &lt;span class="s1"&gt;'main'&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Debug mode: on
WARNING: This is a development server. Do not use it &lt;span class="k"&gt;in &lt;/span&gt;a production deployment. Use a production WSGI server instead.
 &lt;span class="k"&gt;*&lt;/span&gt; Running on all addresses &lt;span class="o"&gt;(&lt;/span&gt;0.0.0.0&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Running on http://127.0.0.1:8080
 &lt;span class="k"&gt;*&lt;/span&gt; Running on http://192.168.1.147:8080
Press CTRL+C to quit
 &lt;span class="k"&gt;*&lt;/span&gt; Restarting with watchdog &lt;span class="o"&gt;(&lt;/span&gt;fsevents&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Debugger is active!
 &lt;span class="k"&gt;*&lt;/span&gt; Debugger PIN: 872-970-164
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a running server, point a web browser to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4mq36vwnu2wc2y20zv7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fs4mq36vwnu2wc2y20zv7.png" alt="" width="800" height="492"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] "Hello World!" sample app running locally



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In your terminal, you'll see log entries for each HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;127.0.0.1 - - &lt;span class="o"&gt;[&lt;/span&gt;03/Jan/2025 22:34:45] &lt;span class="s2"&gt;"GET / HTTP/1.1"&lt;/span&gt; 200 -
127.0.0.1 - - &lt;span class="o"&gt;[&lt;/span&gt;03/Jan/2025 22:34:45] &lt;span class="s2"&gt;"GET /favicon.ico HTTP/1.1"&lt;/span&gt; 404 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To exit the devserver, issue a &lt;code&gt;^C&lt;/code&gt; (Control-C) on the command-line. In the next section, we'll look at another way to run the app locally.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remaining configuration file(s)
&lt;/h3&gt;

&lt;p&gt;The other config files specify how the app should be containerized, started, and deployed to the cloud. That's the reason why &lt;em&gt;none&lt;/em&gt; of them were used to run the app locally just a moment ago. (There is &lt;em&gt;another&lt;/em&gt; way to run it locally, with the help of &lt;a href="https://docker.com" rel="noopener noreferrer"&gt;Docker&lt;/a&gt;, and we'll take a look at that shortly.) The &lt;code&gt;.*ignore&lt;/code&gt; files for this app filter out content that doesn't have anything to do with an app's functionality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;.dockerignore&lt;/code&gt; -- keeps out files that shouldn't go into a container image&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;.gcloudignore&lt;/code&gt; -- filters out files that do not need to be deployed to GCP&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;.*ignore&lt;/code&gt; files have much in common; the main difference is that package &amp;amp; build files may be needed to build the container but don't need to be deployed to the cloud because they don't play a part in &lt;em&gt;running&lt;/em&gt; the app.&lt;/p&gt;

&lt;p&gt;&lt;em&gt;The last pair of config files, &lt;code&gt;Dockerfile&lt;/code&gt; and &lt;code&gt;Procfile&lt;/code&gt;, are the most important&lt;/em&gt;: &lt;strong&gt;One is required&lt;/strong&gt; to deploy this app to GCR, and how your containerized app should be built determines which you choose. Let's talk about your options, starting with the &lt;code&gt;Dockerfile&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Using Docker
&lt;/h4&gt;

&lt;p&gt;This is the Python app's &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/python/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; python:3-slim&lt;/span&gt;
&lt;span class="c"&gt;#FROM python:2-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; requirements.txt .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;pip &lt;span class="nb"&gt;install&lt;/span&gt; &lt;span class="nt"&gt;-r&lt;/span&gt; requirements.txt
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["python", "main.py"]&lt;/span&gt;
&lt;span class="c"&gt;#ENTRYPOINT exec gunicorn -b :$PORT -w 2 main:app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These instructions tell Docker how to build the container image and how to start the app within. Its contents are described in this table:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Directive&lt;/th&gt;
&lt;th&gt;Explanation&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;FROM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base image to build your container from, a minimal Python 3 installation&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;FROM&lt;/code&gt; (commented out)&lt;/td&gt;
&lt;td&gt;Use minimal Python 2 base image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;WORKDIR&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Work directory for files going into your container&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copies 3rd-party package file, &lt;code&gt;requirements.txt&lt;/code&gt; into work directory&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;RUN&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Executes &lt;code&gt;pip install&lt;/code&gt; command to install 3rd-party packages listed in &lt;code&gt;requirements.txt&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;COPY&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Copies all files into the work directory (including &lt;code&gt;requirements.txt&lt;/code&gt; again)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;ENTRYPOINT&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Starts app via Flask devserver&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;
&lt;code&gt;ENTRYPOINT&lt;/code&gt; (commented out)&lt;/td&gt;
&lt;td&gt;Starts app via Gunicorn server&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] Python app `Dockerfile` directives



&lt;p&gt; &lt;/p&gt;

&lt;h4&gt;
  
  
  Running locally with Docker (optional)
&lt;/h4&gt;

&lt;p&gt;With Docker, you can build a container locally and run it, providing an alternative way to test locally before any cloud deployment. It also better simulates how GCR will run your app as well. Ignore the other configuration file, &lt;code&gt;Procfile&lt;/code&gt;, for now. While it usually doesn't conflict with the &lt;code&gt;Dockerfile&lt;/code&gt;, there are some situations in which a conflict may arise. To avoid this, temporarily rename, delete, or move &lt;code&gt;Procfile&lt;/code&gt; elsewhere. With just the &lt;code&gt;Dockerfile&lt;/code&gt; present, run these commands to build the container and run the app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;docker build &lt;span class="nt"&gt;-qt&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8080 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;build&lt;/code&gt; command is as advertised: it &lt;em&gt;builds&lt;/em&gt; your container image, naming it &lt;code&gt;test&lt;/code&gt; (&lt;code&gt;-t&lt;/code&gt; "tag" option) and outputs the unique hash for your container. Drop the optional &lt;code&gt;-q&lt;/code&gt; ("quiet") flag to see all kinds of Docker output when building the image.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;run&lt;/code&gt; command &lt;em&gt;runs&lt;/em&gt; the named container (via &lt;code&gt;-t&lt;/code&gt;) and redirects the Flask devserver output to your terminal. It maps requests from port 8000 of your development machine to 8080 in the container (via &lt;code&gt;-p&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;Execute both commands as listed and expect output like below, including the Flask request logs like we saw earlier. Be sure to hit the app at &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt; -- that's port &lt;code&gt;8000&lt;/code&gt;, not the Flask devserver's &lt;code&gt;8080&lt;/code&gt; like when running Flask directly earlier:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-qt&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;
sha256:67b5062c93552271f0d334e3b7e8ef9a83244438cfdbe7a56b117a5d50da70f8

&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8080 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Serving Flask app &lt;span class="s1"&gt;'main'&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Debug mode: on
WARNING: This is a development server. Do not use it &lt;span class="k"&gt;in &lt;/span&gt;a production deployment. Use a production WSGI server instead.
 &lt;span class="k"&gt;*&lt;/span&gt; Running on all addresses &lt;span class="o"&gt;(&lt;/span&gt;0.0.0.0&lt;span class="o"&gt;)&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Running on http://127.0.0.1:8080
 &lt;span class="k"&gt;*&lt;/span&gt; Running on http://172.17.0.2:8080
Press CTRL+C to quit
 &lt;span class="k"&gt;*&lt;/span&gt; Restarting with &lt;span class="nb"&gt;stat&lt;/span&gt;
 &lt;span class="k"&gt;*&lt;/span&gt; Debugger is active!
 &lt;span class="k"&gt;*&lt;/span&gt; Debugger PIN: 257-974-169
172.17.0.1 - - &lt;span class="o"&gt;[&lt;/span&gt;02/Jan/2025 08:26:25] &lt;span class="s2"&gt;"GET / HTTP/1.1"&lt;/span&gt; 200 -
172.17.0.1 - - &lt;span class="o"&gt;[&lt;/span&gt;02/Jan/2025 08:26:26] &lt;span class="s2"&gt;"GET /favicon.ico HTTP/1.1"&lt;/span&gt; 404 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Use &lt;code&gt;^C&lt;/code&gt; (Control-C) to quit the devserver as before, exiting the container. Now you know &lt;em&gt;two&lt;/em&gt; different ways to run your app locally. Now let's move beyond Docker &amp;amp; &lt;code&gt;Dockerfile&lt;/code&gt; and look at the &lt;code&gt;Procfile&lt;/code&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  NOT using Docker
&lt;/h4&gt;

&lt;p&gt;The other way to run a containerized app on GCR is to &lt;strong&gt;not&lt;/strong&gt; use Docker explicitly. If the &lt;code&gt;Dockerfile&lt;/code&gt; is missing, Cloud Build uses &lt;a href="https://buildpacks.io" rel="noopener noreferrer"&gt;Buildpacks&lt;/a&gt;, a tool built on open standards that automatically inspects application files and dynamically determines the best way to build &amp;amp; containerize apps. This is clearly the option for those new to Docker, don't know Docker, want to avoid Docker, or don't even &lt;em&gt;think&lt;/em&gt; about containers. More specifically, &lt;a href="https://cloud.google.com/docs/buildpacks" rel="noopener noreferrer"&gt;Buildpacks adopted by Google&lt;/a&gt; is how images can be built &amp;amp; deployed on GCR without Docker.&lt;/p&gt;

&lt;p&gt;While you don't have to know anything about Docker when using Buildpacks, having a &lt;code&gt;.dockerignore&lt;/code&gt; file is still useful in terms of filtering out files that make building a container inefficient, and most Buildpacks implementations honor the ignore file. The one caveat for Python apps is that without a &lt;code&gt;Dockerfile&lt;/code&gt;'s &lt;code&gt;ENTRYPOINT&lt;/code&gt; directive, it doesn't know how to start your app, and that's why the &lt;code&gt;Procfile&lt;/code&gt; is required when not using Docker.&lt;/p&gt;

&lt;p&gt;To opt for Buildpacks instead of Docker, move, rename, or remove the &lt;code&gt;Dockerfile&lt;/code&gt;. Look inside the &lt;code&gt;Procfile&lt;/code&gt; to find the equivalent of &lt;code&gt;Dockerfile&lt;/code&gt;'s &lt;code&gt;ENTRYPOINT&lt;/code&gt; directive describing how to start your app:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;web&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python main.py&lt;/span&gt;
&lt;span class="c1"&gt;#web: gunicorn -b :$PORT -w 2 main:app&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like the &lt;code&gt;Dockerfile&lt;/code&gt;, there's a 2nd, commented-out alternative &lt;code&gt;ENTRYPOINT&lt;/code&gt; for using Gunicorn instead of the Flask devserver.&lt;/p&gt;

&lt;p&gt;You can install the &lt;a href="https://github.com/buildpacks/pack" rel="noopener noreferrer"&gt;Buildpacks &lt;code&gt;pack&lt;/code&gt; tool&lt;/a&gt; to create a containerized app image, but without Docker, you can't run it locally. So for this app, your only local testing option is the Flask devserver.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python app summary
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Python users have a choice for testing apps locally: 1) use Flask devserver, or 2) build a container &amp;amp; run it with Docker.&lt;/li&gt;
&lt;li&gt;To deploy this app to GCR, choose between using Docker (&lt;code&gt;Dockerfile&lt;/code&gt;) and &lt;strong&gt;not&lt;/strong&gt; using Docker (&lt;code&gt;Procfile&lt;/code&gt;) (remember to move/remove the other config). Either way, keep the &lt;code&gt;.dockerignore&lt;/code&gt; file to minimize container size and image build time.&lt;/li&gt;
&lt;li&gt;When ready to go live, switch from the Flask devserver to something more production-worthy, such as Gunicorn, &lt;a href="https://fullstackpython.com/uwsgi.html" rel="noopener noreferrer"&gt;uWSGI&lt;/a&gt;, &lt;a href="https://nginx.org" rel="noopener noreferrer"&gt;nginx&lt;/a&gt;, or &lt;a href="https://uvicorn.org" rel="noopener noreferrer"&gt;uvicorn&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Node.js
&lt;/h2&gt;

&lt;p&gt;Node developers, it's your turn. Here's what's in &lt;em&gt;this&lt;/em&gt; &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs" rel="noopener noreferrer"&gt;repo&lt;/a&gt;:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;File&lt;/th&gt;
&lt;th&gt;Info&lt;/th&gt;
&lt;th&gt;Purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/index.js" rel="noopener noreferrer"&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(n/a)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Main Node app file (CommonJS)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/index.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;index.mjs&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(n/a)&lt;/em&gt;&lt;/td&gt;
&lt;td&gt;Main Node app file (ESmodule)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://pip.pypa.io/en/stable/reference/requirements-file-format" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;3rd-party packages requirements&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/Dockerfile" rel="noopener noreferrer"&gt;&lt;code&gt;Dockerfile&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/build/concepts/dockerfile" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Docker instructions on how to build container and start application&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/.dockerignore" rel="noopener noreferrer"&gt;&lt;code&gt;.dockerignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://docs.docker.com/build-cloud/optimization/#dockerignore-files" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Filter files that should not go into container image&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/.gcloudignore" rel="noopener noreferrer"&gt;&lt;code&gt;.gcloudignore&lt;/code&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/sdk/gcloud/reference/topic/gcloudignore" rel="noopener noreferrer"&gt;info&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Filter files that should not be uploaded to GCP&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] GCR Node repo files



&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  Application files
&lt;/h3&gt;

&lt;p&gt;Python has Flask, and similarly, Node has the Express micro web framework, the only 3rd-party package found in Node's 3rd-party packages requirements file, &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/package.json" rel="noopener noreferrer"&gt;&lt;code&gt;package.json&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"helloworld-nodejs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node.js Cloud Run sample app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.mjs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index.mjs"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"CyberWeb Consulting LLC"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Apache-2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.17.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The app can be found in &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/index.mjs" rel="noopener noreferrer"&gt;&lt;code&gt;index.mjs&lt;/code&gt;&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There's also a CommonJS version, &lt;a href="https://github.com/wescpy/google/blob/main/cloud/cloudrun/nodejs/index.js" rel="noopener noreferrer"&gt;&lt;code&gt;index.js&lt;/code&gt;&lt;/a&gt; for those who prefer it. With either version, app instantiation is followed by the &lt;code&gt;GET&lt;/code&gt; handler returning "Hello World!". The rest sets up the server on the designated &lt;code&gt;PORT&lt;/code&gt; and exports the &lt;code&gt;app&lt;/code&gt;. Unlike Flask, the Express server is much more performant and &lt;em&gt;can&lt;/em&gt; be used in production, perhaps with the help of tools like &lt;a href="https://nodejs.org/dist/latest/docs/api/cluster.html" rel="noopener noreferrer"&gt;&lt;code&gt;cluster&lt;/code&gt;&lt;/a&gt; and &lt;a href="https://pm2.keymetrics.io" rel="noopener noreferrer"&gt;PM2&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running locally with Express.js (optional)
&lt;/h4&gt;

&lt;p&gt;As mentioned earlier, while running locally is optional, it's a good practice. Install the required packages first:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm i

added 69 packages, and audited 70 packages &lt;span class="k"&gt;in &lt;/span&gt;2s

found 0 vulnerabilities
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;You'll see the expected &lt;code&gt;node_modules&lt;/code&gt; folder and &lt;code&gt;package-lock.json&lt;/code&gt; file. Now launch it with &lt;code&gt;npm start&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;npm start

&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; helloworld-nodejs@0.0.1 start
&lt;span class="o"&gt;&amp;gt;&lt;/span&gt; node index.mjs

Listening on port 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Express doesn't log HTTP requests -- developers have to add middleware to them, so don't expect request output like Flask. What &lt;strong&gt;is&lt;/strong&gt; the same is the "Hello World!" you see in your browser when you hit the app at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;. Exit the server with &lt;code&gt;^C&lt;/code&gt; (Control-C).&lt;/p&gt;

&lt;h3&gt;
  
  
  Remaining configuration file(s)
&lt;/h3&gt;

&lt;p&gt;The remaining config files are: &lt;code&gt;Dockerfile&lt;/code&gt;, &lt;code&gt;.dockerignore&lt;/code&gt;, and &lt;code&gt;.gcloudignore&lt;/code&gt;, identical in functionality to their Python cousins. There's no &lt;code&gt;Procfile&lt;/code&gt; because &lt;code&gt;package.json&lt;/code&gt; already contains app startup (&lt;code&gt;npm start&lt;/code&gt;) instructions.&lt;/p&gt;

&lt;h4&gt;
  
  
  Running locally with Docker (optional)
&lt;/h4&gt;

&lt;p&gt;You can also build a container and run the app locally with Docker and the Node &lt;code&gt;Dockerfile&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight docker"&gt;&lt;code&gt;&lt;span class="k"&gt;FROM&lt;/span&gt;&lt;span class="s"&gt; node:22-slim&lt;/span&gt;
&lt;span class="k"&gt;WORKDIR&lt;/span&gt;&lt;span class="s"&gt; /app&lt;/span&gt;
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; package.json .&lt;/span&gt;
&lt;span class="k"&gt;RUN &lt;/span&gt;npm i
&lt;span class="k"&gt;COPY&lt;/span&gt;&lt;span class="s"&gt; . .&lt;/span&gt;
&lt;span class="k"&gt;ENTRYPOINT&lt;/span&gt;&lt;span class="s"&gt; ["node", "index.mjs"]&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It's basically equivalent to the Python version. The &lt;code&gt;docker&lt;/code&gt; command-line instructions to build and run the container are also the same: &lt;code&gt;docker build -qt test .&lt;/code&gt; and &lt;code&gt;docker run -p 8000:8080 -t test&lt;/code&gt;.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;docker build &lt;span class="nt"&gt;-qt&lt;/span&gt; &lt;span class="nb"&gt;test&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt;

sha256:83741dbff139d5bdf09f28cde681e4455828102d75064f99c95708d37d8735e3

&lt;span class="nv"&gt;$ &lt;/span&gt;docker run &lt;span class="nt"&gt;-p&lt;/span&gt; 8000:8080 &lt;span class="nt"&gt;-t&lt;/span&gt; &lt;span class="nb"&gt;test
&lt;/span&gt;Listening on port 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After running both commands and starting your container, hit the server at &lt;a href="http://localhost:8000" rel="noopener noreferrer"&gt;http://localhost:8000&lt;/a&gt;. Again, don't expect any Express output on requests. Press ^C to exit as before.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying apps to Cloud Run
&lt;/h2&gt;

&lt;p&gt;Whether via the server or through Docker, you're in good shape to deploy to the cloud if you ran your app locally. The next step is to deploy it to the cloud so that it's accessible globally... let's start with some GCP setup.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;Container runtime contract&lt;/strong&gt;&lt;br&gt;
There are some restrictions your app must adhere to in order for GCR to run your container successfully; the most important pair:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Your app must be &lt;strong&gt;stateless&lt;/strong&gt;. Don't use embedded databases. When your users hit your app again, they may be reaching another instance in a completely different state. Persist data in cloud-based storage like GCS, &lt;a href="https://cloud.google.com/sql" rel="noopener noreferrer"&gt;Cloud SQL&lt;/a&gt;, or &lt;a href="https://cloud.google.com/firestore" rel="noopener noreferrer"&gt;Cloud Firestore&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Leave a port, say &lt;code&gt;8080&lt;/code&gt; or &lt;a href="https://cloud.google.com/run/docs/configuring/services/containers#configure-port" rel="noopener noreferrer"&gt;another port&lt;/a&gt;, open so GCR can reach your app.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A basic web app may have architecture similar to this illustration:&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j3i7fkrilvckb6m5ors.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5j3i7fkrilvckb6m5ors.png" alt="Sample web app running on GCR/GCP" width="800" height="348"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] Sample web app architecture for GCR/GCP



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;For more information, see the &lt;a href="https://cloud.google.com/run/docs/container-contract" rel="noopener noreferrer"&gt;Container runtime contract page&lt;/a&gt; in the documentation.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Google Cloud SDK
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://cloud.google.com/sdk/gcloud" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud&lt;/code&gt; command-line tool&lt;/a&gt; (CLI) can deploy apps to both GAE &lt;em&gt;and&lt;/em&gt; GCR. Unlike GAE however, GCR &lt;em&gt;also&lt;/em&gt; lets you &lt;a href="https://console.cloud.google.com/run/create" rel="noopener noreferrer"&gt;deploy apps from the Cloud console&lt;/a&gt; once your code or container image are in known places.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;gcloud&lt;/code&gt; CLI comes with the &lt;a href="https://cloud.google.com/sdk" rel="noopener noreferrer"&gt;Cloud SDK&lt;/a&gt; (software development kit). If it's already installed, skip to the next section. New to GCP? Follow the &lt;a href="https://cloud.google.com/sdk/docs/install-sdk" rel="noopener noreferrer"&gt;instructions to install the SDK &lt;em&gt;and&lt;/em&gt; learn a few &lt;code&gt;gcloud&lt;/code&gt; commands&lt;/a&gt;. If you just want to install the SDK, see &lt;a href="https://cloud.google.com/sdk/docs/install" rel="noopener noreferrer"&gt;these instructions&lt;/a&gt;. After installation, run &lt;a href="https://cloud.google.com/sdk/gcloud/reference/init" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud init&lt;/code&gt;&lt;/a&gt; to initialize it.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Cloud projects
&lt;/h3&gt;

&lt;p&gt;A GCP &lt;em&gt;project&lt;/em&gt; is required to manage all the resources, e.g., APIs, compute, storage, etc., for an app. &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener noreferrer"&gt;Create a new one or reuse an existing project&lt;/a&gt;. Then &lt;a href="https://cloud.google.com/billing/docs/how-to/modify-project" rel="noopener noreferrer"&gt;assign a billing account&lt;/a&gt; to that project if you haven't already. (Review the cost sidebar above for cost details if you missed it earlier.)&lt;/p&gt;

&lt;h4&gt;
  
  
  Project identifiers
&lt;/h4&gt;

&lt;p&gt;Every project has an ID and a number. (Projects also have names, but that's out-of-scope for this post.) A &lt;em&gt;project ID&lt;/em&gt; is a unique string identifier chosen by you at project creation or automatically assigned if you don't specify one; it cannot be updated once a project is created. A &lt;em&gt;project number&lt;/em&gt; is a unique numeric identifier automatically assigned by GCP and cannot be updated.&lt;/p&gt;

&lt;p&gt;These project identifiers can be found in the Cloud console at &lt;a href="https://console.cloud.google.com/iam-admin/settings" rel="noopener noreferrer"&gt;https://console.cloud.google.com/iam-admin/settings&lt;/a&gt;. You can also run the &lt;a href="https://cloud.google.com/sdk/gcloud/reference/projects/list" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud projects list&lt;/code&gt;&lt;/a&gt; to see &lt;em&gt;all&lt;/em&gt; identifiers for &lt;strong&gt;all&lt;/strong&gt; projects.&lt;/p&gt;

&lt;p&gt;Most of the time, you use a project ID, such as when deploying your service. For your convenience, set the default project ID with this command: &lt;a href="https://cloud.google.com/sdk/gcloud/reference/config/set" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud config set project PROJ_ID&lt;/code&gt;&lt;/a&gt;. If you don't set a default, pass it in with &lt;code&gt;--project PROJ_ID&lt;/code&gt; with each &lt;code&gt;gcloud&lt;/code&gt; command, else you'll be prompted for it. The project number is used much less frequently, such as in GCR service URLs we'll dive into next.&lt;/p&gt;

&lt;h4&gt;
  
  
  Service URL(s)
&lt;/h4&gt;

&lt;p&gt;GAE refers to a running service as "apps" with a restriction that all projects can only have one app and only be based in one region. GCR calls apps "services" and has fewer restrictions. GCR can create &lt;em&gt;any number of services&lt;/em&gt;, and on top of that, &lt;em&gt;services are deployable to different regions&lt;/em&gt;. No such luxury with GAE! Like all GAE apps though, each GCR service comes with a free, default URL, &lt;code&gt;SVC_NAME-PROJ_NUM.REGION.run.app&lt;/code&gt;, where:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;URL component&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SVC_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Name you chose for your service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PROJ_NUM&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Project number (not to be confused with project ID)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;REGION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Region you deployed your service to&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] GCR free domain name components 



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;These &lt;a href="https://cloud.google.com/run/docs/triggering/https-request#deterministic" rel="noopener noreferrer"&gt;deterministic URLs&lt;/a&gt;, based on these known components, are predictable enough that when going to production, allow you to &lt;a href="https://cloud.google.com/run/docs/mapping-custom-domains" rel="noopener noreferrer"&gt;map custom domains at them&lt;/a&gt;. GCR services have &lt;em&gt;another&lt;/em&gt; service URL, less predictable and no longer recommended for use. (See the sidebar below for more details.)&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;📝 &lt;strong&gt;Deterministic vs. non-deterministic service URLs&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Prior to 2024, GCR service URLs were not as deterministic: &lt;code&gt;SVC_NAME-HASH-REG_ABBR.run.app&lt;/code&gt;. The service name (&lt;code&gt;SVC_NAME&lt;/code&gt;) is the same as described above, but rather than a project number, a random &lt;code&gt;HASH&lt;/code&gt; value is generated, and instead of a full region name, a somewhat cryptic region abbreviation (&lt;code&gt;REG_ABBR&lt;/code&gt;) is used. While these URLs are stable once created, deterministic URLs are more predictable and easier to use. Deterministic URLs became &lt;a href="https://cloud.google.com/run/docs/release-notes#September_03_2024" rel="noopener noreferrer"&gt;generally available in Sep 2024&lt;/a&gt; and became prioritized at that time.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Set your environment up for deployments
&lt;/h3&gt;

&lt;p&gt;Confirm you've completed these setup tasks:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install GCP SDK (includes &lt;code&gt;gcloud&lt;/code&gt; CLI)&lt;/li&gt;
&lt;li&gt;Create new or reuse existing GCP project&lt;/li&gt;
&lt;li&gt;Enable billing &amp;amp; assign billing account&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;(optional) Run &lt;code&gt;gcloud config set project PROJ_ID&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Build &amp;amp; deploy: single, combination command options
&lt;/h4&gt;

&lt;p&gt;To deploy an app to GAE, you'd use &lt;code&gt;gcloud app deploy&lt;/code&gt;. The equivalent for GCR is &lt;a href="https://cloud.google.com/sdk/gcloud/reference/run/deploy" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud run deploy&lt;/code&gt;&lt;/a&gt;, GCR deploy commands provide more options than GAE, so commands are typically longer. (If they're &lt;em&gt;too long&lt;/em&gt;, you can put all build criteria inside a &lt;a href="https://cloud.google.com/build/docs/build-config-file-schema" rel="noopener noreferrer"&gt;&lt;code&gt;cloudbuild.yaml&lt;/code&gt; config file&lt;/a&gt;. A realistic deploy command looks like this: &lt;code&gt;gcloud run deploy SVC_NAME --allow-unauthenticated --source . --region REGION&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;What does it all mean? Let's break it down:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Command-line entity&lt;/th&gt;
&lt;th&gt;Description/purpose&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;gcloud run deploy&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Base command&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SVC_NAME&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Choose a name for your service&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--allow-unauthenticated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Make this a public service, else it'll be authenticated (&lt;a href="https://cloud.google.com/run/docs/securing/managing-access" rel="noopener noreferrer"&gt;restricted by IAM permissions&lt;/a&gt;)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--source .&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Deploy from source code (vs. registered container image -- more on this below)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--region REGION&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Choose a &lt;a href="https://cloud.google.com/run/docs/locations" rel="noopener noreferrer"&gt;region&lt;/a&gt; to deploy your service to (where you anticipate most of your users are)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
[TABLE] GCR deployment command components



&lt;p&gt; &lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The service name (&lt;code&gt;SVC_NAME&lt;/code&gt;) is required.&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;--allow-unauthenticated&lt;/code&gt; flag opens it up globally, making the app easy to try/test; turn on authentication when moving to production (if not a public website)&lt;/li&gt;
&lt;li&gt;Provide the &lt;code&gt;--region&lt;/code&gt; flag and region or else be prompted interactively for it.&lt;/li&gt;
&lt;li&gt;You can deploy an app: 1) from source code (&lt;code&gt;--source DIRECTORY&lt;/code&gt;) or 2) from an already built &amp;amp; registered container (&lt;code&gt;--image IMG_NAME&lt;/code&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Most of the above are self-explanatory but the last pair can do with a bit more explanation:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Region&lt;/strong&gt;: GAE allows only one app per project which is locked forever to a single region. In sharp contrast, you can deploy any number of GCR services and to any supported region, and regardless of which region it is hosted in, a GCR service is accessible globally.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Source builds&lt;/strong&gt;: We're deploying this basic app directly from source code, but if you've got a good working build (image) and wish to deploy multiple services, likely to different regions, it's inefficient to build the same container image over and over again. You could also run into inconsistencies if somehow the code changed between those builds.&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Build &amp;amp; deploy: multiple, separate commands and options
&lt;/h4&gt;

&lt;p&gt;To avoid the inconsistent build scenarios, "build once and deploy at least once" can be accomplished by splitting up the single, combo &lt;code&gt;gcloud run deploy&lt;/code&gt; command from above into a pair, one to build and the other to deploy:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;code&gt;gcloud builds submit --tag pkg.dev/PROJ_ID/REPO/IMG_NAME&lt;/code&gt; (Docker) or &lt;code&gt;gcloud builds submit --pack image=pkg.dev/PROJ_ID/REPO/IMG_NAME&lt;/code&gt; (Buildpacks)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gcloud run deploy SVC_NAME --image pkg.dev/PROJ_ID/REPO/IMG_NAME&lt;/code&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first command builds a container image and registers it with CAR (&lt;code&gt;*.pkg.dev&lt;/code&gt; registry "domain"). (If you run across &lt;code&gt;gcr.io&lt;/code&gt; registry "domains", they're from CAR's deprecated predecessor, &lt;a href="https://cloud.google.com/container-registry/docs" rel="noopener noreferrer"&gt;Container Registry&lt;/a&gt;.) The &lt;a href="https://cloud.google.com/sdk/gcloud/reference/builds/submit" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud builds submit&lt;/code&gt;&lt;/a&gt; command builds a container image and registers it.&lt;/p&gt;

&lt;p&gt;Registered container images can be deployed as new services, avoiding repeated builds. If you're familiar with Docker, &lt;code&gt;gcloud builds submit&lt;/code&gt; is similar to &lt;code&gt;docker build&lt;/code&gt; and &lt;code&gt;docker push&lt;/code&gt; while &lt;code&gt;gcloud run deploy&lt;/code&gt; would be &lt;code&gt;docker run&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;But&lt;/strong&gt;, if you're just experimenting and don't plan to deploy multiple services with the same image, you can stick with the &lt;a href="http://cloud.google.com/blog/products/serverless/build-and-deploy-an-app-to-cloud-run-with-a-single-command" rel="noopener noreferrer"&gt;single combo, all-in-one&lt;/a&gt; &lt;code&gt;gcloud run deploy&lt;/code&gt; command from the last section.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to the cloud
&lt;/h2&gt;

&lt;p&gt;Formalities aside, let's deploy these sample apps to GCR. For brevity, I'm sticking with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source-only deployments (&lt;code&gt;--source .&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;A set default project ID (else use &lt;code&gt;--project PROJ_ID&lt;/code&gt; or be prompted)&lt;/li&gt;
&lt;li&gt;Allowing unauthenticated traffic (&lt;code&gt;--allow-unauthenticated&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;The service name of &lt;code&gt;hello&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;The &lt;code&gt;us-west1&lt;/code&gt; region (&lt;code&gt;--region&lt;/code&gt;; else be prompted)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;These result in the following deployment command: &lt;code&gt;gcloud run deploy hello --allow-unauthenticated --source . --region us-west1&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Now I'll use that command to deploy the app in these configurations:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Python with Docker&lt;/li&gt;
&lt;li&gt;Node.js with Docker&lt;/li&gt;
&lt;li&gt;Node.js (without Docker) with Buildpacks&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Pick your own service name and &lt;a href="https://cloud.google.com/run/docs/locations" rel="noopener noreferrer"&gt;region&lt;/a&gt; if you wish; adjust your command-line options accordingly.&lt;/p&gt;

&lt;h3&gt;
  
  
  Python with Docker
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;Dockerfile&lt;/code&gt; didn't conflict with the &lt;code&gt;Procfile&lt;/code&gt; in my build-and-deploy below but feel free to rename or remove &lt;code&gt;Procfile&lt;/code&gt; temporarily if you have issues. Go to the &lt;code&gt;python&lt;/code&gt; folder and run the &lt;code&gt;gcloud run deploy&lt;/code&gt; command... your output will look similar to:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run deploy hello &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; us-west1
Building using Dockerfile and deploying container to Cloud Run service &lt;span class="o"&gt;[&lt;/span&gt;hello] &lt;span class="k"&gt;in &lt;/span&gt;project &lt;span class="o"&gt;[&lt;/span&gt;PROJ_ID] region &lt;span class="o"&gt;[&lt;/span&gt;us-west1]
✓ Building and deploying... Done.
  ✓ Uploading sources...
  ✓ Building Container... Logs are available at &lt;span class="o"&gt;[&lt;/span&gt;https://console.cloud.google.com/cloud-build/builds/c2ab2b1a-cec0-4686-bc77-d6a809fd7c3d?project&lt;span class="o"&gt;=&lt;/span&gt;PROJ_NUM].
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service &lt;span class="o"&gt;[&lt;/span&gt;hello] revision &lt;span class="o"&gt;[&lt;/span&gt;hello-00004-ds8] has been deployed and is serving 100 percent of traffic.
Service URL: https://hello-PROJ_NUM.us-west1.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Node.js with Docker
&lt;/h3&gt;

&lt;p&gt;If you switching to the &lt;code&gt;nodejs&lt;/code&gt; folder, issue the same command:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run deploy hello &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; us-west1
Building using Dockerfile and deploying container to Cloud Run service &lt;span class="o"&gt;[&lt;/span&gt;hello] &lt;span class="k"&gt;in &lt;/span&gt;project &lt;span class="o"&gt;[&lt;/span&gt;PROJ_ID] region &lt;span class="o"&gt;[&lt;/span&gt;us-west1]
✓ Building and deploying new service... Done.
  ✓ Uploading sources...
  ✓ Building Container... Logs are available at &lt;span class="o"&gt;[&lt;/span&gt;https://console.cloud.google.com/cloud-build/builds/a4f4ceda-abe8-4116-a30a-8aa5f5cc4279?project&lt;span class="o"&gt;=&lt;/span&gt;PROJ_NUM].
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service &lt;span class="o"&gt;[&lt;/span&gt;hello] revision &lt;span class="o"&gt;[&lt;/span&gt;hello-00001-9r2] has been deployed and is serving 100 percent of traffic.
Service URL: https://hello-PROJ_NUM.us-west1.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Notice there's no difference to the commands at all: Everything the build system needs is in the &lt;code&gt;Dockerfile&lt;/code&gt;. Both result in the app deployed (regardless of language) and available globally at the same URL.&lt;/p&gt;

&lt;h3&gt;
  
  
  Node.js with Buildpacks
&lt;/h3&gt;

&lt;p&gt;Let's say you're not familiar with Docker or prefer not to use it. Delete, move, or remove &lt;code&gt;Dockerfile&lt;/code&gt; and issue the &lt;em&gt;same command&lt;/em&gt;... the app also deploys successfully, thanks to Buildpacks:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;gcloud run deploy hello &lt;span class="nt"&gt;--allow-unauthenticated&lt;/span&gt; &lt;span class="nt"&gt;--source&lt;/span&gt; &lt;span class="nb"&gt;.&lt;/span&gt; &lt;span class="nt"&gt;--region&lt;/span&gt; us-west1
Building using Buildpacks and deploying container to Cloud Run service &lt;span class="o"&gt;[&lt;/span&gt;hello] &lt;span class="k"&gt;in &lt;/span&gt;project &lt;span class="o"&gt;[&lt;/span&gt;PROJ_ID] region &lt;span class="o"&gt;[&lt;/span&gt;us-west1]
✓ Building and deploying... Done.
  ✓ Uploading sources...
  ✓ Building Container... Logs are available at &lt;span class="o"&gt;[&lt;/span&gt;https://console.cloud.google.com/cloud-build/builds/0140d524-ecc1-4b97-9079-5695c03d9e01?project&lt;span class="o"&gt;=&lt;/span&gt;PROJ_NUM].
  ✓ Creating Revision...
  ✓ Routing traffic...
  ✓ Setting IAM Policy...
Done.
Service &lt;span class="o"&gt;[&lt;/span&gt;hello] revision &lt;span class="o"&gt;[&lt;/span&gt;hello-00002-z6n] has been deployed and is serving 100 percent of traffic.
Service URL: https://hello-PROJ_NUM.us-west1.run.app
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Browse Cloud Build log via the link to see how your service was built and deployed. Our Node.js &lt;code&gt;Dockerfile&lt;/code&gt; specifies a Node 22 slim base image, but without it, you're dependent on Buildpacks default runtime versions at build-time. In the last deployment above, Buildpacks used Node 20 because the default hasn't moved up to version 22 yet. So if you're targeting a specific release, using Docker (and &lt;code&gt;Dockerfile&lt;/code&gt;) is your best bet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Globally-accessible and scalable on GCR
&lt;/h3&gt;

&lt;p&gt;For any of the deployments above, hitting the "Service URL" shown at the end of your build command with a browser should look just like your locally-deployed version(s) but are now globally-accessible:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3zxcakbd17bkyx2dwfc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fm3zxcakbd17bkyx2dwfc.png" alt="" width="800" height="493"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] "Hello World!" sample app running on GCR/GCP



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Not only are your GAE apps &amp;amp; GCR services available around the world, they autoscale as well. GCP serverless platforms spin up more instances on-demand based on traffic then winds them down as things subside, all without explicit action on your part.&lt;/p&gt;

&lt;p&gt;Congrats on successfully deploying the demo app(s) to GCR!&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and next steps
&lt;/h2&gt;

&lt;p&gt;Now that you've taken a working Python or Node sample app, tested it locally, and successfully deployed it to GCR/GCP, continue to experiment by updating the code and redeploying, uploading one of your "real" apps, or check out the &lt;a href="https://dev.to/wescpy/hosting-apps-in-the-cloud-with-google-app-engine-3fn"&gt;equivalent GAE post&lt;/a&gt; to see how to deploy the same app to &lt;em&gt;that&lt;/em&gt; platform. While GCR is newer and has fewer restrictions, that post points out some of the reasons why you may still consider GAE. Regardless of next steps, you now have a new option for running apps in the cloud, and nowhere did you consider allocating, configuring, or paying for a server 24x7.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing epilogue
&lt;/h3&gt;

&lt;p&gt;If you want to keep the demo GCR service around, great. Even though it's not a free service, there's a monthly free quota you have to consume in order to incur billing. However, if no one is accessing your app, one of the benefits of serverless is that you're not paying. If someone discovers your app's URL though, or you continue to deploy new versions, this will eventually lead to billable usage. If you're not ready to continue experimenting with GCR or the deployed app, you have several options (from least-to-most "severe"):&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Stop GCR service option&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/run/docs/managing/services#disable" rel="noopener noreferrer"&gt;Disable service&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Temporarily disable service to avoid incurring additional charges; can be re-enable any time.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/run/docs/managing/services#delete" rel="noopener noreferrer"&gt;Delete service&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Delete &amp;amp; remove service; cannot be "un-deleted", but you &lt;em&gt;can&lt;/em&gt; redeploy again later.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://console.cloud.google.com/iam-admin/settings" rel="noopener noreferrer"&gt;Shutdown project&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Delete service, billing, and all project resources if you don't want to continue using it with GCR or other GCP services; cannot restore a project 30 days after it has been shut down.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first pair of options help you avoid charges from a running service, but bear in mind the storage costs for GCS and AR as described earlier may still apply, so delete enough build artifacts to fall under any limits you may be exceeding. The last option "wipes the slate clean" where you no longer have to worry about billing at all.&lt;/p&gt;

&lt;h3&gt;
  
  
  Other GCR features
&lt;/h3&gt;

&lt;p&gt;GCR has other features worth exploring... subjects of future posts. Preview to get a head start:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;GCR feature&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/run/docs/triggering/https-request" rel="noopener noreferrer"&gt;HTTP &amp;amp; event-driven &lt;em&gt;triggers&lt;/em&gt;&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Services can be event-driven in addition to being "triggered" with HTTP requests; learn other ways to kick off work on GCR&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/run/docs/create-jobs" rel="noopener noreferrer"&gt;Jobs&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Run tasks start-to-finish outside of HTTP/S requests, e.g., batch processing; can run for up to 24 hours&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Functions&lt;/a&gt;&lt;/td&gt;
&lt;td&gt;Don't have an entire app? Got snippets of code to run that doesn't involve a "full stack"? Functions are great for microservices, mobile backends, etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;If you found an error in this post, a bug in &lt;a href="https://github.com/wescpy/google/tree/main/cloud/cloudrun" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic I should cover, drop a note in the comments below or file an issue at the repo.&lt;/p&gt;

&lt;p&gt;If you have older GAE apps and considering a move/port to GCR, want to upgrade from Python 2 to 3 or migrate off &lt;a href="https://cloud.google.com/appengine/docs/standard/bundled-services-overview" rel="noopener noreferrer"&gt;GAE bundled services&lt;/a&gt;, I may be able to help... check out &lt;a href="https://appenginemigrations.com" rel="noopener noreferrer"&gt;https://appenginemigrations.com&lt;/a&gt; and submit a request there.&lt;/p&gt;

&lt;p&gt;I enjoy meeting users on the road... see if I'll be visiting your community in the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources related to this post which you may find useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blog post code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/cloud/cloudrun" rel="noopener noreferrer"&gt;Samples in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/cloud" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; GCP posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GCR resources &amp;amp; historical references
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.run" rel="noopener noreferrer"&gt;Home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/run/docs" rel="noopener noreferrer"&gt;Documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/run/docs/runtime-support" rel="noopener noreferrer"&gt;Runtime lifecycle &amp;amp; support schedule&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/serverless/announcing-cloud-run-the-newest-member-of-our-serverless-compute-stack" rel="noopener noreferrer"&gt;Original product launch post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/blog/products/serverless/knative-based-cloud-run-services-are-ga" rel="noopener noreferrer"&gt;General availability announcement&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GCR content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;GCR sample weather alerts app &amp;amp; "always-on CPU" use case&lt;/strong&gt; &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/use-cloud-run-always-cpu-allocation-background-work" rel="noopener noreferrer"&gt;post&lt;/a&gt; and &lt;a href="http://youtu.be/ul1cGarS23M" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCR sample YouTube comment tracker app &amp;amp; full design use case&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: Designing a user interface quickly (frontend/design) &lt;a href="http://youtu.be/GyRHRK61qro" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: Design a serverless architecture (backend/server-side) &lt;a href="http://youtu.be/ertbL2Rxbvk" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Migrate GAE apps to GCR&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: With Docker &lt;a href="https://goo.gle/3geAqFs" rel="noopener noreferrer"&gt;post&lt;/a&gt;, &lt;a href="http://youtu.be/uI1mzwtx4ZM?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_mgrcrdckr_sms_201017&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;video&lt;/a&gt;, and &lt;a href="http://g.co/codelabs/pae-migrate-rundocker" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: Without Docker/with Buildpacks &lt;a href="http://goo.gle/3t3uyUN" rel="noopener noreferrer"&gt;post&lt;/a&gt;, &lt;a href="http://youtu.be/VP3g2OZYXPE?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_mgrcrbdpk_sms_201031&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;video&lt;/a&gt;, and &lt;a href="http://g.co/codelabs/pae-migrate-runbldpks" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GCP serverless content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Explore GCP serverless platforms with a nebulous sample app&lt;/strong&gt;

&lt;ul&gt;
&lt;li&gt;Part 1: Picking the "right" serverless platform &lt;a href="http://youtu.be/gle26fT28Bw?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Part 2: Deploy the &lt;strong&gt;same app&lt;/strong&gt; to App Engine, Cloud Functions, or Cloud Run

&lt;ul&gt;
&lt;li&gt;
&lt;a href="http://goo.gle/2Y0ph5q" rel="noopener noreferrer"&gt;Blog post&lt;/a&gt;, &lt;a href="https://youtu.be/eTotLOVR7MQ?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservdeploy_neb_2021xx&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;video&lt;/a&gt;, and &lt;a href="https://github.com/wescpy/nebulous-serverless" rel="noopener noreferrer"&gt;code repo&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 2 or 3 sample app locally &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-flask?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 2 sample app on GAE &lt;del&gt;codelab&lt;/del&gt; (deprecated: 2.x no longer supported by GAE)&lt;/li&gt;
&lt;li&gt;Run Python 3 sample app on GAE &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gae3?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 3 sample app on GCF (Gen1) &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcf?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 2 sample app on GCR with Docker &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcr2?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 3 sample app on GCR with Docker &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcr3?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Python 3 sample app on GCR without Docker/with Buildpacks &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-python-gcrbp?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Run Node.js sample app on GAE, GCF (Gen1), and GCR &lt;a href="https://codelabs.developers.google.com/codelabs/cloud-nebulous-serverless-nodejs?utm_source=codelabs&amp;amp;utm_medium=et&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;codelab&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;How to call Google APIs from GCP serverless platforms&lt;/strong&gt; &lt;a href="https://cloud.google.com/blog/topics/developers-practitioners/calling-google-apis-serverless-part-i-cloud-apis?utm_source=blog&amp;amp;utm_medium=partner&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservconcept_neb_202007" rel="noopener noreferrer"&gt;post&lt;/a&gt;
&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Top 3 pain points for serverless developers&lt;/strong&gt; &lt;a href="http://youtu.be/5qOwYSCb1Gg" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I was a member of the GCR product team 2020-2023. While product information is as accurate as I can find or recall, the opinions are my own.&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He runs &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;CyberWeb&lt;/a&gt; specializing in GCP &amp;amp; GWS APIs and serverless platforms, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;Python &amp;amp; App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. Wesley was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40" rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt;. He holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide at conferences, user group events, and universities. Follow he/him &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;@wescpy&lt;/a&gt; &amp;amp; his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>node</category>
      <category>python</category>
      <category>serverless</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Generating audio clips with Gemini 2.0 Flash</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 16 Dec 2024 17:20:00 +0000</pubDate>
      <link>https://dev.to/gde/generate-audio-clips-with-gemini-20-flash-from-google-n0g</link>
      <guid>https://dev.to/gde/generate-audio-clips-with-gemini-20-flash-from-google-n0g</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;Happy holidays! Google &lt;a href="https://developers.googleblog.com/en/the-next-chapter-of-the-gemini-era-for-developers" rel="noopener noreferrer"&gt;recently "gifted" us the new Gemini 2.0 Flash model&lt;/a&gt;, expanding on what's available in the original 1.x models. One of the new features is the ability to generate text-based audio clips. Sure good ol' fashioned predictive AI's &lt;em&gt;text-to-speech&lt;/em&gt; (TTS) functionality is certainly useful, but this takes it to the next level, giving genAI users "&lt;strong&gt;&lt;em&gt;idea-to-speech&lt;/em&gt;&lt;/strong&gt;" capabilities. Learn how to access this new feature from Python today!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;UPDATE (Jun 2025)&lt;/strong&gt;: this post uses the Gemini &lt;strong&gt;2.0 Flash Experimental&lt;/strong&gt; model, but since publication, Google has launched additional models that can generate audio. See &lt;a href="https://ai.google.dev/gemini-api/docs/models#model-variations" rel="noopener noreferrer"&gt;this chart&lt;/a&gt; for the capabilities of all Gemini models, including several &lt;strong&gt;Gemini 2.5&lt;/strong&gt; models featuring TTS output.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fgtstqrfnhwq2pu6ezb1z.png" alt="Build with Gemini" width="800" height="388"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog focusing on using Google APIs from Python (and sometimes Node.js). Today's post focuses on Gemini, but you'll find plenty of content beyond Gemini in other posts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP: &lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;, and &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt; (Drive, Docs, Sheets, Gmail, etc.)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/29655"&gt;Google Maps/GMP&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Credentials (&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Today, we're taking a break from the flow of the previous posts in this &lt;a href="https://dev.to/wescpy/series/27183"&gt;series covering the Gemini API&lt;/a&gt; by exploring one new feature. While some users may be content &lt;em&gt;using&lt;/em&gt; ChatGPT or Gemini online or via app, the Gemini API brings generative AI abilities to &lt;strong&gt;your&lt;/strong&gt; apps, so if you're new or exploring, check out the other posts on how to get started as well as see some of its basic capabilities. This post only looks at one feature from the &lt;a href="https://cloud.google.com/vertex-ai/generative-ai/docs/gemini-v2" rel="noopener noreferrer"&gt;Gemini 2.0 Flash&lt;/a&gt; model: &lt;strong&gt;text-based audio clip generation&lt;/strong&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;h3&gt;
  
  
  New client library improves user experience (UX)
&lt;/h3&gt;

&lt;p&gt;You need a client library to talk to Gemini from code. While several client libraries already exist for Gemini, Google has recently introduced a &lt;a href="https://github.com/googleapis/python-genai" rel="noopener noreferrer"&gt;new one&lt;/a&gt;. The new library features an improved UX, so I have to give Google some credit. In the &lt;a href="https://dev.to/wescpy/a-better-google-gemini-api-hello-world-sample-4ddm"&gt;first Gemini post&lt;/a&gt; in the series, I lamented that making the API available from two different platforms confuses developers:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev" rel="noopener noreferrer"&gt;Google AI&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/vertex-ai/docs/generative-ai/multimodal/overview" rel="noopener noreferrer"&gt;GCP Vertex AI&lt;/a&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Differing client libraries, numerous code samples, documentation in different locations under different web domains, etc., all add to a less-than-optimal UX. A replacement client library with the ability to work across platforms allows users to get started and experiment on Google AI then "upgrade" to Vertex AI, when ready for production, without changing their code.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;💡 &lt;strong&gt;Yes, it's an "&lt;code&gt;ifdef&lt;/code&gt;"&lt;/strong&gt;&lt;br&gt;
If you're like me and like to dig around in code, you may be curious about how the new client library works across both Google AI and Vertex AI. It's not magic, so you'll find &lt;strong&gt;if-else&lt;/strong&gt; blocks where it matters, like a C/C++ &lt;code&gt;ifdef&lt;/code&gt;. In the new client library, any time you see &lt;code&gt;mldev&lt;/code&gt;, think Google AI, and as expected, &lt;code&gt;vertex&lt;/code&gt; is Vertex AI.&lt;/p&gt;

&lt;p&gt;One example is found the &lt;a href="https://github.com/googleapis/python-genai/blob/main/google/genai/live.py#L618-L655" rel="noopener noreferrer"&gt;Live API code&lt;/a&gt; while another is in the &lt;a href="https://github.com/googleapis/python-genai/blob/main/google/genai/models.py#L3636-L3645" rel="noopener noreferrer"&gt;models code&lt;/a&gt;. (&lt;strong&gt;NOTE&lt;/strong&gt;: these links will probably be wrong when a new version is pushed, but I'll update them once the library has an official release)&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You'll find the new client library on the &lt;a href="https://ai.google.dev/gemini-api/docs/sdks" rel="noopener noreferrer"&gt;Gemini API SDKs page&lt;/a&gt;. Today's sample app is only available in Python^.&lt;/p&gt;

&lt;p&gt;&lt;small&gt;&lt;sup&gt;^&lt;/sup&gt; -- Python 3 only; Python 2 support is not available for the Gemini API&lt;/small&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Installation and setup
&lt;/h3&gt;

&lt;p&gt;As we're exploring this new feature, the app will run on Google AI. In an upcoming post, we'll explore how to run the same app on Vertex AI. So follow these steps to install the client library and get set up:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install the new client library: &lt;code&gt;pip install -U google-genai&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://makersuite.google.com/app/apikey" rel="noopener noreferrer"&gt;Create an API key&lt;/a&gt; (if you don't already have one)&lt;/li&gt;
&lt;li&gt;Save API key as a string to &lt;code&gt;settings.py&lt;/code&gt; as &lt;code&gt;API_KEY = 'YOUR_API_KEY'&lt;/code&gt; (and follow the suggestions in the sidebar below to protect it)&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;WARNING: Keep API keys secure&lt;/strong&gt;&lt;br&gt;
Storing API keys in files (or hard-coding them for use in actual code or even assigning to environment variables) is for prototyping and learning purposes only. When going to production, put them in environment variables or in a secrets manager. Files like &lt;code&gt;settings.py&lt;/code&gt; or &lt;code&gt;.env&lt;/code&gt; containing API keys are susceptible. &lt;strong&gt;&lt;em&gt;Under no circumstances&lt;/em&gt;&lt;/strong&gt; should you &lt;a href="https://www.gitguardian.com/glossary/remediate-sensitive-data-leaks-api-keys-hardcoded-source-code" rel="noopener noreferrer"&gt;upload files like those to any public or private repo&lt;/a&gt;, &lt;a href="https://spacelift.io/blog/terraform-secrets" rel="noopener noreferrer"&gt;have sensitive data like that in TerraForm config files&lt;/a&gt;, &lt;a href="https://www.darkreading.com/cloud-security/docker-leaks-api-secrets-private-keys-cybercriminals" rel="noopener noreferrer"&gt;add such files to Docker layers&lt;/a&gt;, etc., as once your API key leaks, everyone in the world can use it.&lt;/p&gt;

&lt;p&gt;If you're new to Google developer tools, &lt;a href="https://dev.to/wescpy/series/25404"&gt;&lt;em&gt;API keys&lt;/em&gt;&lt;/a&gt; are one of the credentials types supported by Google APIs, and they're the &lt;em&gt;only&lt;/em&gt; type supported by Maps APIs. Other credentials types include &lt;a href="https://dev.to/wescpy/series/25403"&gt;&lt;em&gt;OAuth client IDs&lt;/em&gt;&lt;/a&gt;, mostly used by GWS APIs, and &lt;em&gt;service accounts&lt;/em&gt;, mostly used by Google Cloud (GCP) APIs. While this post doesn't cover Google Maps, the Maps team put together a great &lt;a href="https://developers.google.com/maps/api-security-best-practices#rec-best-practices" rel="noopener noreferrer"&gt;guide on API key best practices&lt;/a&gt;, so check it out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The app
&lt;/h2&gt;

&lt;p&gt;The sample app &lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem20-audio.py" rel="noopener noreferrer"&gt;&lt;code&gt;gem20-audio.py&lt;/code&gt;&lt;/a&gt; sends a prompt of &lt;code&gt;Describe a cat in a few sentences&lt;/code&gt; to Gemini and requests an audio clip in response, so the app's functionality is pretty brief: make the request, get the response, and save the audio file.&lt;/p&gt;

&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;asyncio&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;contextlib&lt;/span&gt;
&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;

&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;settings&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;API_KEY&lt;/span&gt;

&lt;span class="n"&gt;GENAI&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;API_KEY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;http_options&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;api_version&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;v1alpha&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;
&lt;span class="n"&gt;MODEL&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;gemini-2.0-flash-exp&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;CONFIG&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;generation_config&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;response_modalities&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;AUDIO&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;]}}&lt;/span&gt;
&lt;span class="n"&gt;PROMPT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Describe a cat in a few sentences&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
&lt;span class="n"&gt;FILENAME&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;whatacatis.wav&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="nd"&gt;@contextlib.contextmanager&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;wave_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;1&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;24000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sample_width&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;set up .wav file writer&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;wave&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;open&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;wb&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setnchannels&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;channels&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setsampwidth&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sample_width&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;wf&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;setframerate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rate&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;yield&lt;/span&gt; &lt;span class="n"&gt;wf&lt;/span&gt;

&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;request_audio&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;PROMPT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;FILENAME&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;request LLM generate audio file given prompt&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="s"&gt;** LLM prompt: &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;GENAI&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;aio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;live&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;MODEL&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;config&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;CONFIG&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="nf"&gt;wave_file&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
            &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;prompt&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;end_of_turn&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
            &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;session&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;receive&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
                &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
                    &lt;span class="n"&gt;f&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeframes&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;data&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;** Saved audio to &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;filename&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="sh"&gt;"'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;asyncio&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;request_audio&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;
[CODE] &lt;code&gt;gem20-audio.py&lt;/code&gt;: Audio "Hello World!" sample





&lt;p&gt; &lt;/p&gt;

&lt;h3&gt;
  
  
  App components
&lt;/h3&gt;

&lt;p&gt;There are 4 major chunks to this script:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Imports&lt;/li&gt;
&lt;li&gt;Constants&lt;/li&gt;
&lt;li&gt;Audio file writer&lt;/li&gt;
&lt;li&gt;Core functionality&lt;/li&gt;
&lt;/ol&gt;

&lt;h4&gt;
  
  
  Imports
&lt;/h4&gt;

&lt;p&gt;From the &lt;a href="https://docs.python.org/library" rel="noopener noreferrer"&gt;Python standard library&lt;/a&gt;, &lt;code&gt;asyncio&lt;/code&gt; is required because the Multimodal Live API (&lt;a href="https://ai.google.dev/api/multimodal-live" rel="noopener noreferrer"&gt;feature&lt;/a&gt; and &lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini-v2#live-api" rel="noopener noreferrer"&gt;usage&lt;/a&gt;) is only available asynchronously. The &lt;code&gt;contextlib.contextmanager&lt;/code&gt; decorator is needed so we can wrap and use the audio file-writer with Python's &lt;strong&gt;&lt;code&gt;with&lt;/code&gt;&lt;/strong&gt; statement. The last "stdlib" package used is &lt;code&gt;wave&lt;/code&gt;, which processes &lt;a href="https://wikipedia.org/wiki/WAV" rel="noopener noreferrer"&gt;WAVE&lt;/a&gt; audio files. This is followed by importing Google's new "genAI" client library.&lt;/p&gt;

&lt;p&gt;Like in previous code samples in this series, the API key is saved to &lt;code&gt;settings.py&lt;/code&gt;. Alternatively, you can save your API key to the &lt;code&gt;GEMINI_API_KEY&lt;/code&gt; environment variable (and avoid having &lt;code&gt;.env&lt;/code&gt; or &lt;code&gt;settings.py&lt;/code&gt; files. If Python users want it in a file but want to store it in &lt;code&gt;.env&lt;/code&gt; instead of &lt;code&gt;settings.py&lt;/code&gt;, use the &lt;code&gt;python-dotenv&lt;/code&gt; package which more closely mirrors a Node.js environment. There's also the &lt;a href="https://cloud.google.com/secret-manager" rel="noopener noreferrer"&gt;GCP Secret Manager&lt;/a&gt; as yet another option.&lt;/p&gt;

&lt;h4&gt;
  
  
  Constants and audio file writer
&lt;/h4&gt;

&lt;p&gt;Constants for the API client, generative large language model (genAI LLM), and model configuration follow. The last pair of constants are the user's prompt and filename to save the generated audio to.&lt;/p&gt;

&lt;p&gt;The WAV file (&lt;code&gt;wave_file()&lt;/code&gt;) writer just sets up the basic parameters as a &lt;a href="https://docs.python.org/howto/functional.html#generators" rel="noopener noreferrer"&gt;generator&lt;/a&gt; and &lt;a href="https://docs.python.org/library/contextlib.html#contextlib.contextmanager" rel="noopener noreferrer"&gt;wraps&lt;/a&gt; it in a &lt;a href="https://docs.python.org/library/stdtypes.html#typecontextmanager" rel="noopener noreferrer"&gt;context manager&lt;/a&gt;, allowing it to be used with the &lt;strong&gt;&lt;code&gt;with&lt;/code&gt;&lt;/strong&gt; statement. You'll find nearly-identical code in various samples and Notebooks in the &lt;a href="https://github.com/google-gemini/cookbook/tree/main/gemini-2" rel="noopener noreferrer"&gt;Gemini 2.0 cookbook repo&lt;/a&gt;.&lt;/p&gt;

&lt;h4&gt;
  
  
  Core functionality
&lt;/h4&gt;

&lt;p&gt;All of the "real work" takes place in &lt;code&gt;request_audio()&lt;/code&gt;. It's a single session using the Live API, kicking it off by opening the WAV file for write and sending the prompt to the LLM. The rest of it continuously waits for a server response, writing the chunks of audio data received until it's been exhausted, terminating the session.&lt;/p&gt;

&lt;p&gt;This is minimal code required to do the job. In other examples from Google, you'll find reference to &lt;code&gt;server_content&lt;/code&gt;, &lt;code&gt;inline_data&lt;/code&gt; and writing out &lt;code&gt;parts&lt;/code&gt;. Most of this relates to supporting a multi-turn conversation, but for a single request-response "cycle," less code is less confusing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Running the script
&lt;/h3&gt;

&lt;p&gt;Running the script produces an audio file along with the expected output:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 gem20-audio.py

** LLM prompt: "Describe a cat in a few sentences"
** Saved audio to "whatacatis.wav"
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Your mileage may vary, but this is the audio track I got from Gemini:&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/sX3DbO-pa8c"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary
&lt;/h2&gt;

&lt;p&gt;Developers are eager to jump into the world of AI/ML, especially GenAI &amp;amp; LLMs, and accessing Google's Gemini models via API is part of that picture. The previous posts in the series got your foot in the door, and today, we explore a new feature available from Gemini 2.0 Flash. In upcoming posts, we'll continue this journey by describing how to run it from Vertex AI.&lt;/p&gt;

&lt;p&gt;If you want to see Gemini API code for both platforms, check out the &lt;a href="https://dev.to/wescpy/a-better-google-gemini-api-hello-world-sample-4ddm"&gt;intro (1st) post in this series&lt;/a&gt;. In another future post, we'll pick up the basic genAI web app from the &lt;a href="https://dev.to/wescpy/gemini-api-102a-putting-together-a-basic-genai-web-app-3e3"&gt;previous (3rd) post&lt;/a&gt; (link also below) and show you how to deploy it to Google Cloud.&lt;/p&gt;

&lt;p&gt;If you find errors or have suggestions on content you'd like to see in future posts, also leave a comment below, and if your organization needs help integrating Google technologies via its APIs, reach out to me by submitting a request at &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;https://cyberwebconsulting.com&lt;/a&gt;. Thanks for reading, and I hope to meet you if I come through your community... you'll find my travel calendar at the bottom of that page as well. Season's greetings and see you next year!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;PREV POST:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/gemini-api-102a-putting-together-a-basic-genai-web-app-3e3"&gt;&lt;em&gt;Part 3&lt;/em&gt;: Gemini API 102a... Putting together basic GenAI web apps&lt;/a&gt;&lt;br&gt;
&lt;strong&gt;NEXT POST:&lt;/strong&gt; &lt;a href="https://dev.to/wescpy/generating-images-with-gemini-20-flash-from-google-448e"&gt;&lt;em&gt;Part 5&lt;/em&gt;: Generating images with Gemini 2.0 Flash&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various links relevant to this post:&lt;/p&gt;

&lt;h3&gt;
  
  
  Code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://github.com/wescpy/google/blob/main/gemini/gem20-audio.py" rel="noopener noreferrer"&gt;Sample in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt; (Python)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/gemini" rel="noopener noreferrer"&gt;Code samples for Gemini posts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini API (Google AI)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs/gemini_api_overview" rel="noopener noreferrer"&gt;API overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/sdks" rel="noopener noreferrer"&gt;API SDKs page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/api/multimodal-live" rel="noopener noreferrer"&gt;Multimodal Live API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/tutorials/python_quickstart" rel="noopener noreferrer"&gt;QuickStart page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google/generative-ai-docs/blob/main/site/en/tutorials/python_quickstart.ipynb" rel="noopener noreferrer"&gt;QuickStart code&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/api/python/google/generativeai" rel="noopener noreferrer"&gt;GenAI API reference&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Gemini 2.0 Flash
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.googleblog.com/the-next-chapter-of-the-gemini-era-for-developers" rel="noopener noreferrer"&gt;Launch announcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini-v2" rel="noopener noreferrer"&gt;Developer overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini/flash" rel="noopener noreferrer"&gt;All Flash models&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/google-gemini/cookbook/tree/main/gemini-2" rel="noopener noreferrer"&gt;Cookbook repo&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Generative AI and Gemini resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/docs" rel="noopener noreferrer"&gt;General GenAI docs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://deepmind.google/technologies/gemini" rel="noopener noreferrer"&gt;Gemini home page&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/#three-gemini-sizes-for-unmatched-versatility" rel="noopener noreferrer"&gt;Gemini models overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://ai.google.dev/gemini-api/docs/models/gemini" rel="noopener noreferrer"&gt;Gemini models information (&amp;amp; quotas)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other relevant content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini API post series&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>api</category>
      <category>machinelearning</category>
    </item>
    <item>
      <title>What is the world's "easiest" Google API?!?</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Mon, 02 Dec 2024 06:43:55 +0000</pubDate>
      <link>https://dev.to/gde/the-worlds-easiest-google-api-13oh</link>
      <guid>https://dev.to/gde/the-worlds-easiest-google-api-13oh</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;While I generally cover Google API usage from a functionality perspective, at &lt;em&gt;some&lt;/em&gt; point, I wondered which one of them was the easiest to use. Could it be easy enough where developers can use it without a computer, client library, OAuth2, SDK, or even code? It's Fall 2024, and I find myself on the road again, &lt;a href="https://linkedin.com/feed/update/urn:li:activity:7265568270953439233" rel="noopener noreferrer"&gt;for work&lt;/a&gt; &lt;em&gt;and&lt;/em&gt; for the holidays. This means some of us are thinking about &lt;a href="https://maps.google.com" rel="noopener noreferrer"&gt;Google Maps&lt;/a&gt;, so it's no coincidence the one I found meeting the aforementioned requirements and gets the title of the world's easiest Google API goes to the &lt;strong&gt;Maps Static API&lt;/strong&gt;. To use it, you only need HTTP and an API key... wow(!), stick around to learn more!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpjk3h7d8ezeupyok0m2.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ffpjk3h7d8ezeupyok0m2.jpg" alt="Google Maps Platform" width="800" height="247"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog covering use of Google APIs, generally from Python and sometimes Node.js (but not today because you can use this API without code)! Here, I show developers how to use a variety of Google APIs and share tips-n-tricks not documented by Google, covering a broad variety of topics like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Google Cloud/GCP (&lt;a href="https://dev.to/wescpy/series/30098"&gt;serverless&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt;)&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/23343"&gt;Google Workspace/GWS&lt;/a&gt; (Drive, Docs, Sheets, Gmail, etc.)&lt;/li&gt;
&lt;li&gt;Generative AI with &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/series/29655"&gt;Google Maps&lt;/a&gt; (&lt;em&gt;this series&lt;/em&gt;)&lt;/li&gt;
&lt;li&gt;Credentials (&lt;a href="https://dev.to/wescpy/series/25404"&gt;API keys&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client IDs&lt;/a&gt;, &lt;a href="https://dev.to/wescpy/getting-started-with-google-apis-service-accounts-part-1-2fi0"&gt;service accounts&lt;/a&gt;)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Regardless of which Google APIs you look at, &lt;strong&gt;&lt;em&gt;one&lt;/em&gt;&lt;/strong&gt; of them has got to be the "easiest" to use, right?&lt;/p&gt;

&lt;h2&gt;
  
  
  Google Maps Static API
&lt;/h2&gt;

&lt;p&gt;A few years back, I wrote an &lt;a href="http://goo.gle/3nPxmlc" rel="noopener noreferrer"&gt;interesting app that uses four different Google APIs&lt;/a&gt; to address a fictitious but possible business use case. Earlier this year, I &lt;a href="https://github.com/wescpy/analyze_gsimg/tree/master/#description" rel="noopener noreferrer"&gt;upgraded it&lt;/a&gt; by adding use of two more Google APIs, one of them being the &lt;a href="https://developers.google.com/maps/documentation/maps-static/overview" rel="noopener noreferrer"&gt;Maps Static API&lt;/a&gt;. I soon realized it was unlike any other Google API I've ever used: no client library import, no instantiating an API client object, no OAuth2, and no API function calls in my code. "API calls" are URL-based HTTP GET requests.&lt;/p&gt;

&lt;p&gt;The only "security" required is an API key (true for &lt;em&gt;all&lt;/em&gt; Maps APIs). Check the documentation on how to &lt;a href="https://developers.google.com/maps/documentation/geocoding/get-api-key" rel="noopener noreferrer"&gt;get &amp;amp; use API keys&lt;/a&gt; if needed. But, before using the API, let's discus what it &lt;em&gt;is&lt;/em&gt; first!&lt;/p&gt;

&lt;h2&gt;
  
  
  Background
&lt;/h2&gt;

&lt;p&gt;At its heart, the Maps Static API returns a map image (default: PNG) in response to an HTTP request via a URL. The service creates a map based on URL parameters sent via the HTTP request and returns the map image as the response. For each request, specify the location to show, the image size, and optionally, the zoom level, type of map, and placement of optional markers at locations on the map.&lt;/p&gt;

&lt;p&gt;Because all of the "action" happens via URL, you can use the API to embed a Google Maps image on a web page, without requiring JavaScript or dynamic page loading. You can also temporarily store the image binary and render it later.&lt;/p&gt;

&lt;p&gt;However, you cannot distribute the map image, sell it, transfer it to any 3rd-party service, cache or store it for more than 30 days or in an insecure way, manipulate it, or modify its attribution in any way. These and other restrictions can be found in the &lt;a href="https://developers.google.com/maps/terms-20180207#section_10_5" rel="noopener noreferrer"&gt;Maps API Terms of Service&lt;/a&gt;. Now that you know what it is, let's look at a few examples of how to use the Maps Static API.&lt;/p&gt;

&lt;h2&gt;
  
  
  Parameters
&lt;/h2&gt;

&lt;p&gt;The following pair of parameters are required for all Maps Static API requests:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Your API key (string); ex: &lt;code&gt;UfwmmjcXOgfV9TviafMPUjYXVP-iQAWbqRmZ7WE&lt;/code&gt; (not a real one)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Image dimensions (string); ex: &lt;code&gt;480x480&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Maps Static API required parameters



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;The location parameters in the table below are pseudo-required. If you wish to put a specific location (or locations) on a map with a marker, then the &lt;code&gt;markers&lt;/code&gt; parameter is required. Otherwise, both &lt;code&gt;center&lt;/code&gt; and &lt;code&gt;zoom&lt;/code&gt; parameters are required.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;markers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Location by which to center the map image highlighted with a marker (string); exs:&lt;code&gt;Googleplex&lt;/code&gt;, &lt;code&gt;1600 Amphitheatre 94043&lt;/code&gt;, "&lt;code&gt;37.4226277,-122.0841644&lt;/code&gt;", etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;center&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Location by which to center the map image with (string); exs: &lt;em&gt;(same as &lt;code&gt;markers&lt;/code&gt; above)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zoom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Magnification of generated map (integer from 1-20, inclusive); ex: &lt;code&gt;16&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Maps Static API pseudo-required parameters



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;For &lt;code&gt;center&lt;/code&gt; or &lt;code&gt;markers&lt;/code&gt;, you need to provide a minimal string for the Maps service to find the location. Examples include a landmark name, street address, city &amp;amp; state, postal code, or latitude-longitude pair, to name a few.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;zoom&lt;/code&gt; level is an integer representing the magnification of the generated map. It must be an integer between 1 and 20. A &lt;code&gt;zoom&lt;/code&gt; level is optional if &lt;code&gt;markers&lt;/code&gt; is provided, defaulting to a zoom level of 16. The table below illustrates zoom levels and representative magnification views:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Zoom level&lt;/th&gt;
&lt;th&gt;Representative view&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;World&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;5&lt;/td&gt;
&lt;td&gt;Landmass/continent&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;City&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;td&gt;Streets&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;16&lt;/td&gt;
&lt;td&gt;&lt;em&gt;(default)&lt;/em&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;20&lt;/td&gt;
&lt;td&gt;Buildings&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Maps Static API zoom levels



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Examples
&lt;/h2&gt;

&lt;p&gt;Below are numerous examples of using the API, demonstrating its capabilities as well as how to use the parameters above.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example set 1: Basic location with markers
&lt;/h3&gt;

&lt;p&gt;Let's say you wanted a map of Google's headquarters, known as the "Googleplex" (or other landmarks or locations)&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map with marker on landmark&lt;/strong&gt;: Issue the following request with the &lt;code&gt;key&lt;/code&gt;, &lt;code&gt;size&lt;/code&gt;, and &lt;code&gt;markers&lt;/code&gt; parameters:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26signature%3DP6n5u5BHGbx3V8I2h1rh8AH9BL0%3D" alt="Map pinned to landmark" width="480" height="480"&gt;Map pinned to landmark

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map with marker on street address&lt;/strong&gt;: If you prefer using &lt;a href="https://developers.google.com/maps/documentation/maps-static/start#Addresses" rel="noopener noreferrer"&gt;street addresses&lt;/a&gt; instead:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=1600+Amphitheatre+94043&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3D1600%2BAmphitheatre%2B94043%26signature%3DNHASo5yB3jnpI-n3xSzIGlOQUQs%3D" alt="Map pinned to street address" width="480" height="480"&gt;Map pinned to street address

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map with marker on geolocation&lt;/strong&gt;: If you prefer using &lt;a href="https://developers.google.com/maps/documentation/maps-static/start#Latlons" rel="noopener noreferrer"&gt;geolocation (latitude-longitude pairs)&lt;/a&gt;:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=37.4226277,-122.0841644&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3D37.4226277%2C-122.0841644%26signature%3DBt1Ld0FkoMww_WESeRPpMrb1PJI%3D" alt="Map pinned to geolocation" width="480" height="480"&gt;Map pinned to geolocation

 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Notice that the three markers don't land on &lt;em&gt;exactly&lt;/em&gt; the same spot even though the destination is the same. In certain situations, you may prefer &lt;strong&gt;not&lt;/strong&gt; to use markers, so let's explore that.&lt;/p&gt;

&lt;h3&gt;
  
  
  Example set 2: Basic location without markers
&lt;/h3&gt;

&lt;p&gt;Because &lt;code&gt;markers&lt;/code&gt; is used on all the above examples, the &lt;code&gt;zoom&lt;/code&gt; level is optional, defaulting to 16. If you want the same maps as the above but &lt;em&gt;without&lt;/em&gt; markers on maps, provide both &lt;code&gt;center&lt;/code&gt; and &lt;code&gt;zoom&lt;/code&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Map of landmark:&lt;/strong&gt;
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;zoom=16&amp;amp;center=Googleplex&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26zoom%3D16%26center%3DGoogleplex%26signature%3DKZeOv5zP7lSe_E7u1S7sVZ24fjg%3D" alt="Map of landmark" width="480" height="480"&gt;Map of landmark

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map of street address:&lt;/strong&gt;
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;zoom=16&amp;amp;center=1600+Amphitheatre+94043&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26zoom%3D16%26center%3D1600%2BAmphitheatre%2B94043%26signature%3DsX8dqjUBqEV9w08BmgqiDSHCZOs%3D" alt="Map of street address" width="480" height="480"&gt;Map of street address

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Map of/from geolocation:&lt;/strong&gt;
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;zoom=16&amp;amp;center=37.4226277,-122.0841644&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26zoom%3D16%26center%3D37.4226277%2C-122.0841644%26signature%3DPCYW6EhpiGArhmXBjFEY8kFh7O0%3D" alt="Map of/from geolocation" width="480" height="480"&gt;Map of geolocation

 &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example set 3: Types &amp;amp; Sizes
&lt;/h3&gt;

&lt;p&gt;While &lt;code&gt;size&lt;/code&gt; is required, there's no reason to always use &lt;code&gt;480x480&lt;/code&gt; or for it to be a square. Want a wider, thinner map "strip" instead? Change &lt;code&gt;size&lt;/code&gt; accordingly. What a different type of map? Use the &lt;code&gt;maptype&lt;/code&gt; parameter. Here are some examples:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Change &lt;code&gt;size&lt;/code&gt; dimensions to reshape map&lt;/strong&gt;
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x128&amp;amp;markers=Googleplex&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D640x128%26markers%3DGoogleplex%26signature%3Dv6GNNfY63FAz0dKNNdy39NZFaAk%3D" alt="Wider, thinner map strip around the Googleplex" width="640" height="128"&gt;Wider, thinner map strip around the Googleplex

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;terrain&lt;/code&gt; map type:&lt;/strong&gt; The &lt;code&gt;maptype&lt;/code&gt; parameter has &lt;code&gt;roadmap&lt;/code&gt; as its default value, so use it to &lt;a href="https://developers.google.com/maps/documentation/maps-static/start#MapTypes" rel="noopener noreferrer"&gt;specify a different map type&lt;/a&gt;, like this example of the Googleplex in a terrain map:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&amp;amp;maptype=terrain&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26maptype%3Dterrain%26signature%3DuzFOXb_jcOTgsWMvCMcvNViLA3o%3D" alt="Terrain map around the Googleplex" width="480" height="480"&gt;Terrain map around the Googleplex

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;satellite&lt;/code&gt; map type:&lt;/strong&gt;
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&amp;amp;maptype=satellite&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26maptype%3Dsatellite%26signature%3DYIF-aJQykSvJwWIlyzJg15iWc-s%3D" alt="Satellite map around the Googleplex" width="480" height="480"&gt;Satellite map around the Googleplex

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;hybrid&lt;/code&gt; map type:&lt;/strong&gt; The &lt;code&gt;hybrid&lt;/code&gt; map type combines &lt;code&gt;roadmap&lt;/code&gt; and &lt;code&gt;satellite&lt;/code&gt;:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&amp;amp;maptype=hybrid&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26maptype%3Dhybrid%26signature%3DTl1BEA2WIexXDbegQBL4eJ4vVe8%3D" alt="Hybrid map around the Googleplex" width="480" height="480"&gt;Hybrid map around the Googleplex

 &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example set 4: Zoom level
&lt;/h3&gt;

&lt;p&gt;Desire a different zoom level? Change it with the &lt;code&gt;zoom&lt;/code&gt; parameter... increasing it increases the magnification and vice versa:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Zooming in&lt;/strong&gt;:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;zoom=18&amp;amp;markers=Googleplex&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26zoom%3D18%26markers%3DGoogleplex%26signature%3DgxhDEiNcg3uM2aw0L7PAo3ZW4zc%3D" alt="Zooming in on the Googleplex" width="480" height="480"&gt;Zooming in on the Googleplex

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Zooming out&lt;/strong&gt;:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;zoom=14&amp;amp;markers=Googleplex&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26zoom%3D14%26markers%3DGoogleplex%26signature%3D3gWIydAnbpgZSaVLYT5yqZ6fLQw%3D" alt="Zooming out from the Googleplex" width="480" height="480"&gt;Zooming out from the Googleplex

 &lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Example set 5: Combos &amp;amp; Colors
&lt;/h3&gt;

&lt;p&gt;Markers have some flexibility. You can place more than one marker and/or change their colors.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Additional markers:&lt;/strong&gt; In the zoom-out map above, you see another landmark nearby, "Google Visitor Experience." Want to put a marker on &lt;em&gt;that&lt;/em&gt; landmark as well? Provide an additional &lt;code&gt;markers&lt;/code&gt; parameter or better yet, have a single parameter with locations separated by a pipe (&lt;code&gt;|&lt;/code&gt;) symbol:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex|Google+Visitor+Experience&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%257CGoogle%2BVisitor%2BExperience%26signature%3DVzcoHoS352exJ1YuLJ2uFwMG7ZY%3D" alt="Multiple markers on a map" width="480" height="480"&gt;Multiple markers on a map

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Change marker colors:&lt;/strong&gt; Want to change the default color? Provide &lt;code&gt;markers&lt;/code&gt; with a secondary parameter, &lt;code&gt;color:COLOR&lt;/code&gt;, also separated with a pipe, before the locations. Here's an example featuring blue markers:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=color:blue|Googleplex|Google+Visitor+Experience&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3Dcolor%3Ablue%257CGoogleplex%257CGoogle%2BVisitor%2BExperience%26signature%3D2RugKPoltqQ3Umu6D4hDA4XwJI8%3D" alt="Changing map marker color" width="480" height="480"&gt;Changing map marker color

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Different marker colors:&lt;/strong&gt; Changing the default marker color sets all markers to that color. To have different markers with different colors, multiple &lt;code&gt;markers&lt;/code&gt; parameters are required. Here's an example where the Googleplex gets a default red marker while the Google Visitor Experience gets a green one:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&amp;amp;markers=color:green|Google+Visitor+Experience&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26markers%3Dcolor%3Agreen%257CGoogle%2BVisitor%2BExperience%26signature%3DOmhUQR94-2XqOT9mAzW6AnYmOXU%3D" alt="Different marker colors" width="480" height="480"&gt;Different marker colors

 &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image formats:&lt;/strong&gt; Don't like PNG, the default image format? &lt;a href="https://developers.google.com/maps/documentation/maps-static/start#ImageFormats" rel="noopener noreferrer"&gt;Request other formats&lt;/a&gt; like GIF or JPG. Here's an example that outputs a JPG map with red &amp;amp; brown markers:
&lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&amp;amp;markers=color:brown|Google+Visitor+Experience&amp;amp;format=jpg&lt;/code&gt;
&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fmaps.googleapis.com%2Fmaps%2Fapi%2Fstaticmap%3Fkey%3DAIzaSyAW-FKhzDYdlsVTvFDh1hbGvrk9Pke6atY%26size%3D480x480%26markers%3DGoogleplex%26markers%3Dcolor%3Abrown%257CGoogle%2BVisitor%2BExperience%26format%3Djpg%26signature%3DdlD8-w2gZMadOmrUtzlAfDpESY4%3D" alt="Colored marker JPG map image" width="480" height="480"&gt;Colored marker JPG map image

 &lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;code&gt;color&lt;/code&gt; parameter is either a 24-bit value, e.g., &lt;code&gt;color=0xFFFFCC&lt;/code&gt;, or one of the predefined colors: &lt;code&gt;black&lt;/code&gt;, &lt;code&gt;brown&lt;/code&gt;, &lt;code&gt;green&lt;/code&gt;, &lt;code&gt;purple&lt;/code&gt;, &lt;code&gt;yellow&lt;/code&gt;, &lt;code&gt;blue&lt;/code&gt;, &lt;code&gt;gray&lt;/code&gt;, &lt;code&gt;orange&lt;/code&gt;, &lt;code&gt;red&lt;/code&gt;, &lt;code&gt;white&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The above are just &lt;em&gt;some&lt;/em&gt; of the ways you can use the Maps Static API. There are plenty of other customizations available. For more information on stylizing maps, see the &lt;a href="https://developers.google.com/maps/documentation/maps-static/styling" rel="noopener noreferrer"&gt;styling guide&lt;/a&gt;. The complete &lt;a href="https://developers.google.com/maps/documentation/maps-static/start" rel="noopener noreferrer"&gt;developers' guide&lt;/a&gt; outlines what else the Maps Static API can do.&lt;/p&gt;

&lt;p&gt;While it's simple to call the Maps Static API as a URL, more than likely, you'll have an application generate the required parameters to render the desired map in your web application, and we'll address that in the next post. Want to try the examples above? Let's get you an API key!&lt;/p&gt;

&lt;h2&gt;
  
  
  Google developer project setup
&lt;/h2&gt;

&lt;p&gt;These are the steps necessary before creating an API key:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Designate a developer project&lt;/em&gt;&lt;/strong&gt; -- either &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects#creating_a_project" rel="noopener noreferrer"&gt;create a new project&lt;/a&gt; or reuse an existing one&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable billing&lt;/em&gt;&lt;/strong&gt; -- Google Maps Platform usage isn't free, so you need an active billing account (more on this below)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable Maps Static API&lt;/em&gt;&lt;/strong&gt; -- all Google APIs must be enabled before they can be used. If you already enabled billing by the time you get here, the API will be enabled quickly. Otherwise, you will be prompted to enable billing when you enable the Maps Static API.&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;❗ &lt;strong&gt;Billing account required&lt;/strong&gt;
&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;While many Google APIs are free to use, Maps APIs are not. However, all users are granted a &lt;a href="https://mapsplatform.google.com/pricing" rel="noopener noreferrer"&gt;free monthly quota of $200USD of usage&lt;/a&gt; before incurring billing. Review the &lt;a href="https://developers.google.com/maps/billing-and-pricing/billing" rel="noopener noreferrer"&gt;Maps Platform billing page&lt;/a&gt; to understand all the terms and conditions, then create a billing account backed by a credit card or other financial instrument as described. Once you have a billing account, assign it to your project.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's walk through the steps above one-at-a-time:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Create a new project&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://console.cloud.google.com/projectcreate" rel="noopener noreferrer"&gt;from the Cloud/developer console&lt;/a&gt; or with the &lt;code&gt;gcloud projects create . . .&lt;/code&gt; command; alternatively, reuse an existing project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable billing&lt;/em&gt;&lt;/strong&gt; &lt;a href="https://console.cloud.google.com/billing" rel="noopener noreferrer"&gt;from the Cloud/developer console&lt;/a&gt;. Refer to the &lt;a href="https://cloud.google.com/billing/docs/how-to/manage-billing-account" rel="noopener noreferrer"&gt;Cloud billing documentation&lt;/a&gt; to learn more. After creating the billing account, link it to your project.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;em&gt;Enable Maps Static API&lt;/em&gt;&lt;/strong&gt;. With a project and linked billing account, it's time to enable the Maps Static API. Choose your preferred method from these three common ways to enable Google APIs:

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;DevConsole manually&lt;/strong&gt; -- Enable API manually from DevConsole with these steps:

&lt;ol&gt;
&lt;li&gt;Go to &lt;a href="http://console.developers.google.com" rel="noopener noreferrer"&gt;DevConsole&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Library&lt;/strong&gt; tab in the left-nav; search for "Maps Static"&lt;/li&gt;
&lt;li&gt;Click on &lt;strong&gt;Maps Static API&lt;/strong&gt; then click the blue &lt;strong&gt;Enable&lt;/strong&gt; button (see image below)&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;DevConsole link&lt;/strong&gt; -- You may be new to Google APIs or don't have experience enabling APIs manually in the DevConsole. If this is you...

&lt;ol&gt;
&lt;li&gt;Go directly to &lt;a href="https://console.cloud.google.com/apis/library/static-maps-backend.googleapis.com" rel="noopener noreferrer"&gt;API listing page&lt;/a&gt; to read more about it and click Enable from there (same image below)&lt;/li&gt;
&lt;li&gt;Alternatively, skip the API info and click &lt;a href="http://console.developers.google.com/start/api?id=static-maps-backend.googleapis.com" rel="noopener noreferrer"&gt;this link&lt;/a&gt; for an enable-only button.&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;li&gt;

&lt;strong&gt;Command-line&lt;/strong&gt; (&lt;code&gt;gcloud&lt;/code&gt;) -- Those who prefer working in a terminal can enable APIs with a single command in the &lt;a href="https://cloud.google.com/shell" rel="noopener noreferrer"&gt;Cloud Shell&lt;/a&gt; or locally on your computer if you &lt;a href="https://cloud.google.com/sdk/install" rel="noopener noreferrer"&gt;installed the Cloud SDK&lt;/a&gt; which includes the &lt;code&gt;gcloud&lt;/code&gt; command-line tool (CLI) and initialized its use.

&lt;ol&gt;
&lt;li&gt;If this is you, issue this command to enable the API: &lt;code&gt;gcloud services enable static-maps-backend.googleapis.com&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Confirm all the APIs you've enabled with this command: &lt;code&gt;gcloud services list&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;


&lt;/li&gt;

&lt;/ul&gt;

&lt;/li&gt;

&lt;/ol&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpckmtcsachk9qoa0cb47.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpckmtcsachk9qoa0cb47.png" alt="Maps Static API listing page" width="663" height="355"&gt;&lt;/a&gt;&lt;/p&gt;
Maps Static API listing page



&lt;p&gt; &lt;/p&gt;

&lt;h2&gt;
  
  
  Create API key
&lt;/h2&gt;

&lt;p&gt;With the prerequisites complete, it's time to create the API key to use with your Maps Static API requests. Follow these steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Go to the &lt;a href="https://console.cloud.google.com/apis/credentials" rel="noopener noreferrer"&gt;DevConsole credentials page&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Click &lt;strong&gt;+ Create Credentials&lt;/strong&gt; up top&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;API key&lt;/strong&gt; and wait a few seconds for completion&lt;/li&gt;
&lt;li&gt;Copy and save API key somewhere safe you can refer to&lt;/li&gt;
&lt;/ol&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ &lt;strong&gt;WARNING: Keep API keys secure&lt;/strong&gt;&lt;br&gt;
Storing API keys in files (or hard-coding them for use in actual code or even assigning to environment variables) is for prototyping and learning purposes only. When going to production, put them in environment variables or in a secrets manager. Files like &lt;code&gt;settings.py&lt;/code&gt; or &lt;code&gt;.env&lt;/code&gt; containing API keys are susceptible. &lt;strong&gt;&lt;em&gt;Under no circumstances&lt;/em&gt;&lt;/strong&gt; should you &lt;a href="https://www.gitguardian.com/glossary/remediate-sensitive-data-leaks-api-keys-hardcoded-source-code" rel="noopener noreferrer"&gt;upload files like those to any public or private repo&lt;/a&gt;, &lt;a href="https://spacelift.io/blog/terraform-secrets" rel="noopener noreferrer"&gt;have sensitive data like that in TerraForm config files&lt;/a&gt;, &lt;a href="https://www.darkreading.com/cloud-security/docker-leaks-api-secrets-private-keys-cybercriminals" rel="noopener noreferrer"&gt;add such files to Docker layers&lt;/a&gt;, etc., as once your API key leaks, everyone in the world can use it.&lt;/p&gt;

&lt;p&gt;If you're new to Google developer tools, &lt;a href="https://dev.to/wescpy/series/25404"&gt;&lt;em&gt;API keys&lt;/em&gt;&lt;/a&gt; are one of the credentials types supported by Google APIs, and they're the &lt;em&gt;only&lt;/em&gt; type supported by Maps APIs. Other credentials types include &lt;a href="https://dev.to/wescpy/series/25403"&gt;&lt;em&gt;OAuth client IDs&lt;/em&gt;&lt;/a&gt;, mostly used by GWS APIs, and &lt;em&gt;service accounts&lt;/em&gt;, mostly used by Google Cloud (GCP) APIs. The Maps team put together a great &lt;a href="https://developers.google.com/maps/api-security-best-practices#rec-best-practices" rel="noopener noreferrer"&gt;guide on API key best practices&lt;/a&gt;, so check it out!&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Summary and next steps
&lt;/h2&gt;

&lt;p&gt;Now armed with an API key, give it a shot, and take a look at a map of the Googleplex by substituting in your API key into the &lt;code&gt;YOUR_API_KEY&lt;/code&gt; placeholder: &lt;code&gt;https://maps.googleapis.com/maps/api/staticmap?key=YOUR_API_KEY&amp;amp;size=480x480&amp;amp;markers=Googleplex&lt;/code&gt; as well as the other sample API calls from earlier. In the next post, we'll look at Python and Node.js code using the API so look for that!&lt;/p&gt;

&lt;p&gt;Google provides a variety of APIs for developers across most of its product lines. But of all its 480+ APIs, I think the Maps Static API is the easiest to use: no client library, no API client objects, no API function/method calls, and no code required either! A URL with parameters and an API key is all that's needed. What do you think? If you can think of an &lt;em&gt;easier&lt;/em&gt; Google API to use, let me know in a comment below!&lt;/p&gt;

&lt;p&gt;If you find errors or have suggestions on content you'd like to see in future posts, also leave a comment below, and if your organization needs help integrating Google technologies via its APIs, reach out to me by submitting a request at &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;https://cyberwebconsulting.com&lt;/a&gt;. Thanks for reading, and I hope to meet you if I come through your community... you'll find my travel calendar at the bottom of that page as well. Lastly, below is reference information plus relevant links for further exploration, and if you want to continue exploring Maps APIs, see the previous post in this series:&lt;/p&gt;

&lt;p&gt;NEXT POST: &lt;a href="https://dev.to/wescpy/explore-the-world-with-google-maps-apis-lhj"&gt;Explore the world with Google Maps APIs&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Here is a summary table of the Maps Static API parameters:&lt;/p&gt;

&lt;h3&gt;
  
  
  Maps Static API parameters
&lt;/h3&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Parameter&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;key&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(&lt;strong&gt;required&lt;/strong&gt;) Your API key (string); ex: &lt;code&gt;UfwmmjcXOgfV9TviafMPUjYXVP-iQAWbqRmZ7WE&lt;/code&gt; (not a real one)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;size&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(&lt;strong&gt;required&lt;/strong&gt;) Image dimensions (string); ex: &lt;code&gt;480x480&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;markers&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(&lt;strong&gt;required&lt;/strong&gt; without &lt;code&gt;center&lt;/code&gt; and &lt;code&gt;zoom&lt;/code&gt;) Location by which to center the map image highlighted with a marker (string); exs:&lt;code&gt;Googleplex&lt;/code&gt;, &lt;code&gt;1600 Amphitheatre 94043&lt;/code&gt;, "&lt;code&gt;37.4226277,-122.0841644&lt;/code&gt;", etc.&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;center&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(&lt;strong&gt;required&lt;/strong&gt; without &lt;code&gt;markers&lt;/code&gt;) Location by which to center the map image with (string); exs: &lt;em&gt;(same as &lt;code&gt;markers&lt;/code&gt; above)&lt;/em&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;zoom&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;(&lt;strong&gt;required&lt;/strong&gt; without &lt;code&gt;markers&lt;/code&gt;) Magnification of generated map (integer from 1-20, inclusive); ex: &lt;code&gt;16&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maptype&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Type of map to return; ex: &lt;code&gt;terrain&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;
Google Maps Static API parameters



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Links relevant to the contents of this post:&lt;/p&gt;

&lt;h3&gt;
  
  
  Maps Static API
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static/overview" rel="noopener noreferrer"&gt;Product overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static/start" rel="noopener noreferrer"&gt;Getting Started/Developers guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static/styling" rel="noopener noreferrer"&gt;Maps styling guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static/usage-and-billing" rel="noopener noreferrer"&gt;Usage &amp;amp; billing page&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Maps APIs
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/api-picker" rel="noopener noreferrer"&gt;Choosing a Maps API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation/maps-static/get-api-key" rel="noopener noreferrer"&gt;Getting &amp;amp; using API keys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/api-security-best-practices" rel="noopener noreferrer"&gt;API keys best practices&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/faq#usage_apis" rel="noopener noreferrer"&gt;API quotas &amp;amp; limits&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google Maps Platform
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://mapsplatform.google.com" rel="noopener noreferrer"&gt;Platform home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://mapsplatform.google.com/pricing" rel="noopener noreferrer"&gt;Platform pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/billing-and-pricing/pricing" rel="noopener noreferrer"&gt;Detailed pricing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps" rel="noopener noreferrer"&gt;Developers home&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://developers.google.com/maps/documentation" rel="noopener noreferrer"&gt;Developers docs&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other Google Maps content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://dev.to/wescpy/explore-the-world-with-google-maps-apis-lhj"&gt;Explore the world with Google Maps APIs&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://dev.to/googleworkspace/getting-started-using-google-apis-api-keys-part-2-38i6"&gt;Getting started using Google APIs: API Keys (Part 2/2)&lt;/a&gt; post&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://goo.gl/oAzBN9" rel="noopener noreferrer"&gt;Accessing Google Maps from a spreadsheet?!?&lt;/a&gt; video&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for Linux Journal &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with their GCP &amp;amp; GWS API needs, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>api</category>
      <category>webdev</category>
      <category>google</category>
      <category>learning</category>
    </item>
    <item>
      <title>Hosting apps in the cloud today with Google App Engine</title>
      <dc:creator>Wesley Chun (@wescpy)</dc:creator>
      <pubDate>Tue, 22 Oct 2024 08:12:56 +0000</pubDate>
      <link>https://dev.to/gde/hosting-apps-in-the-cloud-with-google-app-engine-3fn</link>
      <guid>https://dev.to/gde/hosting-apps-in-the-cloud-with-google-app-engine-3fn</guid>
      <description>&lt;h2&gt;
  
  
  TL;DR:
&lt;/h2&gt;

&lt;p&gt;If you're looking to put code online today, you have many options, with a VM or Kubernetes as the best choice for some. Others with less code may consider (AWS) Lambda. There are also options where developers don't have to consider traditional servers... these are known as &lt;em&gt;serverless&lt;/em&gt; (compute) platforms. Google Cloud (GCP) offers several serverless options, and while Google (and I) will encourage developers to go straight to Cloud Run, it would be remiss not to start the conversation with their "OG" serverless platform, App Engine, as it still does a few things that Cloud Run can't or won't do. Cloud Run will be covered in future posts.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvc74h6wc0boa6zj6q33x.png" alt="Serverless computing with Google" width="800" height="334"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] Serverless computing with Google



&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Welcome to the blog focused on using Google APIs, developer tools, and platforms, primarily from Python, but also sometimes Node.js. Here, you'll see me cover everything from to &lt;a href="https://dev.to/wescpy/text-based-language-processing-enhanced-with-aiml-1b1h"&gt;AI/ML&lt;/a&gt; on Google Cloud/GCP to &lt;a href="https://dev.to/wescpy/explore-the-world-with-google-maps-apis-lhj"&gt;Google Maps&lt;/a&gt; to &lt;a href="https://dev.to/wescpy/intro-to-the-youtube-apis-searching-for-videos-5a0o"&gt;YouTube&lt;/a&gt; to &lt;a href="https://dev.to/wescpy/series/27183"&gt;Gemini&lt;/a&gt;, and also nut-n-bolts like &lt;a href="https://dev.to/wescpy/series/25404"&gt;API key&lt;/a&gt;, and &lt;a href="https://dev.to/wescpy/series/25403"&gt;OAuth client ID&lt;/a&gt; credentials, especially for &lt;a href="https://dev.to/wescpy/series/23343"&gt;Workspace/GWS APIs&lt;/a&gt;. The aim is to provide something for &lt;em&gt;everyone&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The purpose of this post is to directly address the question, "Where can I host my app in the cloud?" More specifically, "How can I do so with the least amount of setup and friction?" The answers to both will almost always point to &lt;em&gt;serverless&lt;/em&gt;, a subset of cloud computing focused on &lt;em&gt;logic&lt;/em&gt;-hosting.&lt;/p&gt;

&lt;h2&gt;
  
  
  Serverless computing and logic-hosting
&lt;/h2&gt;

&lt;p&gt;"Logic" means any code, whether a short code snippet to a web app to a container running a service. So "logic-hosting" is an all-encompassing term that includes app-hosting (hosting your apps in the cloud), function-hosting (hosting your functions in the cloud; FaaS), and container-hosting (hosting your containers in the cloud; CaaS).&lt;/p&gt;

&lt;p&gt;All of that hosting can be accomplished with serverless computing, the ability to host that logic without thinking about or being concerned with servers, virtual machines or bare-metal. It's a bit of a misnomer because &lt;em&gt;of course&lt;/em&gt; there are servers, but they've been abstracted away from users. Developers only need to focus on building their solutions, not what they run on. Learn more in the serverless post I'm about to introduce below.&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Google offers various serverless platforms for developers, and I introduce four (4) of them in the &lt;a href="https://dev.to/wescpy/a-broader-perspective-of-serverless-1md1"&gt;"Broader Perspective of Serverless" post&lt;/a&gt;. The majority of the platforms covered are offered by GCP, and I want to dive into each. Setting aside the use cases of functions and microservices for Cloud Functions (GCF) and Cloud Run Functions (GCRF), the platforms most suitable for applications (web apps primarily) are Google App Engine (GAE) and Cloud Run (GCR).&lt;/p&gt;

&lt;p&gt;While Google and I encourage developers to start with GCR, GAE may still be the right tool for a certain class of users who prefer a bit less sophistication and core "building blocks" for building apps. GAE still has some features not available in GCR and is a pure PaaS solution while GCR falls between the PaaS and IaaS categories of cloud computing, meaning you have to do a few more things (like bundle a web server and tell GCR how to start your app) whereas it's taken care of by GAE.&lt;/p&gt;

&lt;p&gt;With this in mind, I feel it's a better approach to start with GAE before diving into GCR, so here we are. The intention of the rest of the post is to show Python (3) and Node.js developers how to get a "Hello World!" app up-and-running on GAE with the least amount of friction. I'll then do the same with GCR and the functions/FaaS platforms in upcoming posts.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;⚠️ ALERT: &lt;strong&gt;Cost: billing required (but free?!?)&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;While many Google products are free to use, GCP products are not. In order to run the sample apps, you must &lt;a href="http://console.cloud.google.com/billing" rel="noopener noreferrer"&gt;enable billing&lt;/a&gt; and have active billing supported by a financial instrument like a &lt;a href="https://cloud.google.com/appengine/docs/standard/payment-instrument" rel="noopener noreferrer"&gt;credit card&lt;/a&gt; (&lt;a href="https://support.google.com/paymentscenter/answer/9001356?ref_topic=9023854#allowed-methods" rel="noopener noreferrer"&gt;payment method depends on region/currency&lt;/a&gt;). If you're new to GCP, review the &lt;a href="https://cloud.google.com/billing/docs/onboarding-checklist" rel="noopener noreferrer"&gt;billing and onboarding guide&lt;/a&gt;. Billing wasn't mandatory to use GAE in the past but &lt;a href="https://cloud.google.com/appengine/docs/standard/payment-instrument" rel="noopener noreferrer"&gt;became a requirement in 2019&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The good news is that GAE (and other GCP products) have an &lt;a href="https://cloud.google.com/free/docs/gcp-free-tier#free-tier-usage-limits" rel="noopener noreferrer"&gt;"Always Free" tier&lt;/a&gt;, a free daily or monthly usage before incurring charges. The GAE &lt;a href="https://cloud.google.com/appengine/pricing" rel="noopener noreferrer"&gt;pricing&lt;/a&gt; and &lt;a href="https://cloud.google.com/appengine/quotas" rel="noopener noreferrer"&gt;quotas&lt;/a&gt; pages have more information. Furthermore, deploying to GCP serverless platforms incur &lt;a href="https://cloud.google.com/appengine/pricing#pricing-for-related-google-cloud-products" rel="noopener noreferrer"&gt;minor build and storage costs&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://cloud.google.com/build/pricing" rel="noopener noreferrer"&gt;Cloud Build&lt;/a&gt; has its own free quota as does &lt;a href="https://cloud.google.com/storage/pricing#cloud-storage-always-free" rel="noopener noreferrer"&gt;Cloud Storage&lt;/a&gt; (GCS). For greater transparency, Cloud Build builds your application image which is than sent to the Cloud &lt;a href="https://cloud.google.com/container-registry/pricing" rel="noopener noreferrer"&gt;Container Registry&lt;/a&gt; (CR) or &lt;a href="https://cloud.google.com/artifact-registry/pricing" rel="noopener noreferrer"&gt;Artifact Registry&lt;/a&gt; (AR), its successor. Storage of that image uses up some of that GCS quota as does network egress when transferring that image to the service you're deploying to. However you may live in a region that does not have a free tier, so monitor storage usage to minimize potential costs. (Check out storage use and delete old/unwanted build artifacts via the &lt;a href="https://console.cloud.google.com/storage/browser" rel="noopener noreferrer"&gt;GCS browser&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;With the above said, you may qualify for credits to offset any costs of using GCP. If you are a startup, consider applying for the &lt;a href="https://cloud.google.com/startup" rel="noopener noreferrer"&gt;GCP for Startups&lt;/a&gt; program grants. If you are a higher education student, faculty member, researcher, or are an education technology ("edtech") company, explore all the &lt;a href="https://cloud.google.com/edu" rel="noopener noreferrer"&gt;GCP for education programs&lt;/a&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  Getting started
&lt;/h2&gt;

&lt;p&gt;Let's see how easy it is to get started with GAE. The shortest/smallest app or "MVP" (minimally-viable product) requires just three (3) files:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;GAE configuration&lt;/li&gt;
&lt;li&gt;3rd-party requirements&lt;/li&gt;
&lt;li&gt;Main application&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Both samples are available in the &lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine" rel="noopener noreferrer"&gt;repo for this post&lt;/a&gt;. Let's walk through each code sample, run locally, then do some GCP setup and deploy to GAE; Python first.&lt;/p&gt;

&lt;h2&gt;
  
  
  Python
&lt;/h2&gt;

&lt;p&gt;The GAE configuration file is (always) named &lt;code&gt;app.yaml&lt;/code&gt; (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/python3/app.yaml" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;), and while &lt;a href="https://cloud.google.com/appengine/docs/standard/reference/app-yaml" rel="noopener noreferrer"&gt;many options are available&lt;/a&gt;, an MVP only requires specifying the &lt;a href="https://cloud.google.com/appengine/docs/standard/lifecycle/support-schedule" rel="noopener noreferrer"&gt;language runtime ID&lt;/a&gt;. In this sample app, I set it to Python 3.12:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;python312&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Python's &lt;code&gt;requirements.txt&lt;/code&gt; file (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/python3/requirements.txt" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;) is used for specifying all 3rd-party libraries, and in this case, it's just the Flask micro web framework:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;flask
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Name the main application file anything you want; I settled on &lt;code&gt;main.py&lt;/code&gt; (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/python3/main.py" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;), shown here:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;flask&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Flask&lt;/span&gt;

&lt;span class="n"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Flask&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;__name__&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="nd"&gt;@app.route&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;/&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;root&lt;/span&gt;&lt;span class="p"&gt;():&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;Hello World!&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;

&lt;span class="c1"&gt;# local-only
&lt;/span&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;__name__&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;__main__&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;os&lt;/span&gt;
    &lt;span class="n"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;run&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;debug&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;threaded&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;True&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;host&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;0.0.0.0&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="n"&gt;port&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nf"&gt;int&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;os&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;environ&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="s"&gt;PORT&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;)))&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Flask library is imported, then the Flask app instantiated. The only route in this app is to '/' which returns &lt;code&gt;'Hello World!'&lt;/code&gt; from the (default) &lt;code&gt;GET&lt;/code&gt; request (browser, &lt;code&gt;curl&lt;/code&gt;, etc.). You can name route functions anything you like, such as &lt;code&gt;main()&lt;/code&gt; or &lt;code&gt;index()&lt;/code&gt; -- I often use &lt;code&gt;root()&lt;/code&gt;. The last few lines are only for running this app locally... drop this section entirely if only deploying to the cloud. It uses the &lt;code&gt;os&lt;/code&gt; module to read the &lt;code&gt;PORT&lt;/code&gt; environment variable and fires up the Flask "devserver" (development server) on port 8080 if &lt;code&gt;PORT&lt;/code&gt; isn't set.&lt;/p&gt;

&lt;p&gt;Now run the script to test the app locally; your (terminal) output should look something like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ python3 main.py
 * Serving Flask app 'main'
 * Debug mode: on
WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead.
 * Running on all addresses (0.0.0.0)
 * Running on http://127.0.0.1:8080
 * Running on http://172.20.20.20:8080
Press CTRL+C to quit
 * Restarting with watchdog (fsevents)
 * Debugger is active!
 * Debugger PIN: 135-387-333
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;With a running server, point a web browser to &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt;:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8qib9561wm5wr2a0u0l.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fj8qib9561wm5wr2a0u0l.png" alt="" width="546" height="379"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] "Hello World!" sample app running locally (Mac)



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;In your terminal, you'll see log entries for each HTTP request:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;127.0.0.1 - - [05/Dec/2023 15:42:55] "GET / HTTP/1.1" 200 -
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To exit the devserver, issue a &lt;code&gt;^C&lt;/code&gt; (Control-C) on the command-line.&lt;/p&gt;

&lt;h2&gt;
  
  
  Node.js
&lt;/h2&gt;

&lt;p&gt;Now, let's turn to the Node.js version, which looks quite similar, starting with the config file, same name: &lt;code&gt;app.yaml&lt;/code&gt; (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/nodejs/app.yaml" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;) but specifying Node 20:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;runtime&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;nodejs20&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The Node.js 3rd-party requirements are found in the &lt;code&gt;package.json&lt;/code&gt; file (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/nodejs/package.json" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;). The Python sample relies on the Flask web framework, and similarly, this Node.js app uses the Express.js framework. The &lt;code&gt;package.json&lt;/code&gt; file looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"helloworld-nodejs"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"0.0.1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"description"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Node.js App Engine sample app"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"main"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"index.js"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"scripts"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"start"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"node index.js"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"author"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"@wescpy"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"license"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Apache-2.0"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"dependencies"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"express"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"^4.17.1"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Like with Python, call your main application anything you like. Typically with Node.js, they're named &lt;code&gt;app.js&lt;/code&gt;, &lt;code&gt;main.js&lt;/code&gt;, or what I picked: &lt;code&gt;index.js&lt;/code&gt; (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/nodejs/index.js" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;). Here are its contents:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="nx"&gt;module&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exports&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;App instantiation is followed by the main (&lt;code&gt;GET&lt;/code&gt;) handler returning "Hello World!" regardless of request content. The rest sets up the server on the designated &lt;code&gt;PORT&lt;/code&gt; and exports the &lt;code&gt;app&lt;/code&gt;. Unlike Python which automatically spawns a web server on GAE, Node.js requires you to run your own, hence why this code isn't enclosed in an &lt;code&gt;if&lt;/code&gt; block like Python.&lt;/p&gt;

&lt;p&gt;Run &lt;code&gt;npm install&lt;/code&gt; to install all the required packages, resulting in the expected &lt;code&gt;node_modules&lt;/code&gt; folder and &lt;code&gt;package-lock.json&lt;/code&gt; file. Express doesn't log individually-received requests -- developers have to add middleware to see HTTP requests, so after starting the app with &lt;code&gt;npm start&lt;/code&gt; to test the app locally, the only output you'll see until you quit the server looks like this (unlike Flask which dumps user requests to the console):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ npm start

&amp;gt; helloworld-nodejs@0.0.1 start
&amp;gt; node index.js

Listening on port 8080
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Hit this app with your web browser at &lt;a href="http://localhost:8080" rel="noopener noreferrer"&gt;http://localhost:8080&lt;/a&gt; and observe similar "Hello World!" output like with the Python app. Press &lt;code&gt;^C&lt;/code&gt; (Control-C) to quit the Express server.&lt;/p&gt;

&lt;p&gt;If you prefer to work with modern ECMAScript modules, quick &lt;code&gt;require&lt;/code&gt;-to-&lt;code&gt;import&lt;/code&gt; and module export tweaks result in the equivalent &lt;code&gt;index.mjs&lt;/code&gt; (&lt;a href="https://github.com/wescpy/google/blob/main/cloud/appengine/nodejs/index.mjs" rel="noopener noreferrer"&gt;repo link&lt;/a&gt;) ESM file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;express&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;express&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;express&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;/&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;req&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;rsp&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;send&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Hello World!&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="mi"&gt;8080&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listen&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Listening on port &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;PORT&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="k"&gt;default&lt;/span&gt; &lt;span class="nx"&gt;app&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Deploying apps to App Engine
&lt;/h2&gt;

&lt;p&gt;Now that you have something working locally, a more exciting next step is to run the app in the cloud and observe how it's accessible globally. Let's start with some GCP setup.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Cloud SDK
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;gcloud&lt;/code&gt; command-line tool (CLI) is required to deploy apps to GAE. It comes with the &lt;a href="https://cloud.google.com/sdk" rel="noopener noreferrer"&gt;Google Cloud SDK&lt;/a&gt; (software development kit). If you already have it installed, skip to the next section. If you're new to GCP, follow the &lt;a href="https://cloud.google.com/sdk/docs/install-sdk" rel="noopener noreferrer"&gt;instructions to install the SDK and learn a few &lt;code&gt;gcloud&lt;/code&gt; commands&lt;/a&gt; to get comfortable using it. If you simply want to install the SDK, see &lt;a href="https://cloud.google.com/sdk/docs/install" rel="noopener noreferrer"&gt;these instructions&lt;/a&gt; instead. After installation, run &lt;code&gt;gcloud init&lt;/code&gt; to initialize it -- you may be prompted to login.&lt;/p&gt;

&lt;h3&gt;
  
  
  Google Cloud project
&lt;/h3&gt;

&lt;p&gt;A cloud &lt;em&gt;project&lt;/em&gt; is required to manage all the resources, e.g., APIs, compute, and storage, used for an app. &lt;a href="https://cloud.google.com/resource-manager/docs/creating-managing-projects" rel="noopener noreferrer"&gt;Create a new one or reuse an existing project&lt;/a&gt;. Then &lt;a href="https://cloud.google.com/billing/docs/how-to/modify-project" rel="noopener noreferrer"&gt;assign a billing account&lt;/a&gt; to that project if you haven't already.&lt;/p&gt;

&lt;p&gt;Each project can create at most one GAE app, and it gets a free domain name: &lt;code&gt;PROJECT_ID.REG.appspot.com&lt;/code&gt; where &lt;code&gt;PROJECT_ID&lt;/code&gt; is the project ID (duh) while &lt;code&gt;REG&lt;/code&gt; is an abbreviation for the assigned region.&lt;/p&gt;

&lt;p&gt;For your convenience, set a default project with this command: &lt;code&gt;gcloud config set project PROJECT_ID&lt;/code&gt;. If you don't set a default, you'd have to pass it in with &lt;code&gt;--project PROJECT_ID&lt;/code&gt; each time you run a &lt;code&gt;gcloud&lt;/code&gt; command. Otherwise you'll be prompted for the project ID.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;📝 &lt;strong&gt;Optional Python-related installs&lt;/strong&gt;&lt;br&gt;
Python developers have a few additional, optional installations if they're of interest to you.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;App Engine SDK&lt;/strong&gt; (Python 3): If you have an existing Python 2 app using the &lt;a href="https://cloud.google.com/appengine/docs/standard/bundled-services-overview" rel="noopener noreferrer"&gt;GAE bundled services&lt;/a&gt; and considering a migration to Python 3, this is for you. The &lt;a href="https://github.com/GoogleCloudPlatform/appengine-python-standard" rel="noopener noreferrer"&gt;App Engine (services) SDK&lt;/a&gt; (not to be confused with the GCP SDK described above) gives Python 3 apps &lt;a href="https://cloud.google.com/appengine/docs/standard/python3/services/access" rel="noopener noreferrer"&gt;access to those bundled services&lt;/a&gt; bridging Gen1 apps to Gen2. Install it with: &lt;code&gt;pip install appengine-python-standard&lt;/code&gt; (or &lt;code&gt;pip3&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;GCP App Engine "extras"&lt;/strong&gt;: There are a pair of additional, (very) optional GCP components. The &lt;code&gt;app-engine-python&lt;/code&gt; component contains the local GAE devserver (not to be confused with the Flask or Express devservers) if you want to simulate bundled services locally. Another one is &lt;code&gt;app-engine-python-extras&lt;/code&gt;, which has code for Python 2 apps using built-in libraries. Since &lt;a href="https://dev.to/wescpy/python-app-engine-jan-2024-deprecation-what-you-need-to-know-4bci"&gt;2.x apps can no longer be deployed&lt;/a&gt;, you wouldn't use this. Regardless, install one or both components with a command like this: &lt;code&gt;gcloud components install app-engine-python-extras app-engine-python&lt;/code&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Final setup
&lt;/h3&gt;

&lt;p&gt;Once you've confirmed you've completed these basic tasks, you should be ready to deploy your app:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Install GCP SDK (includes &lt;code&gt;gcloud&lt;/code&gt; CLI)&lt;/li&gt;
&lt;li&gt;Create new or reuse existing GCP project&lt;/li&gt;
&lt;li&gt;Enable billing &amp;amp; assign billing account&lt;/li&gt;
&lt;li&gt;Run &lt;code&gt;gcloud init&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;(optional) Run &lt;code&gt;gcloud config set project PROJECT_ID&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;(optional) Any additional installs (see sidebar above)&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Deploy app to App Engine
&lt;/h3&gt;

&lt;p&gt;Before deploying your sample Python or Node.js sample app, a few words on regions. Here are the key points all developers should know:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Every GAE app is hosted in a &lt;a href="https://cloud.google.com/appengine/docs/locations" rel="noopener noreferrer"&gt;GCP region&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Regardless of which region it is hosted in, your app is accessible globally.&lt;/li&gt;
&lt;li&gt;When deploying an app the first time, you'll be prompted to &lt;em&gt;set&lt;/em&gt; your GAE app's region.&lt;/li&gt;
&lt;li&gt;You'll only set the region once because it's a permanent choice (see sidebar below).&lt;/li&gt;
&lt;/ol&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;❗ App Engine region cannot be modified!&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Every Cloud project can only have one App Engine app, and that app can &lt;em&gt;only&lt;/em&gt; ever belong to one region. When assigned, that decision is permanent and cannot be changed, so choose wisely. Rest assured however that regardless of where your app is hosted, it's still reachable globally. Choose a region where you expect most of your users to be. If you insist on another region, you'll have to create another GAE app with another project. One advantage of newer GCP serverless platforms like GCR is that you can have more than one and they can serve different regions.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Let's take a look at the deployment process... run: &lt;a href="https://cloud.google.com/sdk/gcloud/reference/app/deploy" rel="noopener noreferrer"&gt;&lt;code&gt;gcloud app deploy&lt;/code&gt;&lt;/a&gt;. If you didn't set your project ID with &lt;code&gt;gcloud&lt;/code&gt; earlier, pass it in with &lt;code&gt;--project PROJECT_ID&lt;/code&gt; here or enter when prompted. You'll then be prompted to select a region. Your output will resemble this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;$ gcloud app deploy
Reauthentication required.
Please enter your password:
You are creating an app for project [PROJECT_ID].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at
&amp;lt;https://cloud.google.com/appengine/docs/locations&amp;gt;.

Please choose the region where you want your App Engine application located:

 [1] asia-east1    (supports standard and flexible)
 [2] asia-east2    (supports standard and flexible and search_api)
 [3] asia-northeast1 (supports standard and flexible and search_api)

. . .

 [21] us-west2      (supports standard and flexible and search_api)
 [22] us-west3      (supports standard and flexible and search_api)
 [23] us-west4      (supports standard and flexible and search_api)
 [24] cancel
Please enter your numeric choice:  19

Creating App Engine application in project [PROJECT_ID] and region [us-east4]....done.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Because your region choice is permanent, you'll never be prompted for it again. The rest of the output shown below is what you'll see next for your first and all subsequent deployments:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Services to deploy:

descriptor:                  [/private/tmp/app.yaml]
source:                      [/private/tmp]
target project:              [PROJECT_ID]
target service:              [default]
target version:              [20231205t154433]
target url:                  [https://PROJECT_ID.REG.appspot.com]
target service account:      [PROJECT_ID@appspot.gserviceaccount.com]

Do you want to continue (Y/n)?

Beginning deployment of service [default]...
╔════════════════════════════════════════════════════════════╗
╠═ Uploading 3 files to Google Cloud Storage                ═╣
╚════════════════════════════════════════════════════════════╝
File upload done.
Updating service [default]...done.
Setting traffic split for service [default]...done.
Deployed service [default] to [https://PROJECT_ID.REG.appspot.com]

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;On the first deployment, all three files are new, so that is what the build system uploads. On subsequent deployments, only new and modified files are uploaded. Once deployed, visit the app at: &lt;code&gt;https://PROJECT_ID.REG.appspot.com&lt;/code&gt;, a real online address, and you'll see the same "Hello World!" page as before:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ln4s4lyoethegguqwyx.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F8ln4s4lyoethegguqwyx.png" alt="" width="502" height="284"&gt;&lt;/a&gt;&lt;/p&gt;
[IMAGE] "Hello World!" sample app running globally (PC)



&lt;p&gt; &lt;/p&gt;

&lt;p&gt;Now that you've successfully deployed a sample Python or Node.js app and have seen it running on Google Cloud, continue to experiment by changing the sample app and redeploying, or uploading one of your "real" apps. Regardless of next steps, you've now learned one place you can run your apps in the cloud, and nowhere in the discussion above did you consider allocating, configuring, or paying for a server.&lt;/p&gt;

&lt;h2&gt;
  
  
  Summary and next steps
&lt;/h2&gt;

&lt;p&gt;Congratulations on taking your first step in learning about Google's original serverless platform, App Engine. In addition to web apps, GAE can also host mobile backends. A web UI is optional, and GAE only needs to receive HTTP &lt;code&gt;GET&lt;/code&gt; and &lt;code&gt;POST&lt;/code&gt; requests, as long as your app has the appropriate handlers for such requests. Overall, is serverless right for you? And what &lt;em&gt;other&lt;/em&gt; platforms does GCP offer?&lt;/p&gt;

&lt;h3&gt;
  
  
  Where should you run your app?
&lt;/h3&gt;

&lt;p&gt;Serverless is the right solution for developers who want their CSP (cloud services provider) to do most of the heavy-lifting in terms of app-hosting and deployment. It is suitable for apps where the traffic is unpredictable or infrequent (say for student projects, small business websites, mobile backends, etc.), including short-lived apps with extreme traffic, say for a &lt;a href="https://googleappengine.blogspot.com/2011/05/royal-wedding-bells-in-cloud.html" rel="noopener noreferrer"&gt;royal wedding&lt;/a&gt;!&lt;/p&gt;

&lt;p&gt;Apps that get steady, continuous traffic, requiring it be available 24x7, are likely best served with VM-based solutions like Google &lt;a href="https://cloud.google.com/compute" rel="noopener noreferrer"&gt;Compute Engine&lt;/a&gt; (GCE) or Google &lt;a href="https://cloud.google.com/gke" rel="noopener noreferrer"&gt;Kubernetes Engine&lt;/a&gt; (GKE) because the cost of serverless will outgrow its benefits. However, if you're just getting off the ground, serverless helps you get there first, or at least, faster. More on serverless &lt;em&gt;non&lt;/em&gt;-use cases can be found in the &lt;a href="https://dev.to/wescpy/a-broader-perspective-of-serverless-1md1"&gt;other serverless post&lt;/a&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Newer serverless platforms &amp;gt; GAE
&lt;/h3&gt;

&lt;p&gt;If serverless sounds right for you, GAE is one option, but GCP has two others: GCF and GCR, both newer than GAE and part of a "Serverless 2.0" generation of products. In late summer 2024, the 2nd-generation of Cloud Functions was &lt;a href="https://cloud.google.com/blog/products/serverless/google-cloud-functions-is-now-cloud-run-functions" rel="noopener noreferrer"&gt;rebranded as Cloud Run Functions&lt;/a&gt; (GCRF). The newer platforms extend from GAE's original app-hosting mission to include functions/microservices and containers.&lt;/p&gt;

&lt;p&gt;When you &lt;em&gt;don't&lt;/em&gt; have an entire app or don't want/need to worry about full-stack or (LAMP, MEAN, etc.) stacks at all, function-hosting is likely the tool for you. These scenarios cover shorter snippets of code to perform specific tasks or microservices in a multi-tiered application, or basic cloud-based mobile backend functionality.&lt;/p&gt;

&lt;p&gt;Modern software applications are now shipped as containers. Organizing app code and resources helps isolate dependencies, coordinate versions, and serves the purpose of providing a "shopping bag" for what apps depends on. In the past, developers had to decide between the flexibility of containers versus the convenience of serverless. This "problem" is directly addressed by GCR which runs containers in a serverless way. Furthermore, those with modern AI/ML workloads will be happy to know that &lt;a href="https://cloud.google.com/blog/products/application-development/run-your-ai-inference-applications-on-cloud-run-with-nvidia-gpus" rel="noopener noreferrer"&gt;GCR supports GPUs serverlessly&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Overall, the newer platforms are generally more flexible and have newer features than GAE, giving you the ability to have multiple (GCF) functions or (GCR) services vs. one GAE app per project. Both GCF and GCR can be deployed to different regions too, which can be critical for some applications.&lt;/p&gt;

&lt;h3&gt;
  
  
  GAE &amp;gt; Newer serverless platforms
&lt;/h3&gt;

&lt;p&gt;With all of the advantages of GCF, GCR, and GCRF, what can GAE still do better? The bundled services are a big part of the picture. Don't most apps need a database, caching server, external task execution, large blob/file storage, etc? And what if &lt;a href="https://cloud.google.com/appengine/docs/standard/quotas" rel="noopener noreferrer"&gt;they're (mostly) free&lt;/a&gt; and highly-scalable? Yep, that's the promise that the original App Engine team envisioned. The newer serverless platforms require use of standalone GCP services, each of which has their own pricing, or your own best-of-breed options if you prefer or if GCP doesn't have an exact replacement.&lt;/p&gt;

&lt;p&gt;Additionally, GAE infrastructure features an &lt;a href="https://cloud.google.com/appengine/docs/standard/serving-static-files#serving_from_your_application" rel="noopener noreferrer"&gt;automatic static file-serving&lt;/a&gt; and &lt;a href="https://docs.cloud.google.com/appengine/docs/standard/how-requests-are-handled#response_caching" rel="noopener noreferrer"&gt;front-end/response&lt;/a&gt; caching. This free pseudo-CDN (content delivery network) gives you edge-caching without having to set up your own CDN, critical to rendering assets to your users much faster than if served by your application. It's also more cost-effective than a true CDN service like &lt;a href="https://cloud.google.com/cdn" rel="noopener noreferrer"&gt;Cloud CDN&lt;/a&gt;. These are just a few things GAE still does better, so if they're less important for &lt;em&gt;your&lt;/em&gt; app(s), you'll likely be better served by the newer serverless platforms.&lt;/p&gt;

&lt;h3&gt;
  
  
  Billing epilogue
&lt;/h3&gt;

&lt;p&gt;If you want to keep the sample GAE app up and working-as-intended to continue learning and experimenting, great. However, recall it's not a free service, and while you're not likely to incur any charges, the longer you run it, and the more you deploy new versions, this will eventually lead to billable usage. There are several options to minimize getting charged.&lt;/p&gt;

&lt;p&gt;One option is to "pause" your app so as to not get billed for inadvertent usage, especially if robots, hackers, or other online scanners discover your app. To do this, &lt;a href="https://console.cloud.google.com/appengine/settings" rel="noopener noreferrer"&gt;disable your app&lt;/a&gt;. When you're ready to continue playing with GAE, re-enable it. While your app is disabled and won't incur charges, be aware &lt;em&gt;other&lt;/em&gt; resources used in your Cloud project may accrue charges.&lt;/p&gt;

&lt;p&gt;Let's say you deployed your app numerous times, with accrued images and other build files exceeding the free tier on GCS or the registries (CR/AR). (In my experience, the registries accrue costs faster than GCS.) These will continue to bill you even with a disabled app, so be sure to tidy both up in addition to disabling your app to avoid (bad) surprises. (Also review the earlier sidebar on costs if you haven't already!)&lt;/p&gt;

&lt;p&gt;On the other hand, if you're not going to continue with GAE or this GCP project, &lt;em&gt;delete everything&lt;/em&gt; forever to avoid possible billing. To do that, &lt;a href="https://console.cloud.google.com/iam-admin/settings" rel="noopener noreferrer"&gt;shutdown your project&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Summary
&lt;/h3&gt;

&lt;p&gt;If you're a Python, Java, Go, PHP, Ruby, or Node.js developer curious about GAE and made it this far, you now know what the platform is all about and how to deploy a sample app to the cloud. While new features are primarily going into the newer serverless platforms, GAE is still fulfilling its purpose of providing a serverless app-hosting service with autoscaling properties. GAE apps using bundled services can now migrate from Gen1 to Gen2 with much less friction and upgrade to more recent language versions.&lt;/p&gt;

&lt;p&gt;When ready to migrate off the bundled services, developers have additional options of staying on GAE (Gen2), breaking up large apps into multiple microservices for GCF or GCRF, or containerizing their apps for GCR. Furthermore, apps that don't use GAE bundled services can also be migrated to "serverful"/VM-based platforms like GCE or GKE.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next steps
&lt;/h3&gt;

&lt;p&gt;As mentioned earlier, GCP has several serverless platforms. While it's great to learn about the background and remaining use cases for GAE, developers are most likely to use GCR for today's modern apps. Therefore, the next place for you to go from here is directly to the GCR intro post, or, take a step back to gain a broader perspective of serverless... links to both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;NEXT POST: &lt;a href="https://dev.to/wescpy/guide-to-modern-app-hosting-without-servers-on-google-cloud-37n8"&gt;Guide to modern app-hosting without servers on Google Cloud (Run)&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;PREV POST: &lt;a href="https://dev.to/wescpy/a-broader-perspective-of-serverless-1md1"&gt;A broader perspective of serverless&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Wrap-up
&lt;/h3&gt;

&lt;p&gt;If you found an error in this post, bug in &lt;a href="https://github.com/wescpy/google/tree/main/cloud/appengine" rel="noopener noreferrer"&gt;the code&lt;/a&gt;, or have a topic you want me to cover in the future, drop a note in the comments below or file an issue at the repo. If you have an older GAE app and need advice on possible next steps, whether upgrading from Python 2 to 3, migrating off the bundled services, or shifting to other serverless platforms (GCF/GCRF or GCR), I may be able to help... check out &lt;a href="https://appenginemigrations.com" rel="noopener noreferrer"&gt;https://appenginemigrations.com&lt;/a&gt; and submit a request there.&lt;/p&gt;

&lt;p&gt;Thanks for checking out my blog... I hope you find this content useful. I enjoy meeting users on the road, so check if I am visiting your community soon... see the travel calendar on my &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;consulting page&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;p&gt;Below are various resources related to this post which you may find useful.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blog post code samples
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google/tree/main/cloud/appengine" rel="noopener noreferrer"&gt;Samples in &lt;em&gt;this&lt;/em&gt; post&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/wescpy/google" rel="noopener noreferrer"&gt;Code samples for &lt;em&gt;all&lt;/em&gt; posts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google App Engine (GAE) resources
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine/docs" rel="noopener noreferrer"&gt;All GAE documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine/docs/standard" rel="noopener noreferrer"&gt;GAE Standard documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine/docs/standard/lifecycle/runtime-lifecycle" rel="noopener noreferrer"&gt;GAE runtime lifecycle&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cloud.google.com/appengine/docs/standard/lifecycle/support-schedule" rel="noopener noreferrer"&gt;GAE support schedule&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GCP compute platforms (serverless and VM-based)
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/appengine" rel="noopener noreferrer"&gt;Google App Engine&lt;/a&gt; (GAE)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/functions" rel="noopener noreferrer"&gt;Google Cloud Functions (Gen1) and Cloud Run Functions (Gen2)&lt;/a&gt; (GCF/GCRF)&lt;/li&gt;
&lt;li&gt;
&lt;a href="http://cloud.run" rel="noopener noreferrer"&gt;Google Cloud Run&lt;/a&gt; (GCR)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/compute" rel="noopener noreferrer"&gt;Google Compute Engine&lt;/a&gt; (GCE)&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://cloud.google.com/gke" rel="noopener noreferrer"&gt;Google Kubernetes Engine&lt;/a&gt; (GKE)&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  GCP serverless content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Pick a GCP serverless platform &lt;a href="http://youtu.be/gle26fT28Bw?list=PL2pQQBHvYcs0PEecTcLD9_VaLvuhK0_VQ" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;How to design serverless apps &lt;a href="http://youtu.be/ertbL2Rxbvk?list=PL2pQQBHvYcs0PEecTcLD9_VaLvuhK0_VQ" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;Exploring serverless with a nebulous app: Deploy the &lt;em&gt;same app&lt;/em&gt; to App Engine, Cloud Functions, or Cloud Run &lt;a href="http://goo.gle/2Y0ph5q" rel="noopener noreferrer"&gt;blog post&lt;/a&gt;, &lt;a href="https://youtu.be/eTotLOVR7MQ?list=PL2pQQBHvYcs0PEecTcLD9_VaLvuhK0_VQ?utm_source=youtube&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebservdeploy_neb_2021xx&amp;amp;utm_content=info_card" rel="noopener noreferrer"&gt;video&lt;/a&gt;, &lt;a href="https://github.com/wescpy/nebulous-serverless" rel="noopener noreferrer"&gt;code repo&lt;/a&gt;, &lt;a href="https://twitter.com/googledevs/status/1442903087497183233?utm_source=twitter&amp;amp;utm_medium=unpaidsoc&amp;amp;utm_campaign=CDR_wes_aap-serverless_nebserv_sms_201028&amp;amp;utm_content=-" rel="noopener noreferrer"&gt;socials&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Google App Engine (GAE) historical references
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GAE 2008 &lt;a href="http://googleappengine.blogspot.com/2008/04/introducing-google-app-engine-our-new.html" rel="noopener noreferrer"&gt;original (Gen1) launch post&lt;/a&gt; and &lt;a href="http://youtu.be/3Ztr-HhWX1c" rel="noopener noreferrer"&gt;video&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2009 &lt;a href="http://googleappengine.blogspot.com/2009/04/seriously-this-time-new-language-on-app.html" rel="noopener noreferrer"&gt;Java launch post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2009 &lt;a href="http://googleappengine.blogspot.com/2011/11/app-engine-160-out-of-preview-release.html" rel="noopener noreferrer"&gt;GAE general availability (GA) announcement&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2012 &lt;a href="http://googleappengine.blogspot.com/2012/03/go-version-1-now-on-app-engine.html" rel="noopener noreferrer"&gt;Go(lang) launch post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2013 &lt;a href="http://cloudplatform.googleblog.com/2013/05/ushering-in-next-generation-of.html" rel="noopener noreferrer"&gt;PHP launch post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2014 &lt;a href="http://cloudplatform.googleblog.com/2014/03/bringing-together-best-of-paas-and-iaas.html" rel="noopener noreferrer"&gt;Flexible launch post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2018 &lt;a href="https://cloud.google.com/blog/products/gcp/introducing-app-engine-second-generation-runtimes-and-python-3-7" rel="noopener noreferrer"&gt;Gen2 launch post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;GAE 2021 &lt;a href="https://cloud.google.com/blog/products/serverless/support-for-app-engine-services-in-second-generation-runtimes" rel="noopener noreferrer"&gt;bundled services on Gen2 launch post&lt;/a&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Other GAE content by the author
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;GAE (Gen1 &amp;amp; early Gen2) 2024 deprecation "what you need to know" &lt;a href="https://dev.to/wescpy/python-app-engine-jan-2024-deprecation-what-you-need-to-know-4bci"&gt;blog post&lt;/a&gt;
&lt;/li&gt;
&lt;li&gt;"Rapid cloud development using App Engine for the Cycle Hire Widget app" &lt;a href="http://bit.ly/lft-aeb" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; -- Building London's bike-share app with GAE case study&lt;/li&gt;
&lt;li&gt;"Change the world in 10 lines of code!" &lt;a href="http://goo.gl/j553Wf" rel="noopener noreferrer"&gt;video&lt;/a&gt; -- Python GAE (inbound) Mail intro&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;sup&gt;^&lt;/sup&gt; -- Optional; only provides platform background information&lt;br&gt;
 &lt;br&gt;
&lt;strong&gt;DISCLAIMER:&lt;/strong&gt; I was a member of the GAE product team 2009-2013 and 2020-2023. While product information is as accurate as I can find or recall, the opinions are my own.&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;




&lt;p&gt;&lt;small&gt;&lt;br&gt;
&lt;strong&gt;WESLEY CHUN&lt;/strong&gt;, MSCS, is a &lt;a href="https://developers.google.com/experts" rel="noopener noreferrer"&gt;Google Developer Expert&lt;/a&gt; (GDE) in Google Cloud (GCP) &amp;amp; Google Workspace (GWS), author of Prentice Hall's bestselling &lt;a href="https://corepython.com" rel="noopener noreferrer"&gt;"Core Python"&lt;/a&gt; series, co-author of &lt;a href="https://withdjango.com" rel="noopener noreferrer"&gt;"Python Web Development with Django"&lt;/a&gt;, and has written for &lt;a href="https://linuxjournal.com/article/5948" rel="noopener noreferrer"&gt;Linux Journal&lt;/a&gt; &amp;amp; CNET. He's currently an AI Technical Program Manager at Red Hat focused on upstream open source projects that make their way into &lt;a href="https://redhat.com/ai" rel="noopener noreferrer"&gt;Red Hat AI&lt;/a&gt; products. In his spare time, Wesley &lt;a href="https://cyberwebconsulting.com" rel="noopener noreferrer"&gt;helps clients&lt;/a&gt; with Google integrations, &lt;a href="https://appenginemigration.com" rel="noopener noreferrer"&gt;App Engine migrations&lt;/a&gt;, and Python training &amp;amp; engineering. He was one of the original Yahoo!Mail engineers and spent 13+ years on various Google product teams, speaking on behalf of their APIs, producing sample apps, codelabs, and videos for &lt;a href="http://bit.ly/3xk2Swi" rel="noopener noreferrer"&gt;serverless migration&lt;/a&gt; and &lt;a href="http://goo.gl/JpBQ40." rel="noopener noreferrer"&gt;GWS developers&lt;/a&gt; Wesley holds degrees in Computer Science, Mathematics, and Music from the University of California, is a Fellow of the Python Software Foundation, and loves to travel to meet developers worldwide. Follow he/him &lt;a class="mentioned-user" href="https://dev.to/wescpy"&gt;@wescpy&lt;/a&gt; on &lt;a href="https://twitter.com/wescpy" rel="noopener noreferrer"&gt;Tw/X&lt;/a&gt;, &lt;a href="http://blskyl.ink/wescpy" rel="noopener noreferrer"&gt;BS&lt;/a&gt;, and his &lt;a href="https://dev.to/wescpy"&gt;technical blog&lt;/a&gt;. Find this content useful? &lt;a href="https://forms.gle/bQiDMiGyGrrwv5sy5" rel="noopener noreferrer"&gt;Contact CyberWeb&lt;/a&gt; for professional services or &lt;a href="http://buymeacoffee.com/wescpy" rel="noopener noreferrer"&gt;buy him a coffee (or tea)&lt;/a&gt;!&lt;br&gt;
&lt;/small&gt;&lt;/p&gt;

</description>
      <category>python</category>
      <category>webdev</category>
      <category>googlecloud</category>
      <category>serverless</category>
    </item>
  </channel>
</rss>
