Skip to content

Commit 52398df

Browse files
committed
docs: Add Cloud Run and Node JS connector examples
1 parent e5fd4e6 commit 52398df

44 files changed

Lines changed: 2799 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/cloudrun/README.md

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
# Node.js Samples for Cloud Run
2+
3+
This directory contains samples demonstrating how to connect to Cloud SQL from Cloud Run using various Node.js ORMs and the [Cloud SQL Node.js Connector](https://github.com/GoogleCloudPlatform/cloud-sql-nodejs-connector).
4+
5+
## Available Samples
6+
7+
* [Knex.js](./knex)
8+
* [Prisma](./prisma)
9+
* [Sequelize](./sequelize)
10+
* [TypeORM](./typeorm)
11+
12+
Each ORM directory contains subdirectories for supported databases (MySQL, PostgreSQL, SQL Server), and each example includes implementations in:
13+
* CommonJS (`.cjs`)
14+
* ES Modules (`.mjs`)
15+
* TypeScript (`.ts`)
16+
17+
## Prerequisites
18+
19+
1. A Google Cloud Project with billing enabled.
20+
2. A Cloud SQL instance.
21+
3. A Cloud Run service account with the `Cloud SQL Client` IAM role.
22+
4. For IAM Authentication, the service account must be added as a database user.
23+
24+
## Deployment
25+
26+
Refer to the `README.md` in each ORM directory for specific deployment instructions.

examples/cloudrun/knex/README.md

Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
# Connecting Cloud Run to Cloud SQL with the Node.js Connector
2+
3+
This guide provides a comprehensive walkthrough of how to connect a Cloud Run service to a Cloud SQL instance using the Cloud SQL Node.js Connector. It covers connecting to instances with both public and private IP addresses and demonstrates how to handle database credentials securely.
4+
5+
## Develop a Node.js Application
6+
7+
The following Node.js applications demonstrate how to connect to a Cloud SQL instance using the Cloud SQL Node.js Connector.
8+
9+
### `mysql2/index.cjs` and `pg/index.mjs`
10+
11+
These files contain the core application logic for connecting to a Cloud SQL for MySQL or PostgreSQL instance. They provide two separate authentication methods, each exposed at a different route:
12+
- `/`: Password-based authentication
13+
- `/iam`: IAM-based authentication
14+
15+
### `tedious/index.ts`
16+
17+
This file contains the core application logic for connecting to a Cloud SQL for SQL Server instance. It uses the `cloud-sql-nodejs-connector` to create a database connection pool with password-based authentication at the `/` route.
18+
19+
> [!NOTE]
20+
>
21+
> Cloud SQL for SQL Server does not support IAM database authentication.
22+
23+
## Lazy Instantiation
24+
25+
In a Cloud Run service, global variables are initialized when the container instance starts up. The application instance then handles subsequent requests until the container is spun down.
26+
27+
The `Connector` and `knex` objects are defined as global variables (initially set to `null`) and are lazily instantiated (created only when needed) inside the request handlers.
28+
29+
This approach offers several benefits:
30+
31+
1. **Faster Startup:** By deferring initialization until the first request, the Cloud Run service can start listening for requests almost immediately, reducing cold start latency.
32+
2. **Resource Efficiency:** Expensive operations, like establishing background connections or fetching secrets, are only performed when actually required.
33+
3. **Connection Reuse:** Once initialized, the global `Connector` and `knex` instances are reused for all subsequent requests to that container instance. This prevents the overhead of creating new connections for every request and avoids hitting connection limits.
34+
35+
## IAM Authentication Prerequisites
36+
37+
For IAM authentication to work, you must ensure two things:
38+
39+
1. **The Cloud Run service's service account has the `Cloud SQL Client` role.** You can grant this role with the following command:
40+
```bash
41+
gcloud projects add-iam-policy-binding PROJECT_ID \
42+
--member="serviceAccount:SERVICE_ACCOUNT_EMAIL" \
43+
--role="roles/cloudsql.client"
44+
```
45+
Replace `PROJECT_ID` with your Google Cloud project ID and `SERVICE_ACCOUNT_EMAIL` with the email of the service account your Cloud Run service is using.
46+
47+
2. **The service account is added as a database user to your Cloud SQL instance.** You can do this with the following command:
48+
```bash
49+
gcloud sql users create SERVICE_ACCOUNT_EMAIL \
50+
--instance=INSTANCE_NAME \
51+
--type=cloud_iam_user
52+
```
53+
Replace `SERVICE_ACCOUNT_EMAIL` with the same service account email and `INSTANCE_NAME` with your Cloud SQL instance name.
54+
55+
## Deploy the Application to Cloud Run
56+
57+
Follow these steps to deploy the application to Cloud Run.
58+
59+
### Build and Push the Docker Image
60+
61+
1. **Enable the Artifact Registry API:**
62+
63+
```bash
64+
gcloud services enable artifactregistry.googleapis.com
65+
```
66+
67+
2. **Create an Artifact Registry repository:**
68+
69+
```bash
70+
gcloud artifacts repositories create REPO_NAME \
71+
--repository-format=docker \
72+
--location=REGION
73+
```
74+
75+
3. **Configure Docker to authenticate with Artifact Registry:**
76+
77+
```bash
78+
gcloud auth configure-docker REGION-docker.pkg.dev
79+
```
80+
81+
4. **Build the Docker image (replace `mysql2` with `pg` or `tedious` as needed):**
82+
83+
```bash
84+
docker build -t REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME mysql2
85+
```
86+
87+
5. **Push the Docker image to Artifact Registry:**
88+
89+
```bash
90+
docker push REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME
91+
```
92+
93+
### Deploy to Cloud Run
94+
95+
Deploy the container image to Cloud Run using the `gcloud run deploy` command.
96+
97+
**Sample Values:**
98+
* `SERVICE_NAME`: `my-cloud-run-service`
99+
* `REGION`: `us-central1`
100+
* `PROJECT_ID`: `my-gcp-project-id`
101+
* `REPO_NAME`: `my-artifact-repo`
102+
* `IMAGE_NAME`: `my-app-image`
103+
* `INSTANCE_CONNECTION_NAME`: `my-gcp-project-id:us-central1:my-instance-name`
104+
* `DB_USER`: `my-db-user` (for password-based authentication)
105+
* `DB_IAM_USER`: `my-service-account@my-gcp-project-id.iam.gserviceaccount.com` (for IAM-based authentication)
106+
* `DB_NAME`: `my-db-name`
107+
* `DB_PASSWORD`: `my-user-pass-name`
108+
* `VPC_NETWORK`: `my-vpc-network`
109+
* `SUBNET_NAME`: `my-vpc-subnet`
110+
111+
**For MySQL and PostgreSQL (Public IP):**
112+
113+
```bash
114+
gcloud run deploy SERVICE_NAME \
115+
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
116+
--set-env-vars=DB_USER=DB_USER,DB_IAM_USER=DB_IAM_USER,DB_NAME=DB_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME \
117+
--region=REGION \
118+
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
119+
```
120+
121+
**For MySQL and PostgreSQL (Private IP):**
122+
123+
```bash
124+
gcloud run deploy SERVICE_NAME \
125+
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
126+
--set-env-vars=DB_USER=DB_USER,DB_IAM_USER=DB_IAM_USER,DB_NAME=DB_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME,IP_TYPE=PRIVATE \
127+
--network=VPC_NETWORK \
128+
--subnet=SUBNET_NAME \
129+
--vpc-egress=private-ranges-only \
130+
--region=REGION \
131+
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
132+
```
133+
134+
**For SQL Server (Public IP):**
135+
136+
```bash
137+
gcloud run deploy SERVICE_NAME \
138+
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
139+
--set-env-vars=DB_USER=DB_USER,DB_NAME=DB_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME \
140+
--region=REGION \
141+
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
142+
```
143+
144+
**For SQL Server (Private IP):**
145+
146+
```bash
147+
gcloud run deploy SERVICE_NAME \
148+
--image=REGION-docker.pkg.dev/PROJECT_ID/REPO_NAME/IMAGE_NAME \
149+
--set-env-vars=DB_USER=DB_USER,DB_NAME=DB_NAME,INSTANCE_CONNECTION_NAME=INSTANCE_CONNECTION_NAME,IP_TYPE=PRIVATE \
150+
--network=VPC_NETWORK \
151+
--subnet=SUBNET_NAME \
152+
--vpc-egress=private-ranges-only \
153+
--region=REGION \
154+
--update-secrets=DB_PASSWORD=DB_PASSWORD:latest
155+
```
156+
157+
> [!NOTE]
158+
> **`For PSC connections`**
159+
>
160+
> To connect to the Cloud SQL instance with PSC connection type, create a PSC endpoint, a DNS zone and DNS record for the instance in the same VPC network as the Cloud Run service and replace the `IP_TYPE` in the deploy command with `PSC`. To configure DNS records, refer to [Connect to an instance using Private Service Connect](https://docs.cloud.google.com/sql/docs/mysql/configure-private-service-connect) guide
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
# Use the official Node.js image.
2+
# https://hub.docker.com/_/node
3+
FROM node:25-slim
4+
5+
# Create and change to the app directory.
6+
WORKDIR /usr/src/app
7+
8+
# Copy application dependency manifests to the container image.
9+
# A wildcard is used to ensure both package.json AND package-lock.json are copied.
10+
# Copying this separately prevents re-running npm install on every code change.
11+
COPY package*.json ./
12+
13+
# Install production dependencies.
14+
# If you add a package-lock.json speed your build by switching to 'npm ci'.
15+
# RUN npm ci --only=production
16+
RUN npm install --omit=dev
17+
18+
# Copy local code to the container image.
19+
COPY . .
20+
21+
# Run the web service on container startup.
22+
CMD ["node", "index.cjs"]
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
// Copyright 2025 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// https://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
const express = require('express');
16+
const {Connector, IpAddressTypes} = require('@google-cloud/cloud-sql-connector');
17+
const knex = require('knex');
18+
19+
const app = express();
20+
21+
// Connector and connection pools are initialized as null to allow for lazy instantiation.
22+
// Lazy instantiation is a best practice for Cloud Run applications because it allows
23+
// the application to start faster and only initialize connections when they are needed.
24+
// This is especially important in serverless environments where applications may be
25+
// started and stopped frequently.
26+
let connector = null;
27+
let passwordPool = null;
28+
let iamPool = null;
29+
30+
// Helper to get the IP type enum from string
31+
function getIpType(ipTypeStr) {
32+
const ipType = ipTypeStr || 'PUBLIC';
33+
if (ipType === 'PRIVATE') {
34+
return IpAddressTypes.PRIVATE;
35+
} else if (ipType === 'PSC') {
36+
return IpAddressTypes.PSC;
37+
} else {
38+
return IpAddressTypes.PUBLIC;
39+
}
40+
}
41+
42+
// Function to create a database connection pool using IAM authentication
43+
async function getIamConnection() {
44+
const instanceConnectionName = process.env.INSTANCE_CONNECTION_NAME;
45+
// IAM service account email
46+
const dbUser = process.env.DB_IAM_USER;
47+
const dbName = process.env.DB_NAME;
48+
const ipType = getIpType(process.env.IP_TYPE);
49+
50+
// Creates a new connector object.
51+
if (!connector) {
52+
connector = new Connector();
53+
}
54+
55+
// Get the connection options for the Cloud SQL instance.
56+
const clientOpts = await connector.getOptions({
57+
instanceConnectionName,
58+
ipType: ipType,
59+
authType: 'IAM',
60+
});
61+
62+
// Create a new knex connection pool.
63+
return knex({
64+
client: 'mysql2',
65+
connection: {
66+
...clientOpts,
67+
user: dbUser,
68+
database: dbName,
69+
},
70+
});
71+
}
72+
73+
// Function to create a database connection pool using password authentication
74+
async function getPasswordConnection() {
75+
const instanceConnectionName = process.env.INSTANCE_CONNECTION_NAME;
76+
// Database username
77+
const dbUser = process.env.DB_USER;
78+
const dbName = process.env.DB_NAME;
79+
const dbPassword = process.env.DB_PASSWORD;
80+
const ipType = getIpType(process.env.IP_TYPE);
81+
82+
// Creates a new connector object.
83+
if (!connector) {
84+
connector = new Connector();
85+
}
86+
87+
// Get the connection options for the Cloud SQL instance.
88+
const clientOpts = await connector.getOptions({
89+
instanceConnectionName,
90+
ipType: ipType,
91+
});
92+
93+
// Create a new knex connection pool.
94+
return knex({
95+
client: 'mysql2',
96+
connection: {
97+
...clientOpts,
98+
user: dbUser,
99+
password: dbPassword,
100+
database: dbName,
101+
},
102+
});
103+
}
104+
105+
// Helper to get or create the password pool
106+
async function getConnectionSettings() {
107+
if (!passwordPool) {
108+
passwordPool = await getPasswordConnection();
109+
}
110+
return passwordPool;
111+
}
112+
113+
// Helper to get or create the IAM pool
114+
async function getIamConnectionSettings() {
115+
if (!iamPool) {
116+
iamPool = await getIamConnection();
117+
}
118+
return iamPool;
119+
}
120+
121+
app.get('/', async (req, res) => {
122+
try {
123+
const db = await getConnectionSettings();
124+
// Use knex to run a simple query
125+
const result = await db.raw('SELECT 1');
126+
// Knex raw result for mysql2 is [rows, fields]
127+
res.send(`Database connection successful (password authentication), result: ${JSON.stringify(result[0])}`);
128+
} catch (err) {
129+
console.error(err);
130+
res.status(500).send(`Error connecting to the database (password authentication): ${err.message}`);
131+
}
132+
});
133+
134+
app.get('/iam', async (req, res) => {
135+
try {
136+
const db = await getIamConnectionSettings();
137+
const result = await db.raw('SELECT 1');
138+
res.send(`Database connection successful (IAM authentication), result: ${JSON.stringify(result[0])}`);
139+
} catch (err) {
140+
console.error(err);
141+
res.status(500).send(`Error connecting to the database (IAM authentication): ${err.message}`);
142+
}
143+
});
144+
145+
const port = parseInt(process.env.PORT) || 8080;
146+
app.listen(port, () => {
147+
console.log(`Server running on port ${port}`);
148+
});
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
{
2+
"name": "knex-mysql-cloudrun",
3+
"version": "1.0.0",
4+
"description": "Knex MySQL example for Cloud Run (CommonJS)",
5+
"main": "index.cjs",
6+
"type": "commonjs",
7+
"scripts": {
8+
"start": "node index.cjs"
9+
},
10+
"dependencies": {
11+
"@google-cloud/cloud-sql-connector": "^1.8.4",
12+
"express": "^5.1.0",
13+
"knex": "^3.1.0",
14+
"mysql2": "^3.15.2"
15+
},
16+
"devDependencies": {
17+
}
18+
}

0 commit comments

Comments
 (0)