Add a proxy to a backend service in Angular

Problem definition

Browsers restrict cross-origin HTTP requests performed from Angular code due to security reasons. That means a web application served from for example https://mydomain.com cannot fetch data from the backend service served from https://api.mydomain.com since the origin is not the same. Also, Browsers do not trust servers with self signed certificates and they restrict the requests to such servers.

To tackle such a problem, a proxy can be used so that the requests are sent to a specific route to be forwarded to the backend service. This way the requests are on the same origin and in the background all those requests are sent to the backend service by the proxy and the problem with the CORS is resolved. Also, the web server can be configured to trust the self signed certificates and since the browser is not involved in sending requests to the backend service anymore the certificate trust issue is solved.

Create a new Angular application

Install Angular cli.

sudo npm install -g @angular/cli

Generate a new application.

ng new angular-proxy
? Would you like to add Angular routing? Yes
? Which stylesheet format would you like to use? SCSS

Create a mock backend server

npm install -D json-server

Create a db.json file that contains some posts.

code db.json

content:

{
  "posts": [
    {
      "id": 1,
      "title": "Grass Is Greener, The",
      "body": "Duis bibendum. Morbi non quam nec dui luctus rutrum. Nulla tellus.\n\nIn sagittis dui vel nisl. Duis ac nibh. Fusce lacus purus, aliquet at, feugiat non, pretium quis, lectus."
    },
    {
      "id": 2,
      "title": "Polly of the Circus",
      "body": "Nullam porttitor lacus at turpis. Donec posuere metus vitae ipsum. Aliquam non mauris.\n\nMorbi non lectus. Aliquam sit amet diam in magna bibendum imperdiet. Nullam orci pede, venenatis non, sodales sed, tincidunt eu, felis.\n\nFusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem."
    },
    {
      "id": 3,
      "title": "Young Again",
      "body": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui."
    },
    {
      "id": 4,
      "title": "Siffleurs, Les (Viheltäjät)",
      "body": "Etiam vel augue. Vestibulum rutrum rutrum neque. Aenean auctor gravida sem.\n\nPraesent id massa id nisl venenatis lacinia. Aenean sit amet justo. Morbi ut odio.\n\nCras mi pede, malesuada in, imperdiet et, commodo vulputate, justo. In blandit ultrices enim. Lorem ipsum dolor sit amet, consectetuer adipiscing elit."
    },
    {
      "id": 5,
      "title": "When in Rome",
      "body": "Maecenas leo odio, condimentum id, luctus nec, molestie sed, justo. Pellentesque viverra pede ac diam. Cras pellentesque volutpat dui.\n\nMaecenas tristique, est et tempus semper, est quam pharetra magna, ac consequat metus sapien ut nunc. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Mauris viverra diam vitae quam. Suspendisse potenti."
    },
    {
      "id": 6,
      "title": "Day Lincoln Was Shot, The",
      "body": "Cras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque.\n\nQuisque porta volutpat erat. Quisque erat eros, viverra eget, congue eget, semper rutrum, nulla. Nunc purus."
    },
    {
      "id": 7,
      "title": "Tartuffe (Herr Tartüff)",
      "body": "In hac habitasse platea dictumst. Etiam faucibus cursus urna. Ut tellus.\n\nNulla ut erat id mauris vulputate elementum. Nullam varius. Nulla facilisi.\n\nCras non velit nec nisi vulputate nonummy. Maecenas tincidunt lacus at velit. Vivamus vel nulla eget eros elementum pellentesque."
    },
    {
      "id": 8,
      "title": "The Magic Crystal",
      "body": "In hac habitasse platea dictumst. Morbi vestibulum, velit id pretium iaculis, diam erat fermentum justo, nec condimentum neque sapien placerat ante. Nulla justo."
    },
    {
      "id": 9,
      "title": "Full Metal Jacket",
      "body": "Fusce posuere felis sed lacus. Morbi sem mauris, laoreet ut, rhoncus aliquet, pulvinar sed, nisl. Nunc rhoncus dui vel sem."
    },
    {
      "id": 10,
      "title": "20,000 Leagues Under the Sea",
      "body": "Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Proin risus. Praesent lectus.\n\nVestibulum quam sapien, varius ut, blandit non, interdum in, ante. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Duis faucibus accumsan odio. Curabitur convallis.\n\nDuis consequat dui nec nisi volutpat eleifend. Donec ut dolor. Morbi vel lectus in quam fringilla rhoncus."
    }
  ]
}

