Skip to content
Pedlar Logo
Pedlar
GithubLinkedIn

Easy Static Web App & Azure Function Development

azure4 min read

Introduction

Running a full stack application locally can be tricky and awkward. In this article we show you how to get a Static Web App talking to an Azure Function locally, using the SWA CLI. The SWA CLI is a local developer tool for Azure Static Web Apps and some things it can do are:

  • Serve static app assets, or proxy to your app dev server
  • Serve API requests, or proxy to APIs running in Azure Functions Core Tools
  • Emulate authentication and authorization
  • Emulate Static Web Apps configuration, including routing and ACL roles
  • Deploy your app to Azure Static Web Apps

Once setup, all you need to type is:

swa start ./client --api-location ./api

This article is based off this video: Serverless Full-Stack Kickstart with Davide Mauri.

Prerequisites

  • Npm
  • Homebrew
  • Azure Cli (to deploy)

Recommended

Install

Functions Cli

brew tap azure/functions
brew install azure-functions-core-tools@4
# if upgrading on a machine that has 2.x or 3.x installed:
brew link --overwrite azure-functions-core-tools@4

Azure Functions Core Tools

Static Web App (SWA) Cli

npm install -g @azure/static-web-apps-cli

https://azure.github.io/static-web-apps-cli/docs/use/install/

Host SWA Locally with Cli

Create Directories

mkdir swa-test && cd swa-test
mkdir client && mkdir api

Create Basic Web Client

cd client && curl https://raw.githubusercontent.com/vuejs/vuejs.org/master/src/v2/examples/vue-20-todomvc/index.html -o index.html

Serve Web Client Locally

swa-test/client/
cd ..
swa start client

Go to the localhost address to see your basic todo website running e.g. http://localhost:4280

Connect to Api

Create Azure Function

swa-test/api/
cd api
func init --worker-runtime dotnet
func new --name ToDo --template HttpTrigger

Run Client & Api

swa-test/
cd ..
swa start ./client --api-location ./api

Both the SWA and Azure Function should start running locally and you can view them on localhost.

To test the Azure Function use Postman (or an alternative) with

curl http://localhost:7071/api/todo?name=test

You should get a response similar to:

Hello, test. This HTTP triggered function executed successfully.

However, the client is not talking to the api yet so we need to make some changes.

Update Client

If you manage to follow the code below you’ll have the client and api talking via the GET method and it will load in the ToDo when the client starts up.

If you want to add the full Add, Edit, Delete, Update functionality (or just want to skip the code below) have a look at the full code at the repo: https://github.com/tombrereton/swa-cli

swa-test/client/index.html
<script>
+ API = "api/todo";
+ HEADERS = {'Accept': 'application/json', 'Content-Type': 'application/json'};
- var todoStorage = {
- fetch: function() {
- var todos = JSON.parse(localStorage.getItem(STORAGE_KEY) || "[]");
- todos.forEach(function(todo, index) {
- todo.id = index;
- });
- todoStorage.uid = todos.length;
- return todos;
- },
- save: function(todos) {
- localStorage.setItem(STORAGE_KEY, JSON.stringify(todos));
- }
- };
var app = new Vue({
// app initial state
data: {
- todos: todoStorage.fetch(),
+ todos: [],
newTodo: "",
editedTodo: null,
visibility: "all"
},
+ created: function() {
+ fetch(API, {headers: HEADERS, method: "GET"})
+ .then(res => {
+ return res.json();
+ })
+ .then(res => {
+ this.todos = res == null ? [] : res;
+ })
+ },
- // watch todos change for localStorage persistence
- watch: {
- todos: {
- handler: function(todos) {
- todoStorage.save(todos);
- },
- deep: true
- }
- },
</script>

Update Api

swa-test/api/ToDo.cs
public static class ToDoHandler
{
static List<ToDo> _db = new List<ToDo>();
static int _nextId = 1;
static ToDoHandler()
{
_db.Add(new ToDo { Id = 1, Title = "Hello from Azure Function!", Completed = true });
_db.Add(new ToDo { Id = 2, Title = "Hello, from the other side", Completed = false });
}
[FunctionName("Get")]
public static async Task<IActionResult> Run([HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "todo/{id:int?}")]
HttpRequest req, ILogger log, int? id)
{
if (id == null)
return new OkObjectResult(_db);
var todoTask = _db.Find(i => i.Id == id);
if (todoTask == null)
return await Task.FromResult<IActionResult>(new NotFoundResult());
return await Task.FromResult<IActionResult>(new OkObjectResult(todoTask));
}
}
internal class ToDo
{
public int Id { get; internal set; }
public string Title { get; internal set; }
public bool Completed { get; internal set; }
}