Serverless in Azure using Static Web Apps, Functions and Cosmos DB
Tags: azure, azurefunctions, staticwebapps, cosmosdb
Published: 2023-01-16 06:20:51

####_In this post, we look at Serverless development on Azure. First we look at the tools and packages necessary to develop locally. Then we create a static site using Azure Static Web Apps (SWA). Then we look at the built in authentication support provided by SWA as we create the authentication layer to ensure users can access their data securely. Then we look at how SWA supports building APIs using built in support for HTTP-triggered functions as we create and Integrate APIs to the static web site using .Net6 and C#. Then we create the data layer in Cosmos DB and integrate the same to functions built earlier. Finally we look at the CI/CD support provided where 'Github repository changes' trigger builds and deploy the solution to Azure._

##Problem Statement
The people at helm at the insistence of HR Department have decided to have a Calendar system. Though the solution is supposed to have loads of features, the decision is to build the whole iteratively and a basic Proof Of Concept (POC) is to be developed first. A person at helm attended a conference where s/he heard about "serverless" which allows on-demand scaling and also brings down TCO of a solution with “pay-as-you-go” usage and so one of the requirements for the POC is to make the entire solution using serverless technologies.

##Solution
Compute options in serverless services are varied in Azure. One could choose Serverless Containerized Microservices using Azure Container Apps OR Serverless Kubernetes using AKS Virtual Nodes OR Serverless functions using Azure Functions OR the newest entrant- Azure Static Web Apps.

Azure Static Web Apps supports static content hosting, APIs powered by Azure Functions, local development experience, CI/CD workflows, global availability, dynamic scale, preview environments and all this without the necessity to manage servers, creating & assigning SSL certificates, establishing reverse proxies, etc.

There are two options in Databases amongst serverless services in Azure. The relational Azure SQL Database serverless and the non relational Azure Cosmos DB.

Azure Cosmos DB is a fully managed NoSQL database which offers features such as Change Data Capture and multiple database APIs - NoSQL, MongoDB, Cassandra, Gremlin, & Table enabling one to model real world data using documents, column-family, graph, and key-value data models.

For the POC, the calendar system will enable users to authenticate themselves and view their events as well as add events to the calendar.
Static Web Apps with Cosmos DB will be used to implement these use cases. Visual Studio code will be used as IDE.

###Architecture Diagram with Application Development Lifecycle

![architecture](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4hjullo04y1ujo67nw8s.png)

##Prerequisites for local development