Now start the json-server using the following command.

npx json-server --watch db.json --host 0.0.0.0

Create a proxy file for development server

code src/proxy.conf.json
{
  "/api/posts": {
    "target": "http://localhost:3000",
    "secure": false,
    "logLevel": "debug",
    "changeOrigin": true,
    "pathRewrite": {
      "^/api": ""
    }
  }
}

Open angular.json to add the proxy configuration.

code angular.json

Add the "proxyConfig" configuration.

"architect": {
  "serve": {
    "builder": "@angular-devkit/build-angular:dev-server",
    "options": {
      "browserTarget": "angular-proxy:build",
      "proxyConfig": "src/proxy.conf.json"
    },
    "configurations": {
      "production": {
        "browserTarget": "angular-proxy:build:production"
      },
      "development": {
        "browserTarget": "angular-proxy:build:development"
      }
    },
    "defaultConfiguration": "development"
  },
…

See more details in Angular documentation

Write a simple page to fetch some data from backend service

Import HttpClientModule into the app module.

code src/app/app.module.ts

And the content:

import { NgModule } from '@angular/core'
import { BrowserModule } from '@angular/platform-browser'

import { HttpClientModule } from '@angular/common/http'
import { AppRoutingModule } from './app-routing.module'
import { AppComponent } from './app.component'

@NgModule({
  declarations: [AppComponent],
  imports: [BrowserModule, AppRoutingModule, HttpClientModule],
  providers: [],
  bootstrap: [AppComponent],
})
export class AppModule {}

Create a service to read the blog posts.

ng g s services/post
code src/app/services/post.service.ts
import { HttpClient } from '@angular/common/http'
import { Injectable } from '@angular/core'

@Injectable({
  providedIn: 'root',
})
export class PostService {
  private url = '/api/posts'

  constructor(private httpClient: HttpClient) {}

  getPosts() {
    return this.httpClient.get(this.url)
  }
}

Update the app component to use the service to get the blog post and save them on posts variable.

code src/app/app.component.ts

The content:

import { Component } from '@angular/core'
import { PostService } from './services/post.service'

@Component({
  selector: 'app-root',
  templateUrl: './app.component.html',
  styleUrls: ['./app.component.scss'],
})
export class AppComponent {
  title = 'angular-proxy'
  posts: any

  constructor(private service: PostService) {}

  ngOnInit() {
    this.service.getPosts().subscribe((response) => {
      this.posts = response
    })
  }
}

Loop over the posts and show the title for each post.

code src/app/app.component.html

The content:

<ul>
  <li *ngFor="let post of posts">
    <h1>{{ post.title }}</h1>
    <p>{{ post.body }}</p>
  </li>
</ul>

Configure nginx proxy while using build packs

Add nginx.conf to configure the proxy:

code nginx.conf

Content as blow:

worker_processes 1;
daemon off;

error_log stderr;
events { worker_connections 1024; }

http {
    charset utf-8;
    log_format cnb 'NginxLog "$request" $status $body_bytes_sent';
    access_log /dev/stdout cnb;
    default_type application/octet-stream;
    include mime.types;
    sendfile on;

    tcp_nopush on;
    keepalive_timeout 30;
    port_in_redirect off;

    server {
        listen {{port}};
        server_name _;

        # Directory where static files are located
        root /workspace/dist/angular-proxy;

        location / {
            try_files $uri $uri/ index.html;
        }

        location /api/posts {
            proxy_pass http://{{env "BACKEND_HOST"}}:{{env "BACKEND_PORT"}};
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            rewrite /api/(.*) /$1  break;
            proxy_ssl_verify off;
        }
    }
}

Add mime.types as it is needed by nginx.

code mime.types

The content as below.

types {
  text/html html htm shtml;
  text/css css;
  text/xml xml;
  image/gif gif;
  image/jpeg jpeg jpg;
  application/x-javascript js;
  application/atom+xml atom;
  application/rss+xml rss;
  font/ttf ttf;
  font/woff woff;
  font/woff2 woff2;
  text/mathml mml;
  text/plain txt;
  text/vnd.sun.j2me.app-descriptor jad;
  text/vnd.wap.wml wml;
  text/x-component htc;
  text/cache-manifest manifest;
  image/png png;
  image/tiff tif tiff;
  image/vnd.wap.wbmp wbmp;
  image/x-icon ico;
  image/x-jng jng;
  image/x-ms-bmp bmp;
  image/svg+xml svg svgz;
  image/webp webp;
  application/java-archive jar war ear;
  application/mac-binhex40 hqx;
  application/msword doc;
  application/pdf pdf;
  application/postscript ps eps ai;
  application/rtf rtf;
  application/vnd.ms-excel xls;
  application/vnd.ms-powerpoint ppt;
  application/vnd.wap.wmlc wmlc;
  application/vnd.google-earth.kml+xml  kml;
  application/vnd.google-earth.kmz kmz;
  application/x-7z-compressed 7z;
  application/x-cocoa cco;
  application/x-java-archive-diff jardiff;
  application/x-java-jnlp-file jnlp;
  application/x-makeself run;
  application/x-perl pl pm;
  application/x-pilot prc pdb;
  application/x-rar-compressed rar;
  application/x-redhat-package-manager  rpm;
  application/x-sea sea;
  application/x-shockwave-flash swf;
  application/x-stuffit sit;
  application/x-tcl tcl tk;
  application/x-x509-ca-cert der pem crt;
  application/x-xpinstall xpi;
  application/xhtml+xml xhtml;
  application/zip zip;
  application/octet-stream bin exe dll;
  application/octet-stream deb;
  application/octet-stream dmg;
  application/octet-stream eot;
  application/octet-stream iso img;
  application/octet-stream msi msp msm;
  application/json json;
  audio/midi mid midi kar;
  audio/mpeg mp3;
  audio/ogg ogg;
  audio/x-m4a m4a;
  audio/x-realaudio ra;
  video/3gpp 3gpp 3gp;
  video/mp4 mp4;
  video/mpeg mpeg mpg;
  video/quicktime mov;
  video/webm webm;
  video/x-flv flv;
  video/x-m4v m4v;
  video/x-mng mng;
  video/x-ms-asf asx asf;
  video/x-ms-wmv wmv;
  video/x-msvideo avi;
}

Install pack

Follow pack installation guide to install pack.

(curl -sSL "https://github.com/buildpacks/pack/releases/download/v0.27.0/pack-v0.27.0-linux.tgz" | sudo tar -C /usr/local/bin/ --no-same-owner -xzv pack)

Build a container image

pack build angular-proxy \
--buildpack paketo-buildpacks/web-servers \
--env BP_NODE_RUN_SCRIPTS=build \
--env BP_WEB_SERVER_ROOT=dist/angular-proxy \
--builder paketobuildpacks/builder:base

Run the built container image

Find the IP address on the host.

ip a

Then run the container image.

docker run --rm --name angular-proxy \
--env PORT=8080 \
--env BACKEND_HOST=<HOST IP ADDRESS> \
--env BACKEND_PORT=3000 \
--publish 8080:8080 angular-proxy

Git repository

The code can be found here.