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()),
});
// Static assets are bytes; JSON responses are strings; a 204 (e.g. DELETE)
// has no body. Only attach body when present (see the note below).
const body = result.body instanceof Uint8Array
? Buffer.from(result.body)
: result.body;
return {
status: result.status,
headers: result.headers,
...(body === undefined ? {} : { body }),
};
},
});One detail in that mapping matters. A 204 No Content response, which a successful DELETE returns, has no body, so the host only sets body when there is one. If you instead coerce an absent body to an empty string, Azure Functions rejects the whole response: the Response object it builds does not allow a body on a 204 (or a 205 or 304), and every delete fails with "Invalid response status code 204".
Two settings that matter
- Empty route prefix. In
host.json, setextensions.http.routePrefixto an empty string. Azure serves routes under/apiby default, which would clash with Understudy's own/apiadmin routes. - Worker indexing. Set
AzureWebJobsFeatureFlagstoEnableWorkerIndexing, 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:7071Start 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-appIn 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.