If a solution cuts costs, streamlines business operations, and helps you manage websites and apps easily, then you would love to take it, wouldn’t you?

Such a solution is hosting your back-end and front-end on the same server. Today, I will share about hosting the .NET API backend and React Next.js frontend on the same Microsoft Azure App, which Computan developers recently did.

Before going to the How-to, here’s the why-to for hosting front-end and back-end on the same app.

Cost Efficiency

When you host your front-end and back-end on the same app, you pay only for one hosting plan. This would cut the server hosting cost in half.

Both the front end and the back end can share the same CPU and memory, reducing the cost of resource allocation.

Simplified Deployment and Management

You can deploy updates to both frontend and backend within the same environment.

Managing configurations, environment variables, and scaling options from a single App Service becomes easier as it simplifies application management.

You can create and use a single CI/CD pipeline to deploy the API and the front-end. This also reduces the complexity of managing multiple pipelines.

Performance Optimization

The communication between the front-end and back-end is faster since both are hosted on the same platform, which reduces latency.

You can implement in-memory caching effectively, as both apps are on the same server.

Simplified Networking and Security

You can apply consistent security settings across both the frontend and backend, such as SSL/TLS certificates and authentication.

The API can be directly accessed from the front-end without cross-origin resource sharing (CORS) issues, as they are in the same domain.

Easier Local Development and Testing

Developing and testing locally in a similar environment ensures consistency and reduces the likelihood of environment-specific issues.

When both components are hosted, you can run integration tests more efficiently, ensuring that changes to one part of the system do not break the other.

Scalability

You can scale back-end and front-end together based on demand, simplifying the scaling process.

In this particular case, Microsoft Azure App Service supports automatic load balancing, which can help distribute the load more effectively when both applications are hosted together.

Convenience for Small to Medium-Sized Applications

For smaller projects or MVPs, it is recommended that both the back-end and front-end be on the same app service to avoid the complexity of managing multiple services.

Setting up the infrastructure is quicker, making it easier for teams to get their applications up and running with minimal overhead.

How to host a .NET API backend and a React Next.js frontend on the same Microsoft Azure app service

Prerequisites

  1. Azure Subscription: Ensure you have an active Azure subscription.
  2. .NET Application
  3. Next JS application

This process assumes you already have your .NET application and Next.Js application running locally or on another server type, and both projects are sitting in the same repo.

Sample repo structure

/MyApp

├── MyApi.NET

└── MyFrontend.NEXTJS

Step 1: Set Up your frontend for azure app service

1: Serve the Next.js app using a custom server. Create a server.js file at the root of your Next.js

project: 

const { createServer } = require('http');

const next = require('next');

const dev = process.env.NODE_ENV !== 'production';

const app = next({ dev });

const handle = app.getRequestHandler();

app.prepare().then(() => {

createServer((req, res) => {

handle(req, res);

}).listen(3000, (err) => {

if (err) throw err;

console.log('> Ready on http://localhost:3000');

});

});

2: Modify your package.json to use the custom server:

"scripts": {

"dev": "node server.js",

"build": "next build",

"start": "node server.js"

}

Step 2: Set Up Your Build pipeline to compile both frontend and backend application

  1. Create a New Pipeline: Go to Azure DevOps, navigate to your project, and create a new pipeline.
  2. Select Your Repository: Choose your combined project's repository (MyApp).
  3. Define Your Pipeline: Here is a sample configuration for your azure-pipelines.yml file:

trigger: - main

pool:

vmImage: 'ubuntu-latest'

stages: - stage: Build

jobs:

- job: Build

   steps:

   - task: UseDotNet@2

     inputs:

       packageType: 'sdk'

       version: '5.x'

       installationPath: $(Agent.ToolsDirectory)/dotnet

 

   - task: NodeTool@0

     inputs:

       versionSpec: '14.x'

     displayName: 'Install Node.js'

 

   - script: |

       cd MyFrontend.NEXTJS

       npm install

       npm run build

     displayName: 'Build Next.js app'

   - task: Npm@1

     inputs:

       command: 'custom'

       workingDir: ' MyFrontend.NEXTJS'

       customCommand: 'run build'

   - task: ArchiveFiles@2

     inputs:

       rootFolderOrFile: 'MyFrontend.NEXTJS'

       includeRootFolder: true

       archiveType: 'zip'

       archiveFile: '$(Build.ArtifactStagingDirectory)/$(Build.Build

