npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@stacksolo/plugin-gcp-cdktf

v0.1.8

Published

GCP CDKTF plugin for StackSolo - provides Google Cloud Platform resource definitions using CDK for Terraform

Readme

@stacksolo/plugin-gcp-cdktf

Google Cloud Platform infrastructure for StackSolo using CDKTF (Terraform).

What is this plugin?

This plugin lets you deploy your apps to Google Cloud. It creates:

  • Cloud Functions - Serverless code that runs when someone calls your API
  • Load Balancers - Routes traffic to the right function based on URL path
  • Static Websites - Host your React/Vue/HTML frontend
  • VPC Networks - Private networks for your resources
  • VPC Connectors - Let your functions talk to private resources (like databases)

Quick Start

Step 1: Set up your config

Create or edit stacksolo.config.json:

{
  "project": {
    "name": "my-app",
    "gcpProjectId": "my-gcp-project",
    "region": "us-central1",
    "backend": "cdktf",

    "networks": [{
      "name": "main",

      "functions": [{
        "name": "api",
        "runtime": "nodejs20",
        "entryPoint": "api",
        "allowUnauthenticated": true
      }],

      "loadBalancer": {
        "name": "gateway",
        "routes": [
          { "path": "/api/*", "functionName": "api" }
        ]
      }
    }]
  }
}

Step 2: Create your function code

Create a file at functions/api/index.ts:

import * as functions from '@google-cloud/functions-framework';

functions.http('api', (req, res) => {
  res.json({ message: 'Hello from Cloud Functions!' });
});

Step 3: Deploy

stacksolo deploy

Your API is now live at the load balancer IP!


Available Resources

1. Cloud Function

Serverless functions that run your backend code. They scale automatically and you only pay when they run.

When to use: APIs, webhooks, background processing

Config:

{
  "functions": [{
    "name": "api",
    "runtime": "nodejs20",
    "entryPoint": "api",
    "memory": "256Mi",
    "timeout": 60,
    "allowUnauthenticated": true
  }]
}

| Field | Required | Default | What it does | |-------|----------|---------|--------------| | name | Yes | - | Name of your function | | runtime | No | nodejs20 | Language runtime (nodejs20, nodejs18, python311, python310, go121, go120) | | entryPoint | No | api | The exported function name in your code | | memory | No | 256Mi | Memory for each instance (128Mi, 256Mi, 512Mi, 1Gi, 2Gi, 4Gi) | | timeout | No | 60 | Max seconds a request can run | | minInstances | No | 0 | Keep instances warm (costs money but faster cold starts) | | maxInstances | No | 100 | Max concurrent instances | | allowUnauthenticated | No | true | Allow public access | | vpcConnector | No | - | Name of VPC connector (for database access) | | env | No | - | Environment variables |

Example with all options:

{
  "functions": [{
    "name": "api",
    "runtime": "nodejs20",
    "entryPoint": "api",
    "memory": "512Mi",
    "timeout": 120,
    "minInstances": 1,
    "maxInstances": 10,
    "allowUnauthenticated": true,
    "vpcConnector": "main-connector",
    "env": {
      "DATABASE_URL": "@database/main.connectionString"
    }
  }]
}

Storage Triggers (Event-Driven Functions)

Functions can be triggered by Cloud Storage events instead of HTTP requests. Perfect for processing uploaded files.

Config:

{
  "functions": [{
    "name": "pdf-processor",
    "runtime": "nodejs20",
    "entryPoint": "handler",
    "memory": "1Gi",
    "timeout": 300,
    "trigger": {
      "type": "storage",
      "bucket": "uploads",
      "event": "finalize"
    }
  }]
}

| Trigger Field | Required | What it does | |---------------|----------|--------------| | type | Yes | Trigger type: http, storage, or pubsub | | bucket | Yes* | Bucket name to watch (for storage triggers) | | event | No | Event type: finalize (default), delete, archive, metadataUpdate |

*Required when type is storage.

Storage event types:

| Event | When it fires | |-------|---------------| | finalize | New file uploaded or overwritten (default) | | delete | File deleted | | archive | File archived (for versioned buckets) | | metadataUpdate | File metadata changed |

