When creating a new tab project for Microsoft Teams using the Microsoft Teams Toolkit for Visual Studio Code extension (TTK), you’ll see several project template options. In my courses, I notice most developers choose to start with the React with Fluent UI template:
This template creates two separate projects:
- The root of the project is a mix of a React app based on the Create React App utility with TTK-specific additions.
- An Azure Function project in the ./api folder.
The template provides everything you need for a tab-based project (personal app or channel/group chat tab), including:
- Setup for single-sign-on (SSO)
- Azure Function
- Configuration to restrict calls to your tab project through supported Microsoft 365 apps (ie: the desktop, mobile and web clients for Microsoft Teams, Outlook, Microsoft 365, etc)
- Code to leverage the user’s OpenID Connect token (aka: bootstrap token or SSO token) for obtaining Microsoft Graph access tokens via OAuth2 On-Behalf-Of (OBO) flow.
- Project configuration to start two web servers
- one for the client-side React app
- one for the Azure Function app using the Azure Function Core Tools
But what if you don’t need all that stuff in the Azure Function? What if you just need the client-side app?
In fact, that OBO token sample might become unnecessary once Nested App Authentication (currently in preview) is fully supported in Microsoft Teams.
If you don’t need the Azure Function, you probably want to remove it.
But how can you do that?
That’s exactly what BillB asked during my monthly office hours livestream in December 2024!
Microsoft 365 Fullstack Dev's Office Hours - December 2024
Join us live, or watch the recording, from our monthly office hours live stream for Microsoft 365 Fullstack Developers!
https://www.voitanos.io/webinars/microsoft-365-full-stack-office-hours-2024-12-december/
I host these public sessions on the third Tuesday of each month, streaming live on my YouTube channel. Find the next session at https://vtns.io/office-hours.
Newsletter subscribers can opt in for an early reminder and submit questions or discussion topics for the upcoming session!
In this article, I’ll walk you through removing the Azure Function from your TTK tab project step by step. While there are several steps involved, I’ve documented everything in detail for you.
Let’s get started!
Update the Teams Project Local Development
First, we need to remove the configuration that starts a local instance of the Azure Function. This involves two main parts:
- Modify the VS Code tasks that start the local web servers for both the client-side React app and back-end Azure Function.
- Update the TTK project to remove all Azure Function references.
Step 1: Edit the VS Code tasks
Let’s begin by updating the VS Code tasks that TTK uses to start both projects. These are configured through VS Code tasks.
The VS Code Run & Debug window shows a list of defined launch tasks. Let’s look at the Debug in Teams (Edge) task as an example…
… is defined in the ./.vscode/launch.json file as a compound command:
{
"name": "Debug in Teams (Edge)",
"configurations": [
"Attach to Frontend in Teams (Edge)",
"Attach to Local Service"
],
"preLaunchTask": "Start Teams App Locally",
"presentation": {
"group": "group 1: Teams",
"order": 1
},
"stopAll": true
}
This command references several configurations defined in the launch.json file:
- Attach to Frontend in Teams (Edge): launches and attaches to a new instance of the Microsoft Edge browser.
- Attach to Local Service: attaches the VS Code debugger to the Node.js service.
The launch configuration includes a preLaunchTask
called Start Teams App Locally, which is defined in ./.vscode/tasks.json. We’ll modify this task to prevent VS Code from compiling, starting, and watching the Azure Functions project:
{
"label": "Start Teams App Locally",
"dependsOn": [
"Validate prerequisites",
"Provision",
"Deploy",
"Start application"
],
"dependsOrder": "sequence"
},
The task references four (4) other tasks in the dependsOn
array. Of these, we need to edit two of them:
Validate prerequisites: this task checks that all required tools are installed, verifies your Microsoft 365 tenant login, and confirms necessary ports are available. For this task, remove port
7071
, which the Azure Functions Core Tools uses to host the local Azure Functions web service:{ "label": "Validate prerequisites", "type": "teamsfx", "command": "debug-check-prerequisites", "args": { "prerequisites": [ "nodejs", "m365Account", "portOccupancy" ], "portOccupancy": [ 53000, // tab service port -- 7071, // backend service port 9229 // backend inspector port for Node.js debugger ] } }
Start application: this task task starts a few processes to spin up the local web server for the React app (
*Start frontend*
) and the Azure Function web server (*Start backend*
). The latter isn’t needed so delete it:{ "label": "Start application", "dependsOn": [ ++ "Start frontend" -- "Start frontend", -- "Start backend" ] }
Now that you’ve removed the Start backend
task, you can delete it from the ./.vscode/tasks.json file. That task has another task it depends on, Watch backend
defined in it’s dependsOn
property. The Watch backend
task watches for code changes in the ./api folder that contains the Azure Function. When it sees a code change, it recompiles the TypeScript to JavaScript so you don’t have to start and stop the process yourself.
So, you can safely remove these two tasks from the ./.vscode/tasks.json file:
-- {
-- "label": "Start backend",
-- "type": "shell",
-- "command": "npm run dev:teamsfx",
-- "isBackground": true,
--
-- // omitted for brevity
--
-- "dependsOn": "Watch backend"
-- },
-- {
-- "label": "Watch backend",
-- "type": "shell",
-- "command": "npm run watch:teamsfx",
-- "isBackground": true,
--
-- // omitted for brevity
--
-- "presentation": {
-- "reveal": "silent"
-- }
-- }
Almost done… there’s one more thing you need to clean up.
One of the remaining tasks we do want to keep, Start frontend
, calls an NPM script that spins up both the frontend and backend processes:
{
"label": "Start frontend",
"type": "shell",
"command": "npm run dev-tab:teamsfx",
"isBackground": true,
"options": {
"cwd": "${workspaceFolder}"
}
// omitted for brevity
}
Open the project’s ./package.json file. You’ll need to modify the NPM script to remove both the backend process call and its associated NPM script:
{
// omitted for brevity...
"scripts": {
-- "dev:teamsfx": "concurrently \"npm run dev-tab:teamsfx\" \"npm run dev-api:teamsfx\"",
++ "dev:teamsfx": "npm run dev-tab:teamsfx",
"dev-tab:teamsfx": "env-cmd --silent -f .localConfigs npm run start",
-- "dev-api:teamsfx": "cd api && npm run dev:teamsfx",
"start": "react-scripts start",
"build": "react-scripts build",
"test": "echo \"Error: no test specified\" && exit 1",
"eject": "react-scripts eject"
},
// omitted for brevity...
}
Now that we’ve updated package.json and the VS Code tasks to remove Azure Function references, we need to modify the TTK project file that orchestrates these tasks.
Step 2: Edit the TTK local project file
Open the local development project file, ./teamsapp.local.yml, where we’ll make changes to the deploy
lifecycle task.
Locate and delete the task
devTool/install
. This ensures you have the Azure Functions Core Tools installed, but it’s not necessary since we’re removing it!-- - uses: devTool/install -- with: -- devCert: -- trust: true -- func: -- version: ~4.0.5455 -- symlinkDir: ./devTools/func -- # Write the information of installed development tool(s) into environment -- # file for the specified environment variable(s). -- writeToEnvironmentFile: -- sslCertFile: SSL_CRT_FILE -- sslKeyFile: SSL_KEY_FILE -- funcPath: FUNC_PATH
Locate and remove the second
cli/runNpmCommand
that targets theworkingDirectory: api
. This runsnpm installin the**./api**folder to download all dependencies for the Azure Functions project.Make sure you don’t remove the one that installs the dependencies in the root project for the React app. I’ve included both in the following snippet so it’s clear which one you should remove.- uses: cli/runNpmCommand name: install dependencies with: args: install --no-audit -- - uses: cli/runNpmCommand -- name: install dependencies -- with: -- workingDirectory: api -- args: install --no-audit
Locate the two
file/createOrUpdateEnvironmentFile
tasks. One adds properties to the**./.localConfigsfile in the root of the project that’s used to inject environment variables in the front-end web process, while the other creates a similar file in the Azure Function project in the./api** folder:# Generate runtime environment variables for tab - uses: file/createOrUpdateEnvironmentFile with: target: ./.localConfigs envs: BROWSER: none HTTPS: true PORT: 53000 SSL_CRT_FILE: ${{SSL_CRT_FILE}} SSL_KEY_FILE: ${{SSL_KEY_FILE}} REACT_APP_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} REACT_APP_START_LOGIN_PAGE_URL: ${{TAB_ENDPOINT}}/auth-start.html -- REACT_APP_FUNC_NAME: ${{FUNC_NAME}} -- REACT_APP_FUNC_ENDPOINT: ${{FUNC_ENDPOINT}} -- # Generate runtime environment variables for backend -- - uses: file/createOrUpdateEnvironmentFile -- with: -- target: ./api/.localConfigs -- envs: -- M365_CLIENT_ID: ${{AAD_APP_CLIENT_ID}} -- M365_CLIENT_SECRET: ${{SECRET_AAD_APP_CLIENT_SECRET}} -- M365_TENANT_ID: ${{AAD_APP_TENANT_ID}} -- M365_AUTHORITY_HOST: ${{AAD_APP_OAUTH_AUTHORITY_HOST}} -- ALLOWED_APP_IDS: 1fec8e78-bce4-4aaf-ab1b-5451cc387264;5e3ce6c0-2b1f-4285-8d4b-75ee78787346;0ec893e0-5785-4de6-99da-4ed124e5296c;4345a7b9-9a63-4910-a426-35363201d503;4765445b-32c6-49b0-83e6-1d93765276ca;d3590ed6-52b3-4102-aeff-aad2292ab01c;00000002-0000-0ff1-ce00-000000000000;bc59ab01-8403-45c6-8796-ac3ef710b3e3;27922004-5251-4030-b22d-91ecd9a37ea4
Finally, remove the
FUNC_NAME
andFUNC_ENDPOINT
variables from the local environment file: ./env/.env.local.
At this point, we’ve removed all references that start up the Azure Function project within the TTK project template.
Remove the Azure Function project infrastructure deployment
Now that we’ve configured the project to not start a local Azure Function project, let’s remove the components used to deploy the Azure Function infrastructure to your Azure subscription.
Step 1: Update the Azure Bicep file
Thankfully, the TTK project templates include Bicep files for creating Azure resources. The default template creates an Azure Static Web App and an Azure Function App with its associated server farm.
Since the Azure Function components make up most of the Bicep file, it will be much slimmer when we’re done. Let’s get started.
First, remove all the input parameters and variables defined at the top of the Bicep file: ./infra/azure.bicep:
param resourceBaseName string
-- param functionAppSKU string
-- param aadAppClientId string
-- param aadAppTenantId string
-- param aadAppOauthAuthorityHost string
-- @secure()
-- param aadAppClientSecret string
-- param location string = resourceGroup().location
-- param serverfarmsName string = resourceBaseName
-- param functionAppName string = resourceBaseName
param staticWebAppName string = resourceBaseName
param staticWebAppSku string
-- var teamsMobileOrDesktopAppClientId = '1fec8e78-bce4-4aaf-ab1b-5451cc387264'
-- var teamsWebAppClientId = '5e3ce6c0-2b1f-4285-8d4b-75ee78787346'
-- var officeWebAppClientId1 = '4345a7b9-9a63-4910-a426-35363201d503'
-- var officeWebAppClientId2 = '4765445b-32c6-49b0-83e6-1d93765276ca'
-- var outlookDesktopAppClientId = 'd3590ed6-52b3-4102-aeff-aad2292ab01c'
-- var outlookWebAppClientId = '00000002-0000-0ff1-ce00-000000000000'
-- var officeUwpPwaClientId = '0ec893e0-5785-4de6-99da-4ed124e5296c'
-- var outlookOnlineAddInAppClientId = 'bc59ab01-8403-45c6-8796-ac3ef710b3e3'
-- var outlookMobileAppClientId = '27922004-5251-4030-b22d-91ecd9a37ea4'
-- var allowedClientApplications = '"${teamsMobileOrDesktopAppClientId}","${teamsWebAppClientId}","${officeWebAppClientId1}","${officeWebAppClientId2}","${outlookDesktopAppClientId}","${outlookWebAppClientId}","${officeUwpPwaClientId}","${outlookOnlineAddInAppClientId}","${outlookMobileAppClientId}"'
Next, jump down past where the static web app is created and remove two variables created using the details from the static web app as they are only used in the Azure Function:
var siteDomain = swa.properties.defaultHostname
var tabEndpoint = 'https://${siteDomain}'
-- var aadApplicationIdUri = 'api://${siteDomain}/${aadAppClientId}'
-- var oauthAuthority = uri(aadAppOauthAuthorityHost, aadAppTenantId)
Remove the server farm object created to host the Azure Function app, and the Azure Function app resource:
-- // Compute resources for Azure Functions
-- resource serverfarms 'Microsoft.Web/serverfarms@2021-02-01' = {
-- name: serverfarmsName
-- kind: 'functionapp'
-- location: location
-- sku: {
-- name: functionAppSKU // You can follow https://aka.ms/teamsfx-bicep-add-param-tutorial to add functionServerfarmsSku property to provisionParameters to override the default value "Y1".
-- }
-- properties: {}
-- }
-- // Azure Functions that hosts your function code
-- resource functionApp 'Microsoft.Web/sites@2021-02-01' = {
-- name: functionAppName
-- kind: 'functionapp'
-- location: location
-- properties: {
-- // omitted for brevity...
-- }
-- }
-- var apiEndpoint = 'https://${functionApp.properties.defaultHostName}'
-- resource authSettings 'Microsoft.Web/sites/config@2021-02-01' = {
-- parent: functionApp
-- name: 'authsettings'
-- properties: {
-- enabled: true
-- defaultProvider: 'AzureActiveDirectory'
-- clientId: aadAppClientId
-- issuer: '${oauthAuthority}/v2.0'
-- allowedAudiences: [
-- aadAppClientId
-- aadApplicationIdUri
-- ]
-- }
-- }
Finally, update the outputs by removing all the references to the Azure Function app. Notice I’ve made one fix to something we’re keeping: The TAB_ENDPOINT
can use the variable that the template already created. Seems like an oversight in the template, so we’ll fix it here:
output TAB_DOMAIN string = siteDomain
output TAB_HOSTNAME string = siteDomain
-- output TAB_ENDPOINT string = 'https://${siteDomain}'
++ output TAB_ENDPOINT string = tabEndpoint
-- output API_FUNCTION_ENDPOINT string = apiEndpoint
output AZURE_STATIC_WEB_APPS_RESOURCE_ID string = swa.id
-- output API_FUNCTION_RESOURCE_ID string = functionApp.id
Now, when you deploy your project, it won’t create unused Azure resources in your subscription.
The last step is to remove all the unnecessary parameters in the ./infra/azure.parameters.json file that we removed from the inputs in the Bicep file:
{
"$schema": "https://schema.management.azure.com/schemas/2015-01-01/deploymentParameters.json#",
"contentVersion": "1.0.0.0",
"parameters": {
"resourceBaseName": {
"value": "helloworld${{RESOURCE_SUFFIX}}"
},
-- "aadAppClientId": {
-- "value": "${{AAD_APP_CLIENT_ID}}"
-- },
-- "aadAppClientSecret": {
-- "value": "${{SECRET_AAD_APP_CLIENT_SECRET}}"
-- },
-- "aadAppTenantId": {
-- "value": "${{AAD_APP_TENANT_ID}}"
-- },
-- "aadAppOauthAuthorityHost": {
-- "value": "${{AAD_APP_OAUTH_AUTHORITY_HOST}}"
-- },
-- "functionAppSKU": {
-- "value": "B1"
-- },
"staticWebAppSku": {
"value": "Free"
}
}
}
Delete the Azure Function project
The last step is to delete the Azure Function project. This one is easy—simply delete the ./api folder in the project!
Conclusion
That’s it! You’ve now completely removed all instances of the Azure Function from your TTK tab project that uses the React with Fluent UI template!
It would be more helpful if the TTK provided the option to add an Azure Function to our project when needed, rather than including one by default.
While Azure Functions are commonly used, as mentioned in the introduction, their usage may decrease for Microsoft Graph-focused apps once Nested App Authentication reaches general availability.
Perhaps Microsoft will add this project template option to TTK when that time comes. Until then, you have this guide as a reference!