1. .net 6 sdk - [https://dotnet.microsoft.com/en-us/download/dotnet/6.0](https://dotnet.microsoft.com/en-us/download/dotnet/6.0)

2. Azure Functions Core Tools v4.x - [https://go.microsoft.com/fwlink/?linkid=2174087](https://go.microsoft.com/fwlink/?linkid=2174087)

3. Static Web Apps CLI - [https://azure.github.io/static-web-apps-cli/](https://azure.github.io/static-web-apps-cli/)

4. Azure Cosmos DB Emulator - [https://aka.ms/cosmosdb-emulator](https://aka.ms/cosmosdb-emulator)

5. Install Azure Functions Extension from Visual Studio Code Extensions Tab - [https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurefunctions)

6. Install Azure Static Web Apps Extension from Visual Studio Code Extensions Tab - [https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azurestaticwebapps)


![Visual Studio Code Extensions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/otj83ex7kpgknc43enx5.png)
###### Ensure you have .Net6 sdk (check by running command `dotnet --list-sdks`), even if you have other .Net sdks even a higher one such as .Net7. This is because Azure Functions have concept of In-process and Isolated worker process. This article will have steps supporting In-process whereas .Net7 is only supported in Isolated worker process.

##Build Static Site
To get started a template in Github can be used. Go to [https://github.com/login?return_to=/staticwebdev/vanilla-basic/generate](https://github.com/staticwebdev/vanilla-basic/generate) and in the page add _mycalendar_ in the Repository field and click on button _Create Repository from Template_ to create repository.

![Template](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/mfw4o0ep2m9kdal40yap.png)
###### Plain vanilla javascript template is used here. There are  more templates including angular, react, vue, blazor available at [https://github.com/staticwebdev](https://github.com/staticwebdev)

Open visual studio code and open a new terminal with bash.
Go to folder where you wish to do your development and run the following command to clone the github project to your local machine.

```
git clone https://github.com/<your_github_account>/mycalendar.git
```

In VSCode select File > Open Folder to open the cloned _mycalendar_ repository

Delete files _package.json_, _package-lock.json_, _playwright.config.ts_, entire _tests_ folder, entire _.devcontainer_ folder and both files (_playwright-onDemand.yml_ and _playwright-scheduled.yml_) in _.github/workflows_ folder (but do not delete this folder)

###Update _Github_ Repository
At the command prompt in terminal, run the following command to update changes in workspace to staging area.

```
git add --all
```
Check the changes to be committed.

```
git status
```
Commit changes from staging to the local repository.

```
git commit -m "Initial commit to create base repository"
```
Push code to Github 

```
git push -u origin main
```

![Update Github repository](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/vd1rf296xsswyn2hgu8y.png)
###Deploy to Azure
In Visual Studio Code, press _F1_ OR _Ctrl+Shift+P_ to open Command Palette.
Search and Select _Azure Static Web Apps:Create Static Web App.._. 
In ensuing screens, select your subscription, 
select an existing resource group or create a new one (if the screen is shown - not in below image), 
decide and select on a free plan or standard plan (if the screen is shown - not in below image), 
provide a name to the web app, 
and then select the region to deploy. 
The next screen provides a list of frontend frameworks. As the application is a vanilla javascript application, choose _Custom_.
Next, provide the location of application code - _/src_ as this is where index.html resides. 
Leave the API location blank for now (if the screen is shown - not in below image) 
and finally provide the location of build output also _/src_ (this is primarily useful if a framework such as angular, react, svelte, etc is used and in such cases, the build folder location goes here).

![Static Web App creation steps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ur6c7czf6o7au4ewneij.png)
Finally, a message is shown in Azure Activity log stating that the Azure Static Web Site is created and a git pull is executed to download a workflow file from github and kept in _.github/workflows_ folder. One of the best things about Static Web Apps is the fact that CI / CD is integrated using Github Action Workflows and anything that is now added to the workspace and then pushed to github repository will get deployed on Azure automatically.

![Static Web App creation completed](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/sl6321n9rkous8g5tdcj.png)
######Azure and Github credentials will have to be provided in the above process.

Check the all resources screen in Azure Portal to find the _mycalendar_ static web app. On selecting the same, a page loads which shows the static webapp URL, the source code, action run history and the workflow which was pulled to the workspace (the last three are github links).

![Azure Static Web App](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/asumsmwfbu7s0yexkb36.png)
Confirm that the static website URL loads.

###Create Application Interface
To create the calendar interface, the free and open-source [DayPilot Lite library](https://javascript.daypilot.org/download/) is used.

Download the library and add the _daypilot-all.min.js_ to the _src_ folder.


![src folder with html and js](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pataohd50kw0ejdeu2x7.png)

Update index.html code to the one below.

```
<!DOCTYPE html>
<html lang="en">

<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <script src="daypilot-all.min.js"></script>
  <title>My Calendar</title>
</head>

<body>
  <main>
    <h1>My Calendar</h1>
  </main>

  <div id="mycalendar"></div>

<script type="text/javascript">
    const mycalendar = new DayPilot.Month("mycalendar", {
        startDate: "2023-01-01",
        onTimeRangeSelected: async function (args) {

            const colors = [
                {name: "Blue", id: "#3c78d8"},
                {name: "Green", id: "#6aa84f"},
                {name: "Yellow", id: "#f1c232"},
                {name: "Red", id: "#cc0000"},
            ];

            const form = [
                {name: "Text", id: "text"},
                {name: "Start", id: "start", type: "datetime"},
                {name: "End", id: "end", type: "datetime"},
                {name: "Color", id: "barColor", options: colors}
            ];

            const data = {
                text: "Event",
                start: args.start,
                end: args.end,
                barColor: "#6aa84f"
            };

            const modal = await DayPilot.Modal.form(form, data);

            mycalendar.clearSelection();

            if (modal.canceled) {
                return;
            }

            mycalendar.events.add({
                start: modal.result.start,
                end: modal.result.end,
                id: DayPilot.guid(),
                text: modal.result.text,
                barColor: modal.result.barColor
            });
        }
    });

    mycalendar.events.list = [
    {
      "start": "2023-01-12T10:30:00",
      "end": "2023-01-12T15:30:00",
      "id": "225eb40f-5f78-b53b-0447-a885c8e92233",
      "text": "React Interview with Shirish Kumar",
      "barColor":"#cc0000"
    },
    {
      "start": "2023-01-16T12:30:00",
      "end": "2023-01-18T17:00:00",
      "id": "1f67def5-e1dd-57fc-2d39-eb7a5f8e789a",
      "text": "Kubernetes Interview with Ramesh Bhat",
      "barColor":"#3c78d8"
    },
    {
      "start": "2023-01-25T10:30:00",
      "end": "2023-01-25T16:00:00",
      "id": "aba78fd9-09d0-642e-612d-0e7e002c29f5",
      "text": "AAD Interview with Girish C",
      "barColor":"#cc0000"
    }
  ];

    mycalendar.init();


</script>
</body>

</html>
```

In the above code, a div with id `mycalendar` is added which is referenced in the javascript. `DayPilot.Month` ensures that the view is month based and the starting date of the calendar is stated using the `startDate` attribute.
`onTimeRangeSelected` section allows the user to add an event by either clicking on a single date or selecting multiple dates by dragging on screen. `DayPilot.Modal.form` provides a form with values to update and Save the event.
`mycalendar.events.list` adds existing event data by adding an array of data in a format the library expects.

In the bash terminal, go to _mycalendar_ folder and run the following command.

```
swa start src
```
######This command will be available if Static Web Apps CLI is installed. Testing and Debugging is one of the primary challenges with serverless as application is broken into smaller pieces and replicating the environment locally for developer is hard. Static Web Apps CLI solves this problem as we will see later. _src_ is the folder where static content including HTML, images, javascript and stylesheets are kept.

On running the command Azure Static Web Apps emulator starts and the calendar application can be accessed at http://localhost:4280.

Confirm that events added in code can be viewed as well as new events can be added by clicking on a date.

##Implement Authentication
Azure Static Web Apps has support for _GitHub_, _Twitter_, and _Azure Active Directory_ for authentication by default. Moreover, Static Web Apps CLI provides authentication emulator to mock responses from the three providers mentioned.

To enable login using _github_ update the HTML main content area (between <main> tags) to the content below.

```
<main>
    <h1>My Calendar</h1>
    <p>
    <div id="login" style="display: flex; justify-content: end;"><a href="/.auth/login/github">Login</a></div>
</main>
```
After authentication, to get access to user information such as user id / email, Azure Static Web Apps provides an API endpoint which means not only do developers not have to implement and maintain any oauth related code but also the endpoint does not face serverless architecture challenges like cold start delays.

Update the `<script>` area with code below under `mycalendar.init()`


```
    mycalendar.init(); //Add the code below
    
    const app = {
      getUserInfo() {
      return fetch('/.auth/me')
        .then(response =>{
            return response.json();
          }).then(data =>{
            const { clientPrincipal } = data;
            console.log(clientPrincipal);
            if (clientPrincipal !=null){
              const userDetails= clientPrincipal.userDetails;
              return userDetails;
            }
            return null;
          })
      },
      init(){
        app.getUserInfo()
            .then(user =>{
             console.log(user); 
            })
      }
    };
    app.init();
  </script>
```
In the above code `init` function calls `getUserInfo` function which in turn calls the _direct-access endpoint_ `/.auth/me` and from the resultant response gets the _github userid_ (provided authenticated by github, else returns null) which gets logged in the browser console.

In the bash terminal, run the following command again.
```
swa start src
```
Load the web page in a browser in incognito mode. After clicking on the login button, the emulator mock screen comes up.

![SWA Auth emulator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4ouaqpzdi1vw0d78b91f.png)
In the mock screen, add your first name in the Username field as shown in image (_arnab_ is shown in image) and select Login.

The calendar interface shows up as Index.html is loaded. Open developer tools and go to Console. ClientPrincipal data as well as Username is shown as in the image below.

![Developer tools console](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fabjbloy4ol0febmrmh3.png)

##Create API and integrate with Static Site
Running logic on the browser has certain limitations namely the ability to connect to data stores / databases to persist data and then retrieve the same. That is where the necessity to run some part of the code server side comes in.
Azure Static Web Apps supports serverless API endpoints powered by Azure Functions where HTTP request triggers the function. The API route is fixed at /api. Also, Azure Static Web Apps extension for Visual Studio Code creates the Function templates in api folder by default. Also, in local development environment API will run in port 7071 and not port 4280 where the static site runs. This in normal cases will lead to Cross-Origin Resource Sharing (CORS) errors like _Access to XMLHttpRequest at ""http://localhost:7071/api/events"" from origin ""http://localhost:4280"" has been blocked by CORS policy_. But, Azure Static Web Apps (using Reverse Proxy) as well as the CLI (for local development scenario) takes care of this challenge as it makes the static web app and API appear to come from the same domain.

The calendar application allows users to view their existing events as well as add new events to the calendar.

For viewing existing events use case, _GET_ method at route endpoint _events_ will be used which means that the full api endpoint at the static site will be _api/events_
For adding new events use case, POST method at the same endpoint can be used.
### View existing events use case
In Visual Studio Code, press _F1_ OR _Ctrl+Shift+P_ to open Command Palette. Search and Select _Azure Static Web Apps: Create HTTP Function_. 
In ensuing screens select _C#_ as language, 
add _GetEvents_ as Function Name, 
add `<Your_First_Name>.MyCalendar` as Namespace (_Arnab.MyCalendar_ in my case) 
and _Anonymous_ as Access Rights (Good enough for POC scenarios but never use this setting in production).
######Security is important. Do check out [Azure Architecture - Serverless Functions security](https://learn.microsoft.com/en-us/azure/architecture/serverless-quest/functions-app-security) 

![Create HTTP Function steps](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/b53jkrfy8bjavccw3kqb.png)

A new folder in the workspace gets created named _api_ and a Functions project gets created.

###### A simple piece of code is written next just sufficient to test the integration of API with static site.
 
In GetEvents.cs file update the contents to the code below. 

```
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using System.Text.Json;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;


namespace Arnab.MyCalendar
{
    public static class GetEvents
    {
        [FunctionName("GetEvents")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "events")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("Get Events list");
            string username = req.Query["u"];
            log.LogInformation(name);
            string json = @"[
                        {
                        ""start"": ""2023-01-12T10:30:00"",
                        ""end"": ""2023-01-12T15:30:00"",
                        ""id"": ""225eb40f-5f78-b53b-0447-a885c8e92233"",
                        ""text"": ""React Interview with Shirish Kumar"",
                        ""barColor"":""#cc0000""
                        },
                        {
                        ""start"": ""2023-01-16T12:30:00"",
                        ""end"": ""2023-01-18T17:00:00"",
                        ""id"": ""1f67def5-e1dd-57fc-2d39-eb7a5f8e789a"",
                        ""text"": ""Kubernetes Interview with Ramesh Bhat"",
                        ""barColor"":""#3c78d8""
                        },
                        {
                        ""start"": ""2023-01-25T10:30:00"",
                        ""end"": ""2023-01-25T16:00:00"",
                        ""id"": ""aba78fd9-09d0-642e-612d-0e7e002c29f5"",
                        ""text"": ""AAD Interview with Girish C"",
                        ""barColor"":""#cc0000""
                        }
                    ]";
            List<Dictionary<string, string>> results=null;
            if (username == "arnab"){
            results =JsonSerializer.Deserialize<List<Dictionary<string, string>>>(json);
            }
            return new OkObjectResult(results);
        }
    }
}

```
In the above code we first specify the HTTP method (GET) and route (events).
The API accepts a querystring 'u' which contains the username value.
There is a hardcoded json string on similar lines as in index.html which is deserialized and returned provided the username is equal to a hardcoded value.
###### The hardcoded value here is _arnab_ but you should update that to your first name provided you are going to use that as username in mock authentication screen. Also do remember to change the namespace to `<Your_First_Name>.MyCalendar`.

To call this API from frontend, update _index.html_. Block comment or remove `mycalendar.events.list` code, add a `loadEvents` function which will call the API and call this function from `init` as code shown below.

```
/* mycalendar.events.list = [
      {
        "start": "2023-01-12T10:30:00",
        "end": "2023-01-12T15:30:00",
        "id": "225eb40f-5f78-b53b-0447-a885c8e92233",
        "text": "React Training",
        "barColor":"#cc0000"
      },
      //more data below
    ]; */
    
    mycalendar.init();
    
    const app = {
      loadEvents(user) {
        console.log(user);
        var url = new URL('/api/events')
        var params = {u:user}
        url.search = new URLSearchParams(params).toString();
        //console.log(url);
        fetch(url)
          .then(response =>{
            return response.json();
          }).then(data =>{
            //console.log(data);
            mycalendar.update({
              events: data
            });
          })                       
      }, //next there will be getUserInfo()
      init(){
        app.getUserInfo()
            .then(user =>{
             console.log(user); 
             if (user !=null){
              app.loadEvents(user);  
              }
            })
      }
    };
    app.init();
```
In the code above `getUserInfo` method is called and if usename is not null, `loadEvents` method is called where the username is added to the API endpoint as querystring / search parameters and the return data is updated in the calendar.  

To test the API and its integration with the static site, in the bash terminal, run the following command.
```
swa start src --api-location api
```
When the web site loads, login with <your_first_name> as username in the ensuing emulator mock screen. The calendar gets updated with data but this time the data is sent from server.

###### In case you face any challenge / errors in running the above SWA CLI command, open a new bash terminal, ensure you are in api folder, update API urls in index.html from '`/api/events`' to '`http://localhost:7071/api/events`' and run the following command to run just the Azure function and in the first terminal run _swa start src_ as before. This style of running is also useful in debugging as you bifurcate the running of frontend site and backend API. But, do remember to switch back the urls to its earlier form before publishing the application to Azure.

```
func start --cors http://localhost:4280 --port 7071
```
### Add new events use case
Create a new HTTP Function as before and name this PostEvents.

In PostEvents.cs file update the contents to the code below.

```
using System;
using System.IO;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Newtonsoft.Json;

namespace Arnab.MyCalendar
{
    public static class PostEvents
    {
        [FunctionName("PostEvents")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "events")] HttpRequest req,
            ILogger log)
        {
            log.LogInformation("Post Event");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            dynamic data = JsonConvert.DeserializeObject(requestBody);
            string cuser = data?.cuser;
            dynamic cevent=data?.cevent;
            log.LogInformation(cuser);
            string eguid =Guid.NewGuid().ToString(); 
            cevent.id=eguid;
            return new OkObjectResult(cevent);
        }
    }
}
```
In the above code, username and event data is being retrieved from request body, a Guid is created and added to the event data and the event data with Guid is returned.

To call this API from frontend, update _index.html_. Add an `addEvents` function and update the mycalendar `onTimeRangeSelected` function.The final script section in index.html is as code shown below

```
  <script type="text/javascript">
      const mycalendar = new DayPilot.Month("mycalendar", {
          startDate: "2023-01-01",
          onTimeRangeSelected: async function (args) {
  
              const colors = [
                  {name: "Blue", id: "#3c78d8"},
                  {name: "Green", id: "#6aa84f"},
                  {name: "Yellow", id: "#f1c232"},
                  {name: "Red", id: "#cc0000"},
              ];
  
              const form = [
                  {name: "Text", id: "text"},
                  {name: "Start", id: "start", type: "datetime"},
                  {name: "End", id: "end", type: "datetime"},
                  {name: "Color", id: "barColor", options: colors}
              ];
  
              const data = {
                  text: "Event",
                  start: args.start,
                  end: args.end,
                  barColor: "#6aa84f"
              };
              const modal = await DayPilot.Modal.form(form, data);
  
              mycalendar.clearSelection();
  
              if (modal.canceled) {
                  return;
              }
              app.getUserInfo()
              .then(user =>{
                console.log(user); 
                if (user !=null){
                  const event = {
                    start: modal.result.start,
                    end: modal.result.end,
                    text: modal.result.text,
                    barColor: modal.result.barColor
                  };
                  app.addEvents(user,event);  
                }
              })
            
          }
      });
  
     
    mycalendar.init();
    
    const app = {
      loadEvents(user) {
        console.log(user);
        var url = new URL('/api/events')
        var params = {u:user}
        url.search = new URLSearchParams(params).toString();
        //console.log(url);
        fetch(url)
          .then(response =>{
            return response.json();
          }).then(data =>{
            //console.log(data);
            mycalendar.update({
              events: data
            });
          })                       
      },
      addEvents(user,event){
        fetch('/api/events', {
                method: 'POST',
                headers: {
                    'Accept': 'application/json',
                    'Content-Type': 'application/json'
                },
                body: JSON.stringify({
                  cuser:user,
                  cevent:event,
                }),
            })
            .then(response =>{
              console.log(response);
            return response.json();
            }).then(data =>{
              console.log(data);
              mycalendar.events.add(data);
            }) 
      },
      getUserInfo() {
      return fetch('/.auth/me')
        .then(response =>{
            return response.json();
          }).then(data =>{
            const { clientPrincipal } = data;
            console.log(clientPrincipal);
            if (clientPrincipal !=null){
              console.log("inside clientprincipal not null");
              const userDetails= clientPrincipal.userDetails;
              return userDetails;
            }
            return null;
          })
      },
      init(){
        app.getUserInfo()
            .then(user =>{
             console.log(user); 
             if (user !=null){
              document.getElementById("login").style.display = "none";
              app.loadEvents(user);  
              }
            })
      }
    };
    app.init();
  </script>
```
The new code in `onTimeRangeSelected` calls `getUserInfo` and if username is not null sends the username and event data to `addEvents` function which makes the POST call to the API and updates the calendar with return data.

Once again test the API and its integration as before to confirm that the code works.

##Implement Persistence layer with Cosmos DB
Run the Cosmos DB emulator. Right click and select _Open Data Explorer_.

![Cosmos DB emulator](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g82m6lq1nhawsbnld61n.png) 
Run the following command in the bash terminal to configure the connection string (available from _Primary Connection String_ in Explorer screen) in the function project settings in the _local.settings.json_ file.

```
func settings add CosmosDBConnection "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==" --connectionString
```
_local.settings.json_ file should now be added to the connectionstring as below.
```
  "ConnectionStrings": {
    "CosmosDBConnection": {
      "ConnectionString": "AccountEndpoint=https://localhost:8081/;AccountKey=C2y6yDjf5/R+ob0N8A7Cgv30VRDJIWEHLM+4QDU5DE2nQ9nDuVTqobD4b8mGGyPMbIZnqyMsEcaGQy67XIw/Jw==",
      "ProviderName": "System.Data.SqlClient"
    }
  }
```
######Emulator connection string is same always unless changed manually. Also _local.settings.json_ is not pushed to github and the connection string there is only useful for local development. The command / process to add connection string in Azure is different and we will see so later.

In the emulator screen select _Explorer_ and then _New Container_.
Add a new Database with name _myCalendar_, state the Container Name as _eventsCollection_ and Partition Key _/userName_.

![Cosmos DB -Create Database and Container](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/k835s01tkdak5ijb8ixp.png)
The database and Container can be viewed now in Explorer.

![Cosmos DB emulator explorer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/rcjdbt1tdh94577o96ue.png)

An easy declarative way to connect Azure services including Cosmos DB to Azure functions is using bindings. Bindings are implemented in extension packages. Run the following dotnet add package command in the terminal to install the Cosmos DB extension package.
```
dotnet add package Microsoft.Azure.WebJobs.Extensions.CosmosDB --version 4.0.0
```
Add a new file in _api_ folder and name the same _event.cs_. This file will have two classes, one in the data structure format in which data arrives from frontend and the other is a structure format as the JSON data that will be persisted in Cosmos DB.
```
using System;
namespace Arnab.MyCalendar
{
    public class CalendarEvent
    {
        public string id { get; set; }
        public string userName{ get; set; }
        public string startsAt { get; set; }
        public string endsAt { get; set; }
    #nullable enable
        public string? eventTitle { get; set; }
        public string? barColor { get; set; }
        public DateTime eventCreateDate{ get; set; }
    }

    public class ClientPostEvent
    {
        #nullable enable
        public string? id { get; set; }
        public string? start { get; set; }
        public string? end { get; set; }
        public string? text { get; set; }
        public string? barColor { get; set; }
    }

    public class ClientData
    {
        public string cuser { get; set; }
        public ClientPostEvent cevent { get; set; }
    }
}
```
In the code above `CalendarEvent` will be used to persist data in Cosmos DB.

Update the contents of PostEvents.cs to the code below.

```
using System;
using System.IO;
using System.Text.Json;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Arnab.MyCalendar
{
    public static class PostEvents
    {
        [FunctionName("PostEvents")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "post", Route = "events")] HttpRequest req,
            [CosmosDB(
            databaseName: "myCalendar",
            containerName: "eventsCollection",
            Connection = "CosmosDBConnection")]
            IAsyncCollector<CalendarEvent> eventsOut,ILogger log)
        {
            log.LogInformation("Post Event");

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
            ClientData data = JsonSerializer.Deserialize<ClientData>(requestBody);
            string cuser = data?.cuser;
            ClientPostEvent cevent=data?.cevent;
            log.LogInformation(cuser);
            string eguid =Guid.NewGuid().ToString(); 
            cevent.id=eguid;
            CalendarEvent nevent = new CalendarEvent() { 
                id = cevent.id,
                userName= cuser,
                startsAt=cevent.start,
                endsAt=cevent.end,
                eventTitle=cevent.text,
                barColor=cevent.barColor,
                eventCreateDate=DateTime.Now
                };
            
            await eventsOut.AddAsync(nevent);
            return new OkObjectResult(cevent);
        }
    }
}
```
In the code above, the presence of bindings enable in connecting to database seamlessly with the attribute `CosmosDB`. Another parameter `eventsOut` is of type `IAsyncCollector<CalendarEvent>` ensuring that any instance of it calling `AddAsync` gets an instance of `CalendarEvent` persisted in the database.

Update the contents of GetEvents.cs to the code below.

```
using System;
using System.IO;
using System.Threading.Tasks;
using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Http;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;

namespace Arnab.MyCalendar
{
     public static class GetEvents
    {
        [FunctionName("GetEvents")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Anonymous, "get", Route = "events")] HttpRequest req,
            [CosmosDB(
                databaseName: "myCalendar",
                containerName: "eventsCollection",
                Connection = "CosmosDBConnection")] CosmosClient client,
            ILogger log)
        {
            log.LogInformation("Get Events list");
            string name = req.Query["u"];
                        
            List<Dictionary<string, string>> results=new  List<Dictionary<string, string>>();
            Container myContainer = client.GetDatabase("myCalendar").GetContainer("eventsCollection");
            QueryDefinition queryDefinition = new QueryDefinition(
                "SELECT * FROM items i WHERE (i.userName = @searchterm)")
                .WithParameter("@searchterm", name);
            string continuationToken = null;
            do
            {
            FeedIterator<CalendarEvent> feedIterator = 
                myContainer.GetItemQueryIterator<CalendarEvent>(
                        queryDefinition, 
                        continuationToken: continuationToken);

                while (feedIterator.HasMoreResults)
                {
                    FeedResponse<CalendarEvent> feedResponse = await feedIterator.ReadNextAsync();
                    continuationToken = feedResponse.ContinuationToken;
                    foreach (CalendarEvent item in feedResponse)
                    {
                        results.Add(new Dictionary<string, string>(){
                                    {"start", item.startsAt},
                                    {"end", item.endsAt},
                                    {"id", item.id},
                                    {"text", item.eventTitle},
                                    {"barColor", item.barColor}
                        });
                    }
                }
            } while (continuationToken != null);
            
            return new OkObjectResult(results);

        }
    } 
}
```
In the above code, a Azure Cosmos DB binding provided `CosmosClient` instance, available in extension version 4.x, is used to read a list of documents. With the access of `CosmosClient` instance, one can do complex stuff with Cosmos DB in Static Web Applications / Functions. 
Here, `CosmosClient` object is used to configure and execute requests against the Azure Cosmos DB service. `Database` is the reference to database and `Container` a reference to container and they both are validated serverside.
`QueryDefinition` helps with the query and its parameters. `FeedIterator<>` helps in tracking the current page of results and getting a new page of results while `FeedResponse<>` represents a single page of responses which is iterated over using a foreach loop.
Also notice the usage of `continuationToken` and `.WithParameter` goodness. 
######Though this code gets all events of a user, in production scenarios you would not wish to do that, and along with username also send start date and end date in query string to ensure limited amount of data is accessed or retrieved.

To test open a new browser in incognito / inPrivate mode, login and confirm that you are able to add new events.
To check whether the events are being persisted, check the Cosmos DB emulator explorer screen to confirm data is being persisted. 

![Cosmos DB emulator- Data Persisted](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/9pt5gpta4posusksgele.png)
Next close the browser and open another browser again in incognito / inPrivate mode, login and confirm that you are able to view the events added earlier and persisted in Cosmos DB.
##Deploy application to Azure
###Provision Database
Log in to Azure Portal, Search for Cosmos DB and select the top result.
Next create Cosmos DB account by selecting _Create_ under _Azure Cosmos DB For NoSQL_ box. 

![Cosmos DB Account API selection](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/hksvdrkix0ozho84r53q.png)
In the next screen, select subscription, select an existing Resource Group or create a new one, add an account name, choose the nearest location and choose _Serverless_ in capacity mode.
Backup policy can be changed as well and locally redundant backups selected (sufficient for POC). Selecting _Review + Create_ button provisions the database.

![Cosmos DB account creation](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/i1jlveki19upmayuq8z9.png)
######One could also try Cosmos DB by going to [cosmos.azure.com/try](https://cosmos.azure.com/try). Selecting the account type - _Azure Cosmos DB For NoSQL_ would create a trial account for 30 days which would open in Azure Portal.

![Try cosmos db](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/fbleuybe1j9meqc4y9an.png)
Once Azure Cosmos DB account page opens up, select the Data Explorer tab and a similar interface as the Data Explorer emulator opens up. Select _New Container_ box, Add a new Database with name _myCalendar_, state the Container Name as _eventsCollection_ and Partition Key _/userName_.
Once the database is created, select the _Connect_ box to view the connection string (OR select `Key` under `Settings` in left bar).

![Azure Cosmos DB Data Explorer](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/ici4rb9eknn448p0pyz4.png)
Copy the same and update the _local.settings.json_ connection string in Visual Studio Code.
Run the application and open it in a browser, add new events and confirm that they persisted in Azure Cosmos DB by viewing them in Azure Cosmos DB data explorer.

###The CI / CD Magic 
The workflow file in _.github/workflows_ folder needs to be updated to let know the location of api code.
Search for `api_location` in _Repository/Build Configurations_ section and update the value to `/api`.

![Update workflow file](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1a0291pguoektr1re1b9.png)
Cosmos DB connection string was configured in _local.settings.json_ file for local development. But, this file is not available in production environment.
To configure the connection string in production, go to  _mycalendar_ Static Web App page in Azure Portal, and select _Configuration_ from the left hand bar. Click _Add_ and in the next screen add _CosmosDBConnection_ as Name and Azure Cosmos DB connection string as Value. Click _OK_ and then _Save_.

![Update Application Setting - Cosmos DB Connectionstring](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/1mtf92gp0mq1229hjwg2.png)

In Visual Studio Code Terminal, open a command prompt, and from _mycalendar_ folder run the following command to commit all code changes and push them to Github.
######Though in this article / post, this step is done at the very end, in application development lifecycle, this ought to be done after each small change (after interface design, after authentication implementation, after API addition, etc )  
```
git add -A 
git status
git commit -m "Final Code with GET, POST, Auth and Azure Cosmos DB"
git push -u origin main
```

![Push to Github](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/nxlacta0v5fdvpjcvwye.png)

And then the magic happens in the _Actions_ tab in github repository.

![Github Actions](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/pwuexepk0aj9mnkuob2b.png)

######I would strongly urge all to check the steps (_Build Azure_ and _Build and Deploy_)  to understand all that happens automatically. 

Once this step completes, the application is built on Azure and is ready for users.

###Run Application
Copy the URL from _mycalendar_ Static Web Apps page in the Azure Portal and paste it in a new browser window / tab.
On clicking Login, the application does not show the mock screen any more and goes to github to authenticate.

![Github Auth Screen](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/uvipw3m4kn2hv46tc4xu.png)

Then there is a consent screen from Microsoft and then the Login steps are complete.
Add new events, confirm that they persisted (Azure Cosmos DB data explorer) and then open the page in another tab to check the loading of existing events.

![Calendar Application](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/7kkbgeq4f5nrwzb1vkit.png)

##Challenges
Deploying a modern web application ain't easy.

Even though in this article / post, vanilla javascript is used (to ensure that this article is useful to all developers with any framework skills such as angular, react, svelte, etc.), in production use cases some framework would be used. This application will have to be built and bundles generated.

While building the application all routes must be carefully configured to ensure users do not receive 404 errors.

APIs would have to be built and it is very much possible that different APIs are built using different technologies / languages.

User authentication will have to be implemented as well as APIs secured.

So, 'multiple servers' or even 'a cluster of servers' would have to be setup (to ensure reliability and availability) to host these bundles and APIs and if the bundles and APIs are hosted on different servers, CORS would have to be implemented or a Reverse Proxy configured. Also, a global CDN for the frontend bundles will be needed.

SSL will have to be configured as well as the ability to have custom domains added.

When maintainability of the application over a period of time is taken into account, a staging environment (similar to production) as well as an automated build process is necessary as well.

Earlier even if one used cloud services, s/he would have to do all these.

Azure Static Web Apps takes care of all the above challenges and ensures that the development team can focus on business requirements.

##Business Benefit
The application developed can be used as a base for any scheduling application. Also, any application built on the lines mentioned in the article / post using Serverless technologies like Static Web Apps and Cosmos DB is beneficial as no necessity of server management, charges for consumed storage only, better scalability, lower latency, quick updates, IDE support (VS Code and SWA CLI), etc all lead to accelerated innovation.  


##Final Notes
Serverless on Azure - [https://azure.microsoft.com/en-in/solutions/serverless/](https://azure.microsoft.com/en-in/solutions/serverless/)
Azure Static Web Apps - [https://azure.microsoft.com/en-gb/products/app-service/static](https://azure.microsoft.com/en-gb/products/app-service/static)
Azure Cosmos DB - [https://azure.microsoft.com/en-in/products/cosmos-db/](https://azure.microsoft.com/en-in/products/cosmos-db/)
SWA Templates - [https://github.com/staticwebdev](https://github.com/staticwebdev)
DayPilot Lite library - [https://javascript.daypilot.org/download/](https://javascript.daypilot.org/download/)
Source Code Repository - [https://github.com/c-arnab/mycalendar](https://github.com/c-arnab/mycalendar)