{"id":18063268,"url":"https://github.com/scale-tone/durable-mvc-starter","last_synced_at":"2025-04-11T15:26:51.155Z","repository":{"id":49787151,"uuid":"346870622","full_name":"scale-tone/durable-mvc-starter","owner":"scale-tone","description":"Basic project setup and scaffolding for creating serverless web applications based on Azure Durable Entities, Azure SignalR Service, React+MobX and TypeScript","archived":false,"fork":false,"pushed_at":"2023-03-04T11:52:03.000Z","size":1212,"stargazers_count":12,"open_issues_count":4,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-03-25T11:38:12.393Z","etag":null,"topics":["azure","azure-durable-functions","durable-entities","mobx","react","serverless","typescript"],"latest_commit_sha":null,"homepage":"https://scale-tone.github.io/2021/03/15/durable-mvc","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/scale-tone.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2021-03-11T23:36:17.000Z","updated_at":"2024-07-16T21:13:21.000Z","dependencies_parsed_at":"2024-10-31T05:20:29.857Z","dependency_job_id":null,"html_url":"https://github.com/scale-tone/durable-mvc-starter","commit_stats":null,"previous_names":[],"tags_count":0,"template":true,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scale-tone%2Fdurable-mvc-starter","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scale-tone%2Fdurable-mvc-starter/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scale-tone%2Fdurable-mvc-starter/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/scale-tone%2Fdurable-mvc-starter/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/scale-tone","download_url":"https://codeload.github.com/scale-tone/durable-mvc-starter/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248429931,"owners_count":21101921,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["azure","azure-durable-functions","durable-entities","mobx","react","serverless","typescript"],"created_at":"2024-10-31T05:10:22.423Z","updated_at":"2025-04-11T15:26:51.132Z","avatar_url":"https://github.com/scale-tone.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# durable-mvc-starter\n\nBasic project setup and scaffolding for creating serverless web applications based on [Azure Durable Entities](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-entities?tabs=javascript), [Azure SignalR Service](https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-overview), React+[MobX](https://mobx.js.org) and TypeScript.\n\nThe gist of this architectural approach is to define a strongly-typed state (a TypeScript class with no methods, like [this one](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/shared/CounterState.ts)), then implement your server-side state transformation logic in form of a Durable Entity (like [this one](https://github.com/scale-tone/durable-mvc-starter/blob/main/DurableEntities/CounterEntity.ts)) and then render your state on the client with some [JSX](https://www.typescriptlang.org/docs/handbook/jsx.html) (like [this one](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/App.tsx)). Once the state changes on the server, its changes are incrementally propagated to the client via [SignalR](https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-overview) and automatically re-rendered thanks to [MobX](https://mobx.js.org).\n\n\u003cimg src=\"https://user-images.githubusercontent.com/5447190/129478058-290f9e9c-bc52-4915-b423-c78cfdb56c70.png\" height=\"400px\"/\u003e\n\nWhy is it called 'Durable MVC'? Because it looks like MVC ([Model-View-Controller](https://en.wikipedia.org/wiki/Model%E2%80%93view%E2%80%93controller)), but instead of controllers the logic is implemented in form of [Durable Entities](https://docs.microsoft.com/en-us/azure/azure-functions/durable/durable-functions-entities?tabs=javascript).\n\nThe project in this repo is technically a pre-configured [Azure Functions Node.js project](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=v2#typescript) with all code intended to be written in TypeScript. And it includes the following scaffolding:\n1. Server-side base classes that allow to define and implement your Durable Entities with a class-based syntax. You derive your Entity class from [DurableEntity\\\u003cTState\\\u003e](https://github.com/scale-tone/durable-mvc-starter/blob/main/common/DurableEntity.ts) and implement your signal handlers in form of methods. The state is available to you via `this.state` property and it is automatically loaded/persisted and propagated to the client. Then you can send signals to your Entity [via this server-side `DurableEntityProxy\u003cTEntity\u003e` helper](https://github.com/scale-tone/durable-mvc-starter/blob/main/common/DurableEntityProxy.ts) and/or via [this client-side `DurableEntitySet.signalEntity()` method](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L121).\n2. Client-side React+MobX+TypeScript project located in [this `/ui` sub-folder](https://github.com/scale-tone/durable-mvc-starter/tree/main/ui). It is automatically (re)built along with the main project, and its output (static HTML/JS/CSS files) is served with [this `serve-statics` Function](https://github.com/scale-tone/durable-mvc-starter/blob/main/serve-statics/index.ts). TypeScript class definitions placed into [this `/ui/src/shared` folder](https://github.com/scale-tone/durable-mvc-starter/tree/main/ui/src/shared) are shared by both server and client projects, so this is where you define your states.\n3. Client-side container for your entities - [DurableEntitySet\\\u003cTState\\\u003e](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts). Once you defined and implemented your entity, you can then bind to a single particular instance of it with [DurableEntitySet.attachEntity\\\u003cTState\\\u003e()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L88) or [DurableEntitySet.createEntity\\\u003cTState\\\u003e()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L112) static methods. But much more typical is to bind to an [observable collection](https://mobx.js.org/observable-state.html) of entities of a certain type, and for that you just need to create an *instance* of `DurableEntitySet\u003cTState\u003e` and then bind to its [items](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L18) property, which is an observable array (newly-created entities are automatically added to it and destroyed entities are automatically removed from it). Everything returned by `DurableEntitySet\u003cTState\u003e` is [marked as observable](https://mobx.js.org/observable-state.html#makeautoobservable), so the only thing left to be done is to write some JSX for rendering.\n4. [This `negotiate-signalr` function](https://github.com/scale-tone/durable-mvc-starter/blob/main/negotiate-signalr/index.ts), that allows the client to connect to [Azure SignalR](https://docs.microsoft.com/en-us/azure/azure-signalr/signalr-overview).\n5. [This `manage-entities` function](https://github.com/scale-tone/durable-mvc-starter/blob/main/manage-entities/index.ts), that exposes Entity states to the client and handles signals sent from it.\n6. A basic sample Entity. [Here is its state](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/shared/CounterState.ts), [here is its class](https://github.com/scale-tone/durable-mvc-starter/blob/main/DurableEntities/CounterEntity.ts) and [here is its rendering](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/App.tsx#L26).\n\nMore examples you can find in [this separate repo](https://github.com/scale-tone/durable-mvc-samples).\nAlso check [this blog post](https://scale-tone.github.io/2021/03/15/durable-mvc) for more details.\n\n# Prerequisites\n* [Azure Functions Core Tools](https://www.npmjs.com/package/azure-functions-core-tools) **globally** installed on your devbox.\n* An instance of Azure SignalR Service [configured in Serverless mode](https://docs.microsoft.com/en-us/azure/azure-signalr/concept-service-mode#serverless-mode).\n\n# How to run locally\n\n* Clone this repo.\n* In the main project's root folder (the one that contains host.json) create a **local.settings.json** file, which should look like this:\n  ```\n  {\n      \"IsEncrypted\": false,\n      \"Values\": {\n          \"AzureWebJobsStorage\": \"\u003cconnection-string-to-your-azure-storage-account\u003e\",\n          \"AzureSignalRConnectionString\": \"\u003cconnection-string-to-your-azure-signalr-service-instance\u003e\",\n          \"AzureSignalRHubName\": \"DurableMvcTestHub\",\n          \"FUNCTIONS_WORKER_RUNTIME\": \"node\"\n      }\n  }\n  ```\n* In that same root folder run:\n  ```\n  npm install\n  npm run build\n  func start\n  ```\n* Navigate to `http://localhost:7071` with your browser.\n\nIn a matter of seconds a new instance of [CounterEntity](https://github.com/scale-tone/durable-mvc-starter/blob/main/DurableEntities/CounterEntity.ts) will be created and rendered in your browser. Try to open that page in multiple browser tabs and observe the state being automatically synchronized across them. Also try to kill/restart the Functions host process (func.exe) and observe the state being preserved.\n\nOnce created, you can also monitor your Durable Entities with [Durable Functions Monitor](https://github.com/scale-tone/DurableFunctionsMonitor).\n\n# How to deploy to Azure\n\nYou can deploy the contents of this same repo with this\n[![Deploy to Azure](https://aka.ms/deploytoazurebutton)](https://portal.azure.com/#create/Microsoft.Template/uri/https%3A%2F%2Fraw.githubusercontent.com%2Fscale-tone%2Fdurable-mvc-starter%2Fmain%2Farm-template.json) button. It will create a Function App instance, an underlying Storage account and an Azure SignalR service instance. *Don't forget to remove those resources once done.*\n\nOnce you cloned this repo and added some code to your copy, you then deploy it [in the same way as you would normally deploy an Azure Functions Node.js project](https://docs.microsoft.com/en-us/azure/azure-functions/functions-reference-node?tabs=v2#deploying-with-dependencies).\n\n# How to define your entities\n\nAnywhere in your codebase (except the `ui` folder) create a class derived from [DurableEntity\\\u003cTState\\\u003e](https://github.com/scale-tone/durable-mvc-starter/blob/main/common/DurableEntity.ts). Methods of that class, that you intend to make your signal handlers, are expected to take **zero or one** parameter. The state is available to your code via `this.state` property, and it will be loaded/saved automatically. \n\nThe default visibility level for an entity is `VisibilityEnum.ToOwnerOnly` (which means that only the creator will be able to access it from the client and change notifications will only be sent to the creator), to change it override the [DurableEntity.visibility](https://github.com/scale-tone/durable-mvc-starter/blob/main/common/DurableEntity.ts#L41) property. \n\nTo do a custom state initialization for a newly created entity instance override the [DurableEntity.initializeState()](https://github.com/scale-tone/durable-mvc-starter/blob/main/common/DurableEntity.ts#L36) method.\n\nThe required boilerplate (`index.ts` and `function.json` files) for exposing your class as a Durable Entity [will then be autogenerated for you](https://github.com/scale-tone/durable-mvc-starter/blob/main/generate-entity-boilerplate.js). Once autogenerated, you will be able to modify those files, e.g. add more bindings, if your entity requires them.\n\n# How to bind to your entities on the client\n\n[DurableEntitySet](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts) provides static methods, that return a single observable state object: \n* [createEntity()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L112) - creates an entity with given key, if not created yet.\n* [attachEntity()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L88) - doesn't create anything, just tries to attach to an existing entity.\n\nTo get an [observable collection](https://mobx.js.org/observable-state.html) of entities of a certain type create an instance of [DurableEntitySet\\\u003cTState\\\u003e](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts) class and then bind to its [items](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L18) property. Newly added entities will automatically appear there and removed (destroyed) entities will automatically be dropped.\n\nTo send signals to your entities use: \n* [DurableEntitySet.signalEntity()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L121) - sends a signal in a fire-and-forget manner. \n* [DurableEntitySet.callEntity()](https://github.com/scale-tone/durable-mvc-starter/blob/main/ui/src/common/DurableEntitySet.ts#L131) - 'calls' an entity aka returns a Promise, that will be resolved once the sent signal actually gets processed.\n\n# How to handle authentication\n\nTo determine which entity instances should be visible to which particular user, backend code needs to be able to identify that user somehow. By default it relies on [Easy Auth module](https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) while doing that, which means that Easy Auth needs to be [properly configured](https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization) for your Azure Functions app instance (and this instance needs to run in Azure).\n\nWhen you configure it for so called [server-directed flow](https://docs.microsoft.com/en-us/azure/app-service/overview-authentication-authorization#authentication-flow) (aka cookie-based, aka no client-side SDK involved), then that's basically it - the backend will identify the calling user automatically, using the authentication cookie that comes with each request.\n\nIn many cases though you might want to implement client-side authentication with some client-side SDK, e.g. [MSAL](https://www.npmjs.com/package/@azure/msal-browser). In that case the backend will expect an access token to be passed with every request, and you will need to provide that access token by calling the `DurableEntitySet.setup()` method like that:\n```\nDurableEntitySet.setup({\n\n    // Implement this method to provide DurableEntitySet with an access token\n    accessTokenFactory: () =\u003e {\n        const myAccessToken = \"oauth-access-token-obtained-from-somewhere\";\n        return Promise.resolve(myAccessToken);\n    }\n});\n```\n\nNote that `accessTokenFactory` should be a method returning a promise. It will be called every time an access token is needed.\n\nFor *demo and test* purposes (e.g. when running everything on your local devbox) you might just want to provide some test user name. Then call the `DurableEntitySet.setup()` method like this:\n```\nDurableEntitySet.setup({\n\n    fakeUserNamePromise: Promise.resolve('test-anonymous-user'),\n});\n```\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscale-tone%2Fdurable-mvc-starter","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fscale-tone%2Fdurable-mvc-starter","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fscale-tone%2Fdurable-mvc-starter/lists"}