Example function code for storage trigger:

// functions/pdf-processor/src/index.ts
import { CloudEvent } from '@google-cloud/functions-framework';
import { Storage } from '@google-cloud/storage';

interface StorageObjectData {
  bucket: string;
  name: string;
  contentType: string;
}

export async function handler(event: CloudEvent<StorageObjectData>) {
  const { bucket, name, contentType } = event.data!;
  console.log(`Processing file: ${name} from bucket: ${bucket}`);

  // Download and process the file
  const storage = new Storage();
  const [contents] = await storage.bucket(bucket).file(name).download();

  // Your processing logic here...
}

2. Load Balancer

Routes incoming traffic to the right place based on the URL path. This is how you get one domain that serves your API and frontend.

When to use:

  • You have multiple functions and want one URL
  • You want to serve your frontend and API from the same domain
  • You need a global IP address

Basic config (one function):

{
  "loadBalancer": {
    "name": "gateway",
    "routes": [
      { "path": "/*", "functionName": "api" }
    ]
  }
}

Multi-function config:

{
  "loadBalancer": {
    "name": "gateway",
    "routes": [
      { "path": "/api/*", "functionName": "api" },
      { "path": "/auth/*", "functionName": "auth" },
      { "path": "/webhooks/*", "functionName": "webhooks" }
    ]
  }
}

API + Frontend config:

{
  "loadBalancer": {
    "name": "gateway",
    "routes": [
      { "path": "/api/*", "functionName": "api" },
      { "path": "/*", "uiName": "web" }
    ]
  },

  "uis": [{
    "name": "web",
    "sourceDir": "./frontend"
  }]
}

