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
- Azure Subscription: Ensure you have an active Azure subscription.
- .NET Application
- 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
- Create a New Pipeline: Go to Azure DevOps, navigate to your project, and create a new pipeline.
- Select Your Repository: Choose your combined project's repository (MyApp).
- 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'
- 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