Azure Pipelines: How to build and test an Angular and Node.js webapp

Azure Pipeline: Build Angular and Node.js webapp (part 2)

In the previous article, we discussed how we can set up a pipeline with multiple stages in Azure Pipelines. In this article we will discuss how we can build the Angular and Node.js apps in the pipeline itself.
I usually put all the build processes in one stage. In that stage I have one job for the backend and one job for the frontend. Check out the Azure documentation on basics about the azure pipelines.

My folder structure

My project has a backend and frontend folder in the same repository. So I am using one pipeline. Of course you can use multiple pipelines if you have two separate repositories this (that is usually done), but you can also put it in one pipeline.
My project looks like this:
├──backend
│  ├──node_modules
│  ├──src
│  └──test
└──frontend
   ├──dist
   ├──node_modules
   └──src

Angular – Building the frontend

First, we’re going to build the backend in the pipeline. It’s not much different than doing it from localhost.
At first, we will install the dependencies, then build the angular app and at last publish the build files to the pipeline.
You can choose to use yarn or npm for this project, but npm is supported by default and yarn is not. I will use npm tasks, but I will show you how to use yarn tasks too.

We install the dependencies with npm. We can use an azure pipeline tasks for this. Not all tasks will work for YAML pipelines, but you can use this handy toolbar that shows you which tasks are supported.

Shows a toolbar on the right that displays all the tasks availabe

I will use this npm task. Instead of a npm install, I’m using npm ci. Read some more about npm ci here. You will want to use npm ci in CI/CD pipelines, so your package management will be more consistent. You can add the task to a job by using the steps keyword. See a code example below.

After pulling the dependencies we will want to build the app. You can do this by using the build command from Angular.
We can run the ng build command with a bash task. We use ‘npm run ng build’ instead of ‘ng build’, because it will error on ‘ng is not installed’ if you use the ng command directly.

At last we will want to publish the files to the pipeline. You can do this by publishing the frontend folder to the pipeline. There’s also a task for that.
In the end your yaml looks like this:

trigger:
- master

pool:
  vmImage: 'ubuntu-latest'

stages:
  - stage: Build
    jobs:
      - job: Frontend
        steps:
        - task: Npm@1
          inputs:
            command: 'ci'
            workingDir: '$(System.DefaultWorkingDirectory)/frontend'
        - task: Bash@3
          inputs:
            targetType: 'inline'
            script: 'npm run ng build'
            workingDirectory: '$(System.DefaultWorkingDirectory)/frontend'
        - task: PublishPipelineArtifact@1
          inputs:
            targetPath: '$(Pipeline.Workspace)/frontend/dist'
            artifact: 'frontend'
            publishLocation: 'pipeline'

The workingDir is the directory where the command executes. The $(System.DefaultWorkingDirectory) variable is a predefined variable by Azure. Read more about the predefined variables here. They’re really handy!

Yarn instead of npm

If you want to use yarn instead of npm, you can use the same bash command. You can do an yarn –frozen-lockfile as follows

- task: Bash@3
  inputs:
    targetType: 'inline'
    script: 'yarn install --frozen-lockfile'

Node.js – Build the backend

Now that we’ve build and published the frontend, we can build the backend. Since it uses node, it’s very similar. You can also, of course, use different backend languages like Java or C#. You can find the tasks for that in the task index I referenced earlier.

We will do the same as in the frontend, install dependencies, build project and publish it to the pipeline. Your yaml will look like this

trigger:
  - master
  
pool:
  vmImage: 'ubuntu-latest'
  
stages:
  - stage: Build
    jobs:
      - job: Frontend
        steps:
        - task: Npm@1
          inputs:
            command: 'ci'
            workingDir: '$(System.DefaultWorkingDirectory)/frontend'
        - task: Bash@3
          inputs:
            targetType: 'inline'
            script: 'npm run ng build'
            workingDirectory: '$(System.DefaultWorkingDirectory)/frontend'
        - task: PublishPipelineArtifact@1
          inputs:
            targetPath: '$(Pipeline.Workspace)/frontend'
            artifact: 'frontend'
            publishLocation: 'pipeline'
      - job: Backend
        steps:
        - task: Npm@1
          inputs:
            command: 'ci'
            workingDir: '$(System.DefaultWorkingDirectory)/backend'
        - task: Bash@3
          inputs:
            targetType: 'inline'
            script: 'npm run build'
            workingDirectory: '$(System.DefaultWorkingDirectory)/backend'
        - task: PublishPipelineArtifact@1
          inputs:
            targetPath: '$(Pipeline.Workspace)/backend'
            artifact: 'backend'
            publishLocation: 'pipeline'

In the next article we will discuss how we can integrate tests in the pipeline. I will show you how to run the tests, how to get the test coverage and how to publish a summary of the tests.