| Field | Required | What it does | |-------|----------|--------------| | name | Yes | Name for the load balancer | | routes | Yes | List of path-to-backend mappings | | routes[].path | Yes | URL path pattern (e.g., /api/*) | | routes[].functionName | * | Function to route to | | routes[].uiName | * | Static website to route to | | domain | No | Custom domain for HTTPS | | enableHttps | No | Enable HTTPS with managed SSL certificate | | dns | No | Auto-configure DNS (requires Cloudflare plugin) |

*Either functionName or uiName is required for each route.

With custom domain and HTTPS:

{
  "loadBalancer": {
    "name": "gateway",
    "domain": "app.example.com",
    "enableHttps": true,
    "redirectHttpToHttps": true,
    "routes": [
      { "path": "/api/*", "functionName": "api" },
      { "path": "/*", "uiName": "web" }
    ]
  }
}

With automatic Cloudflare DNS:

{
  "loadBalancer": {
    "name": "gateway",
    "domain": "app.example.com",
    "enableHttps": true,
    "dns": {
      "provider": "cloudflare",
      "proxied": true
    },
    "routes": [
      { "path": "/*", "functionName": "api" }
    ]
  }
}

When using Cloudflare DNS, you also need to configure cloudflare.zoneId at the project level.

How path matching works:

  • /api/* matches /api/users, /api/orders/123, etc.
  • /* matches everything (use as fallback)
  • More specific paths are matched first

3. Storage Website

Hosts your static frontend (React, Vue, plain HTML) on Cloud Storage with CDN.

When to use: Frontend apps, marketing sites, documentation

Config:

{
  "uis": [{
    "name": "web",
    "sourceDir": "./frontend",
    "indexDocument": "index.html",
    "errorDocument": "index.html"
  }]
}

| Field | Required | Default | What it does | |-------|----------|---------|--------------| | name | Yes | - | Name for the website | | sourceDir | Yes | - | Path to your frontend folder | | location | No | US | Storage location | | indexDocument | No | index.html | Main page | | errorDocument | No | index.html | 404 page (use index.html for SPAs) | | enableCdn | No | true | Enable Cloud CDN for faster loading |

For single-page apps (React, Vue, etc.):

Set errorDocument to index.html so client-side routing works:

{
  "uis": [{
    "name": "web",
    "sourceDir": "./frontend",
    "errorDocument": "index.html"
  }]
}

4. VPC Network

A private network that isolates your resources. Required if you want functions to access databases or other private resources.

When to use:

  • Connecting functions to Cloud SQL
  • Connecting functions to Redis/Memorystore
  • Any private resource access

Config:

{
  "networks": [{
    "name": "main",
    "autoCreateSubnetworks": true
  }]
}

| Field | Required | Default | What it does | |-------|----------|---------|--------------| | name | Yes | - | Name for the network | | autoCreateSubnetworks | No | true | Auto-create subnets in each region |


5. VPC Connector

Connects your Cloud Functions to a VPC network. This is how functions access databases.

When to use: Your function needs to connect to a database or cache

Config:

{
  "vpcConnector": {
    "name": "main-connector",
    "network": "main",
    "region": "us-central1",
    "ipCidrRange": "10.8.0.0/28"
  }
}

| Field | Required | Default | What it does | |-------|----------|---------|--------------| | name | Yes | - | Name for the connector | | network | Yes | - | VPC network name to connect to | | region | Yes | - | GCP region | | ipCidrRange | No | 10.8.0.0/28 | IP range for the connector | | minThroughput | No | 200 | Min throughput in Mbps | | maxThroughput | No | 300 | Max throughput in Mbps |


6. Storage Bucket

Cloud Storage buckets for storing files. Can be used for uploads, data processing, or static hosting.

When to use:

  • Storing uploaded files
  • Triggering functions on file uploads
  • Static file hosting behind a load balancer

Basic config:

{
  "storageBuckets": [{
    "name": "uploads",
    "location": "us-central1"
  }]
}

| Field | Required | Default | What it does | |-------|----------|---------|--------------| | name | Yes | - | Bucket name | | location | No | US | Storage location | | storageClass | No | STANDARD | Storage class (STANDARD, NEARLINE, COLDLINE, ARCHIVE) | | versioning | No | false | Enable object versioning | | uniformBucketLevelAccess | No | true | Uniform IAM access | | existing | No | false | Reference an existing bucket | | website | No | - | Website configuration (see below) | | cors | No | - | CORS configuration |

With website configuration (for SPAs):

{
  "storageBuckets": [{
    "name": "admin-ui",
    "website": {
      "mainPageSuffix": "index.html",
      "notFoundPage": "index.html"
    }
  }]
}

The website config makes the bucket work as a static website:

  • mainPageSuffix - Page served for directory requests (e.g., /admin//admin/index.html)
  • notFoundPage - Page served for 404 errors. Set to index.html for SPA routing.

With CORS (for browser uploads):

{
  "storageBuckets": [{
    "name": "uploads",
    "cors": [{
      "origin": ["https://app.example.com"],
      "method": ["GET", "PUT", "POST"],
      "responseHeader": ["Content-Type"],
      "maxAgeSeconds": 3600
    }]
  }]
}

Using as a function trigger:

{
  "storageBuckets": [
    { "name": "uploads" },
    { "name": "processed" }
  ],
  "functions": [{
    "name": "processor",
    "trigger": {
      "type": "storage",
      "bucket": "uploads",
      "event": "finalize"
    },
    "env": {
      "OUTPUT_BUCKET": "processed"
    }
  }]
}

Complete Examples

Example 1: Simple API

Just an API, no frontend.

{
  "project": {
    "name": "my-api",
    "gcpProjectId": "my-project",
    "region": "us-central1",
    "backend": "cdktf",

    "networks": [{
      "name": "main",

      "functions": [{
        "name": "api",
        "runtime": "nodejs20",
        "entryPoint": "api"
      }],

      "loadBalancer": {
        "name": "gateway",
        "routes": [
          { "path": "/*", "functionName": "api" }
        ]
      }
    }]
  }
}

Result: Your API is available at http://<load-balancer-ip>/


Example 2: API + Frontend

An API backend with a React/Vue frontend.

{
  "project": {
    "name": "my-app",
    "gcpProjectId": "my-project",
    "region": "us-central1",
    "backend": "cdktf",

    "networks": [{
      "name": "main",

      "functions": [{
        "name": "api",
        "runtime": "nodejs20",
        "entryPoint": "api"
      }],

      "uis": [{
        "name": "web",
        "sourceDir": "./frontend",
        "errorDocument": "index.html"
      }],

      "loadBalancer": {
        "name": "gateway",
        "routes": [
          { "path": "/api/*", "functionName": "api" },
          { "path": "/*", "uiName": "web" }
        ]
      }
    }]
  }
}

Result:

  • http://<ip>/api/* → API function
  • http://<ip>/* → Frontend

Example 3: Multiple APIs (Microservices)

Split your backend into separate functions.

{
  "project": {
    "name": "my-app",
    "gcpProjectId": "my-project",
    "region": "us-central1",
    "backend": "cdktf",

    "networks": [{
      "name": "main",

      "functions": [
        {
          "name": "api-users",
          "runtime": "nodejs20",
          "entryPoint": "api"
        },
        {
          "name": "api-orders",
          "runtime": "nodejs20",
          "entryPoint": "api"
        },
        {
          "name": "api-payments",
          "runtime": "nodejs20",
          "entryPoint": "api",
          "memory": "512Mi"
        }
      ],

      "loadBalancer": {
        "name": "gateway",
        "routes": [
          { "path": "/api/users/*", "functionName": "api-users" },
          { "path": "/api/orders/*", "functionName": "api-orders" },
          { "path": "/api/payments/*", "functionName": "api-payments" }
        ]
      }
    }]
  }
}

Example 4: API with Database Access

Connect your function to a Cloud SQL database.

{
  "project": {
    "name": "my-app",
    "gcpProjectId": "my-project",
    "region": "us-central1",
    "backend": "cdktf",

    "networks": [{
      "name": "main",
      "autoCreateSubnetworks": true,

      "vpcConnector": {
        "name": "db-connector",
        "network": "main",
        "region": "us-central1"
      },

      "databases": [{
        "name": "main",
        "databaseVersion": "POSTGRES_15",
        "tier": "db-f1-micro"
      }],

      "functions": [{
        "name": "api",
        "runtime": "nodejs20",
        "entryPoint": "api",
        "vpcConnector": "db-connector",
        "env": {
          "DATABASE_URL": "@database/main.connectionString"
        }
      }],

      "loadBalancer": {
        "name": "gateway",
        "routes": [
          { "path": "/*", "functionName": "api" }
        ]
      }
    }]
  }
}

Writing Function Code

Basic Express-style Function (Node.js)

// functions/api/index.ts

import * as functions from '@google-cloud/functions-framework';
import express from 'express';

const app = express();
app.use(express.json());

app.get('/api/health', (req, res) => {
  res.json({ status: 'ok' });
});

app.get('/api/users', (req, res) => {
  res.json([
    { id: 1, name: 'Alice' },
    { id: 2, name: 'Bob' },
  ]);
});

app.post('/api/users', (req, res) => {
  const { name } = req.body;
  res.json({ id: 3, name });
});

// Export the Express app as a Cloud Function
functions.http('api', app);

Function with Database (Node.js)

// functions/api/index.ts

import * as functions from '@google-cloud/functions-framework';
import express from 'express';
import { Pool } from 'pg';

const app = express();
app.use(express.json());

// Database connection
const pool = new Pool({
  connectionString: process.env.DATABASE_URL,
});

app.get('/api/users', async (req, res) => {
  const result = await pool.query('SELECT * FROM users');
  res.json(result.rows);
});

functions.http('api', app);

Function with Environment Variables

Access environment variables in your code:

const apiKey = process.env.API_KEY;
const databaseUrl = process.env.DATABASE_URL;

Set them in your config:

{
  "functions": [{
    "name": "api",
    "env": {
      "API_KEY": "your-api-key",
      "DATABASE_URL": "@database/main.connectionString"
    }
  }]
}

Folder Structure

When deploying, StackSolo expects this structure:

my-project/
├── stacksolo.config.json
├── functions/
│   ├── api/
│   │   ├── package.json
│   │   ├── index.ts
│   │   └── ... other files
│   └── webhooks/
│       ├── package.json
│       └── index.ts
└── frontend/
    ├── package.json
    ├── index.html
    └── ... your frontend code

Each function is in its own folder with its own package.json.


Deployment

Prerequisites

  1. Install Google Cloud CLI:

    # macOS
    brew install google-cloud-sdk
    
    # Or download from: https://cloud.google.com/sdk/docs/install
  2. Login to Google Cloud:

    gcloud auth login
    gcloud auth application-default login
  3. Set your project:

    gcloud config set project YOUR_PROJECT_ID
  4. Install Terraform:

    # macOS
    brew install terraform
    
    # Or download from: https://developer.hashicorp.com/terraform/downloads

Deploy

stacksolo deploy

View deployed resources

stacksolo status

Destroy everything

stacksolo destroy

Cost Estimates

| Resource | Estimated Monthly Cost | |----------|----------------------| | Cloud Function (256Mi) | ~$0 (free tier covers 2M invocations) | | Cloud Function (512Mi) | ~$5 | | Cloud Function (1Gi) | ~$10 | | Load Balancer | ~$18 | | Storage Website (1GB) | ~$1 | | VPC Connector | ~$0 (pay per GB processed) | | VPC Network | $0 |

Costs vary based on usage. These are rough estimates.


Troubleshooting

"Permission denied" errors

Make sure you're logged in:

gcloud auth login
gcloud auth application-default login

Function not accessible

Check if allowUnauthenticated is set to true:

{
  "functions": [{
    "allowUnauthenticated": true
  }]
}

Function can't connect to database

  1. Make sure you have a VPC connector:

    {
      "vpcConnector": {
        "name": "db-connector",
        "network": "main",
        "region": "us-central1"
      }
    }
  2. Add the connector to your function:

    {
      "functions": [{
        "vpcConnector": "db-connector"
      }]
    }

Load balancer shows wrong content

Check your route order. More specific paths should come first:

{
  "routes": [
    { "path": "/api/*", "functionName": "api" },
    { "path": "/*", "uiName": "web" }
  ]
}

Path prefixes are not stripped (GCP behavior)

GCP load balancers forward the full path to backends. This is different from Kubernetes ingress which strips path prefixes.

| Route | Request URL | Backend receives | |-------|-------------|-----------------| | /api/* → function | /api/users | /api/users | | /admin/* → bucket | /admin/app.js | /admin/app.js |

For functions: Your code receives the full path including the prefix:

// Route: /api/* → api function
app.get('/api/users', (req, res) => {  // ✅ Include /api prefix
  res.json({ users: [] });
});

app.get('/users', (req, res) => {      // ❌ Won't match
  res.json({ users: [] });
});

For storage buckets: Upload files at the full path:

# Route: /admin/* → admin-ui bucket
# Files must be at bucket/admin/...
gsutil -m cp -r dist/* gs://my-bucket/admin/

This means:

  • https://example.com/admin/ → served from gs://my-bucket/admin/index.html
  • https://example.com/admin/app.js → served from gs://my-bucket/admin/app.js

Frontend routes return 404

For single-page apps, set errorDocument to index.html:

{
  "uis": [{
    "errorDocument": "index.html"
  }]
}

Reference

Supported Runtimes

| Runtime | Language | |---------|----------| | nodejs20 | Node.js 20 | | nodejs18 | Node.js 18 | | python311 | Python 3.11 | | python310 | Python 3.10 | | go121 | Go 1.21 | | go120 | Go 1.20 |

Memory Options

| Option | Use case | |--------|----------| | 128Mi | Very light tasks | | 256Mi | Default, good for most APIs | | 512Mi | APIs with more processing | | 1Gi | Heavy processing, large payloads | | 2Gi | Very heavy processing | | 4Gi | Maximum available |

Environment Variable References

Use @type/name.property to reference other resources:

| Reference | What it gets | |-----------|--------------| | @database/main.connectionString | Database connection string | | @database/main.privateIp | Database private IP | | @secret/api-key | Secret value | | @bucket/uploads.name | Bucket name | | @function/api.url | Function URL |


Summary

| What you want | What to use | |---------------|-------------| | Run backend code | Cloud Function | | One URL for everything | Load Balancer | | Host frontend | Storage Website | | Connect function to database | VPC Network + VPC Connector | | Route to different backends | Load Balancer with routes |