Id).zip'

     replaceExistingArchive: true

 

   - script: |

       cd MyApi.NET

       dotnet build --configuration Release

     displayName: 'Build .NET API'

 

   - script: |

       cd MyApi

dotnet publish --configuration Release --output

$(Build.ArtifactStagingDirectory)/publish

displayName: 'Publish .NET API' - task: PublishBuildArtifacts@1

inputs:

pathToPublish: '$(Build.ArtifactStagingDirectory)/publish'

artifactName: 'drop'

This pipeline creates your frontend and your backend packages separately

Step 3: Configure Azure DevOps Release Pipeline for your backend

Create a New Release Pipeline: Go to Azure DevOps, navigate to Releases, and create a new release pipeline.

Add an Artifact: Select the build pipeline you created as the source.

Add a Stage: Add a stage to deploy to Azure App Service.

Select Azure App Service Deployment: Choose the appropriate deployment task.

Configure Deployment: Configure the deployment to your Azure App Service. Here is an

example configuration: -

task: AzureRmWebAppDeployment@4

inputs:

azureSubscription: 'YOUR_AZURE_SUBSCRIPTION'

appType: 'webApp'

WebAppName: 'YOUR_APP_SERVICE_NAME'

packageForLinux:

'$(System.DefaultWorkingDirectory)/_YOUR_BUILD_PIPELINE/drop'

RuntimeStack: 'DOTNETCORE|5.0'

  1. Configure Release Pipeline: Configure triggers to automate the release process after a successful build.

Step 4: Configure Azure DevOps Release Pipeline for your Frontend

It is the same with step 4 but with the frontend artifacts. Sample - task:

AzureRmWebAppDeployment@4

inputs:

azureSubscription: 'YOUR_AZURE_SUBSCRIPTION'

appType: 'webApp'

WebAppName: 'YOUR_APP_SERVICE_NAME'

packageForLinux:

'$(System.DefaultWorkingDirectory)/_YOUR_BUILD_PIPELINE/)/$(Bui

ld.BuildId).zip '

RuntimeStack: 'NODE'

Step 5: Update your web.config to serve your frontend application with the following configurations.

You may need to customize this based on your backend application requirements.

<?xml version="1.0" encoding="utf-8"?>

<!--

This configuration file is required if iisnode is used to run node processes behind IIS or IIS Express. For more information, visit: GitHub

<configuration>

<system.webServer>

<!-- Visit

http://blogs.msdn.com/b/windowsazure/archive/2013/11/14/introduction-to-websockets-on-windows-azure-web-sites.aspx for more information on

WebSocket support -->

<webSocket enabled="false" />

<handlers>

<!-- Indicates that the server.js file is a node.js site to be

handled by the iisnode module -->

<add name="iisnode" path="server.js" verb="*" modules="iisnode"/>

</handlers>

<rewrite>

<rules>

<!-- Do not interfere with requests for node-inspector debugging->

<rule name="NodeInspector" patternSyntax="ECMAScript"

stopProcessing="true">

<match url="^server.js\/debug[\/]?" />

</rule>

<!-- First we consider whether the incoming URL matches a physical

file in the /public folder -->

<rule name="StaticContent">

<action type="Rewrite" url="public{REQUEST_URI}"/>

</rule>

<!-- All other URLs are mapped to the node.js site entry point -->

<rule name="DynamicContent">

<conditions>

<add input="{REQUEST_FILENAME}" matchType="IsFile"

negate="True"/>

</conditions>

<action type="Rewrite" url="server.js"/>

</rule>

</rules>

</rewrite>

<!-- 'bin' directory has no special meaning in node.js and apps can be placed in it -->

<security>

<requestFiltering>

<hiddenSegments>

<remove segment="bin"/>

</hiddenSegments>

</requestFiltering>

</security>

<!-- Make sure error responses are left untouched -->

<httpErrors existingResponse="PassThrough" />

<!--You can control how Node is hosted within IIS using the following options:

* watchedFiles: semi-colon separated list of files that will be watched for changes to restart the server

* node_env: will be propagated to node as NODE_ENV environment variable

* debuggingEnabled - controls whether the built-in debugger is enabled

See https://github.com/tjanczuk/iisnode/blob/master/src/samples/configuration/web.config for a full list of options -->

<!--<iisnode watchedFiles="web.config;*.js"/>-->

</system.webServer>

</configuration>

Code courtesy: Kayode Adegbuyi