React/Redux application with Azure DevOps: Part 8 Integrate backend/frontend and CI/CD
Tags: azuredevops, react, redux, unittest
Published: 2020-03-11 06:54:02

---
title: React/Redux application with Azure DevOps: Part 8 Integrate backend/frontend and CI/CD
published: true
description: React/Redux application with Azure DevOps
tags: AzureDevOps,React,Redux,UnitTest
---

In the [previous post](https://dev.to/kenakamu/react-redux-application-with-azure-devops-part-7-use-backend-from-react-frontend-2ef), 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.

```shell
npm run build
```

2\. Once the process completed, I can see build folder and items are added.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/aakapipiz5ltdf1rruyy.jpg)

3\. Update Server.ts in react-backend/src folder to serve the build folder as static folder. Use it as root, too.

```typescript
/// 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.

```shell
npm run start:dev
```
5\. Open browser and access to localhost:3001. I can see the application is up and running.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1pvayqs8fcri2bbhy6nj.jpg)

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.

```json
"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.

```typescript
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. 

```javascript
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.

```shell
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.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/q5u5fcvcgvsr4qgbryag.jpg)

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.

```yaml
# 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.

```shell
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. 
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/sczzwxukcmk68vf4w1ky.jpg)

3\. I can confirm the test results for both frontend and backend are published.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/0hsgn0spbr8k09tn3wq1.jpg)

4\. Application runs as expected on both environment.
![Alt Text](https://dev-to-uploads.s3.amazonaws.com/i/1va3zqiyo5vo96o90lx0.jpg)

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.

[Go to next article](https://dev.to/kenakamu/react-redux-application-with-azure-devops-part-9-integration-test-and-end-to-end-test-3k0d)