Run on Azure Functions

Run Understudy as one stateless Azure Function, with Azure Blob for state. The same file runs locally on Azurite and deployed in the cloud.

Understudy runs on Azure Functions as a single, stateless function. The function adapts each request onto Understudy and returns its response. A function keeps nothing on local disk between calls, so the data lives in Azure Blob Storage, and the same file runs on your machine and in the cloud.

One file is the whole host

A catch-all HTTP function passes every request to understudy.handle, which dispatches the admin API, the simulated API, and the editor UI. State goes to Blob, reusing the function app's own storage account, so there is nothing extra to configure.

// index.mjs
import { app } from '@azure/functions';
import { createUnderstudy } from '@xtrable-ltd/understudy/host';
import { BlobStore } from '@xtrable-ltd/understudy/adapter-azure-blob';

const understudy = createUnderstudy({
  store: new BlobStore({
    connectionString: process.env.AzureWebJobsStorage,
    containerName: 'understudy',
  }),
});

app.http('understudy', {
  route: '{*path}',
  methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
  authLevel: 'anonymous',
  handler: async (request) => {
    const url = new url(request.url);
    const headers = {};
    request.headers.forEach((value, key) => { headers[key] = value; });

    const result = await understudy.handle({
      method: request.method,
      path: url.pathname,
      query: url.searchParams,
      headers,
      body: async () => new Uint8Array(await request.arrayBuffer()),
    });

    return {
      status: result.status,
      headers: result.headers,
      body: result.body instanceof Uint8Array
        ? Buffer.from(result.body)
        : (result.body ?? ''),
    };
  },
});

Two settings that matter

  • Empty route prefix. In host.json, set extensions.http.routePrefix to an empty string. Azure serves routes under /api by default, which would clash with Understudy's own /api admin routes.
  • Worker indexing. Set AzureWebJobsFeatureFlags to EnableWorkerIndexing, or the function registers nothing at startup and every request quietly returns 404.
// host.json
{
  "version": "2.0",
  "extensions": { "http": { "routePrefix": "" } }
}

Run it locally

Add a package.json with the two dependencies and a local.settings.json, then start the Functions emulator. It runs against Azurite, the local Blob emulator.

// package.json
{
  "type": "module",
  "main": "index.mjs",
  "dependencies": {
    "@azure/functions": "^4.5.0",
    "@xtrable-ltd/understudy": "^0.2.0"
  }
}
// local.settings.json
{
  "IsEncrypted": false,
  "Values": {
    "FUNCTIONS_WORKER_RUNTIME": "node",
    "AzureWebJobsFeatureFlags": "EnableWorkerIndexing",
    "AzureWebJobsStorage": "UseDevelopmentStorage=true"
  }
}
npx azurite --skipApiVersionCheck   # in one terminal
npm install
func start                          # in another; opens on http://localhost:7071

Start Azurite with --skipApiVersionCheck. The Blob SDK asks for a newer REST version than current Azurite releases allow, and without the flag stateful calls fail while the editor UI still loads, which is a confusing mix. It is a local-only concern; real Azure Blob needs nothing.

Deploy it

Publish with the Azure Functions Core Tools (a user-level npm global, no admin rights):

func azure functionapp publish your-function-app

In the cloud you set nothing extra for Understudy. The function app already has an AzureWebJobsStorage connection string for its own storage account, and Understudy reuses it as the store. Locally that value points at Azurite; in the cloud it is the real account. The same index.mjs runs in both places, unchanged.