Google Cloud Run and Cloud Storage

No ready-made Google Cloud adapter, but a Storage is three methods. Implement it over a Cloud Storage bucket and host the editor on Cloud Run.

Same idea as the AWS guide: a small Storage over a bucket, then wire and serve.

1. Install

npm install @xtrable-ltd/nanoesis @google-cloud/storage express

2. Two buckets

A private bucket for editable files, and a public bucket for the published site (serve it through an HTTPS load balancer with Cloud CDN for a custom domain).

3. A Storage over Cloud Storage

// gcs-storage.js
import { Storage as Gcs } from '@google-cloud/storage';

const gcs = new Gcs();

export function bucketStorage(name) {
  const bucket = gcs.bucket(name);
  return {
    async get(key) {
      try {
        const [buf] = await bucket.file(key).download();
        return new Uint8Array(buf);
      } catch (e) {
        if (e.code === 404) return undefined;
        throw e;
      }
    },
    async put(key, bytes) {
      await bucket.file(key).save(Buffer.from(bytes));
    },
    async delete(key) {
      await bucket.file(key).delete({ ignoreNotFound: true });
    },
    async wipe() {
      await bucket.deleteFiles({ force: true });
    },
    async list() {
      const [files] = await bucket.getFiles();
      return files.map((f) => f.name);
    },
  };
}

4. Wire the editor

// editor.js
import { createEditor, devNoAuth } from '@xtrable-ltd/nanoesis/editor-api';
import { bucketStorage } from './gcs-storage.js';

const files = bucketStorage(process.env.WORKING_BUCKET);

export const editor = createEditor({
  editorFiles: files,
  website: bucketStorage(process.env.SITE_BUCKET),
  login: devNoAuth(),            // swap for real login before going live
  enumerate: () => files.list(),
});

5. Serve it on Cloud Run

A small Express app on the port Cloud Run provides. The two routes are identical to every other host.

// server.js
import express from 'express';
import { serveEditorAsset } from '@xtrable-ltd/nanoesis/editor-api';
import { editorDist } from '@xtrable-ltd/nanoesis/editor';
import { editor } from './editor.js';

const toBody = (b) => (b == null ? '' : typeof b === 'string' ? b : Buffer.from(b));
const app = express();
app.use(express.raw({ type: '*/*', limit: '25mb' }));

app.all('/api/*', async (req, res) => {
  const r = await editor.handleApi({
    method: req.method,
    path: req.path,
    query: new URL(req.originalUrl, 'http://localhost').searchParams,
    getHeader: (n) => req.get(n),
    body: async () => new Uint8Array(req.body),
  });
  res.status(r.status).set(r.headers).send(toBody(r.body));
});

app.get('*', async (req, res) => {
  const a = await serveEditorAsset(editorDist, req.path);
  res.status(a.status).set(a.headers).send(toBody(a.body));
});

app.listen(process.env.PORT || 8080);

6. Publish

Open the editor, create a page, and press Publish. The built site is written to your site bucket; serve it through your load balancer and Cloud CDN.