In the previous post, I update frontend so that it can interact with backend. There are two applications at the moment, and I need to integrate them into one application.
React and express
React is just another frontend web application framework, thus once transpiled, produced results are static html/js/css files. In that case, express can serve it without any special configuration.
1. First thing first, transpile react into static files.
npm run build
2. Once the process completed, I can see build folder and items are added.
3. Update Server.ts in react-backend/src folder to serve the build folder as static folder. Use it as root, too.
/// Server.ts
import cookieParser from 'cookie-parser';
import express from 'express';
import { Request, Response } from 'express';
import logger from 'morgan';
import path from 'path';
import BaseRouter from './routes';
// Init express
const app = express();
// Add middleware/settings/routes to express.
app.use(logger('dev'));
app.use(express.json());
app.use(express.urlencoded({extended: true}));
app.use(cookieParser());
app.use('/api', BaseRouter);
/**
* Point express to the 'views' directory. If you're using a
* single-page-application framework like react or angular
* which has its own development server, you might want to
* configure this to only serve the index file while in
* production mode.
*/
const buildDir = path.join(__dirname, '../../build');
app.set('buildDir', buildDir);
const staticDir = path.join(__dirname, '../../build');
app.use(express.static(staticDir));
app.get('*', (req: Request, res: Response) => {
res.sendFile('index.html', {root: buildDir});
});
// Export express instance
export default app;
4. Run the backend server by start debugging or npm command in react-backend folder.
npm run start:dev
5. Open browser and access to localhost:3001. I can see the application is up and running.
6. Now I confirm how it work. Next I update the package.json to copy the build output into backend src folder so that I can package them together. The "postbuild" section runs after build script.
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"postbuild": "mv build ./react-backend/src",
"test": "react-scripts test",
"eject": "react-scripts eject"
},
7. Update Server.ts to point new build folder.
const buildDir = path.join(__dirname, './build');
8. Update util/build.js to copy the build folder into dist when transpile the backend. This is necessary as it clears the folder every time. I also comment out unused folder copy.
const fs = require('fs-extra');
const childProcess = require('child_process');
try {
// Remove current build
fs.removeSync('./dist/');
// Copy front-end files
fs.copySync('./src/build', './dist/build');
//fs.copySync('./src/public', './dist/public');
//fs.copySync('./src/views', './dist/views');
// Transpile the typescript files
childProcess.exec('tsc --build tsconfig.prod.json');
} catch (err) {
console.log(err);
}
9. I deleted all unused code from backend, such as MockDB, UserRoute, Views, etc so that I only have what I need.
Update .gitignore and commit
I didn't update .gitignore after added backend, which affect push to git server. I added following entries.
# backend
/react-backend/node_modules
/react-backend/env
/react-backend/logs
/react-backend/dist
/react-backend/src/build
/react-backend/spec/junitresults*.xml
Then commit the change, but not push yet.
git add .
git commit -m "backend integration"
Pipeline
Before push the changes to the repo, it's time to update build pipeline.
As I have done so many changes, I need to think about how to accommodate the change.
There are several things I need to think.
- Build order: At the moment, I need to build frontend first, then backend.
- Environment file: I cannot commit plan file with secure key, that's why I ignore env folder, but backend server needs it.
- Artifact: I don't need entire files but I just need backend project now.
1. First of all, upload env files to Azure DevOps Secure File where I can sacurly store the file. Go to Azure DevOps | Pipelines | Library | Secure files.
Upload production.env, development.env and test.env which contains environment information.
2. Edit current pipeline. I added/removed/modified several things to accommodate the changes.
- Download and copy environment files
- Update trigger so that it won't trigger by pipeline definition change
- Update build section to test and build backend/frontend
- Publish test results to cover both backend/frontend
- Create drop based on react-backend folder
- Update publish as it's express application rather than react and pass -- --env=development to control the environment settings.
# Node.js React Web App to Linux on Azure
# Build a Node.js React app and deploy it to Azure as a Linux web app.
# Add steps that analyze code, save build artifacts, deploy, and more:
# https://docs.microsoft.com/azure/devops/pipelines/languages/javascript
trigger:
branches:
include:
- master
paths:
exclude:
- azure-pipelines.yml
variables:
# Azure Resource Manager connection created during pipeline creation
azureSubscription: '2e4ad0a4-f9aa-4469-be0d-8c8f03f5eb85'
# Web app name
devWebAppName: 'mycatdogvoting-dev'
prodWebAppName: 'mycatdogvoting'
# Environment name
devEnvironmentName: 'Dev'
prodEnvironmentName: 'Prod'
# Agent VM image name
vmImageName: 'ubuntu-latest'
stages:
- stage: Build
displayName: Build stage
jobs:
- job: Build
displayName: Build
pool:
vmImage: $(vmImageName)
steps:
- task: DownloadSecureFile@1
name: productionEnv
inputs:
secureFile: 'production.env'
- task: DownloadSecureFile@1
name: developmentEnv
inputs:
secureFile: 'development.env'
- task: DownloadSecureFile@1
name: testEnv
inputs:
secureFile: 'test.env'
- script: |
mkdir $(System.DefaultWorkingDirectory)/react-backend/env
mv $(productionEnv.secureFilePath) $(System.DefaultWorkingDirectory)/react-backend/env
mv $(developmentEnv.secureFilePath) $(System.DefaultWorkingDirectory)/react-backend/env
mv $(testEnv.secureFilePath) $(System.DefaultWorkingDirectory)/react-backend/env
displayName: 'copy env file'
- task: NodeAndNpmTool@1
inputs:
versionSpec: '12.x'
- script: |
npm install
CI=true npm test -- --reporters=jest-junit --reporters=default
npm run build
displayName: 'test and build frontend'
- script: |
cd react-backend
npm install
npm run test
npm run build
displayName: 'test and build backend'
- task: PublishTestResults@2
inputs:
testResultsFormat: 'JUnit'
testResultsFiles: |
junit.xml
**/*junit*.xml
failTaskOnFailedTests: true
- task: ArchiveFiles@2
displayName: 'Archive files'
inputs:
rootFolderOrFile: '$(Build.SourcesDirectory)/react-backend'
includeRootFolder: false
archiveType: zip
archiveFile: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
replaceExistingArchive: true
- upload: $(Build.ArtifactStagingDirectory)/$(Build.BuildId).zip
artifact: drop
- stage: DeployToDev
displayName: Deploy to Dev stage
dependsOn: Build
condition: succeeded()
jobs:
- deployment: Deploy
displayName: Deploy to Dev
environment: $(devEnvironmentName)
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy: $(devWebAppName)'
inputs:
azureSubscription: $(azureSubscription)
appType: webAppLinux
WebAppName: $(devWebAppName)
packageForLinux: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
RuntimeStack: 'NODE|12-lts'
StartupCommand: 'npm run start -- --env=development'
- stage: DeployToProd
displayName: Deploy to Prod stage
dependsOn: DeployToDev
condition: succeeded()
jobs:
- deployment: Deploy
displayName: Deploy to Prod
environment: $(prodEnvironmentName)
pool:
vmImage: $(vmImageName)
strategy:
runOnce:
deploy:
steps:
- task: AzureRmWebAppDeployment@4
displayName: 'Azure App Service Deploy: $(prodWebAppName)'
inputs:
ConnectionType: 'AzureRM'
azureSubscription: '$(azureSubscription)'
appType: 'webAppLinux'
WebAppName: '$(prodWebAppName)'
packageForLinux: '$(Pipeline.Workspace)/drop/$(Build.BuildId).zip'
RuntimeStack: 'NODE|12-lts'
StartupCommand: 'npm run start'
3. Save the change and confirm it won't trigger the pipeline.
CI/CD
Now it's time to run the pipeline.
1. Make sure to commit all from local. I need to run git pull first to get latest yaml change from the repo.
git add .
git commit -m 'backend added'
git pull
git push
2. The pipeline is triggered. Once all the deployment completed, confirm everything worked as expected.
3. I can confirm the test results for both frontend and backend are published.
4. Application runs as expected on both environment.
I know that both environment points to same Redis Cache, but you got the idea how to use different configuration file, right?
Summary
I merged backend and frontend and run CI/CD successfully. I will take a look for integration in the next article.
Top comments (0)