In a previous post I showed how build an Angular app and a Node api with Bazel. In this post I will take this a step further by throwing a Java Api into the mix.

My other post can be found here.

Cross Domain Requests

Cors

In the previous demo I relied on CORS to allow ajax request from the Angular application to a Node api. This is necessary since the api and the Angular application are hosted on different domains.

For security reasons, Ajax requests between different domains are by default blocked by browsers. Even just a difference in port counts as a different domain as far as this rule is concerned.

Reverse Proxy

In this post I will show a different approach to cross domain communication.

Instead of CORS I will stand up a reverse proxy in Node and proxy through to the Java api.

The idea is that the Node application will serve the Angular application’s index.html page.

How can we access the Java api?

The Java api is on a different domain, so we can’t access it directly from the browser.

However, as soon as the Angular application is loaded it will be able to make Ajax request to the Node application that served index.html. These requests are allowed since the domain is the same.

The requests to the Node app will then be proxied through to the Java api.

I have included the Node api below:

const express = require("express"); const mustache = require("mustache-express"); const request = require('request'); const bodyParser = require('body-parser'); const app = express(); app.use(bodyParser.urlencoded({extended: false})); app.use(bodyParser.json()); app.engine("html", mustache()); app.set("view engine", "html"); app.get("/cars", (req, res) => { const url = `http://localhost:8080/cars`; request.get({url, json: true}, (err, response, body) => { if (err) { res.json('There was an error fetching cars'); } else { res.json(body); } }); }); app.get("/", (req, res) => { res.render("index.html", {cdn:'http://localhost:5432'}); }); app.listen(7777, () => console.log("Example app started"));

As you can see, there are two routes in the api.

  1. The initial / route for serving index html
  2. The cars endpoint that gets proxied through to the Java backend.

To run the Node api in Bazel I have added a nodejs_binary Bazel rule:

load("@build_bazel_rules_nodejs//:defs.bzl", "nodejs_binary") nodejs_binary( name = "web", data = ["//:views", ":server.js", "//src:devserver"], entry_point = "angular_bazel_example/src/web/server.js" )

Bazel Dev Server

The Bazel team has built a very nice dev server for bundling dev versions of Angular applications. This dev server works great, but there is no way to add proxying.

Still I would like to use it to build the dev bundle. No point in reinventing the wheel!

To incorporate the dev server in my Node app I have configured the dev server as a CDN that will serve the bundled Angular application.

This is why the index.html page served by the Node app has the following script tag:

<script src="{{cdn}}/bundle.min.js"></script>

This works since I am starting up the dev server side by side with the Node app.

Java Api

The Java api is super simple. All it does is return a list of cars.

The Bazel part is inspired by this Bazel repo.

Here is the servlet:

public class CarsServlet extends HttpServlet { @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String cars = "[\"BMW\", \"Mercedes-Benz\", \"Lamborghini\", \"Ferrari\", \"Maserati\"]"; response.setContentType("application/json"); response.getWriter().print(cars); } }

Here is the BUILD file:

// from: https://github.com/bazelbuild/examples/blob/master/tutorial/backend/BUILD load("@io_bazel_rules_appengine//appengine:appengine.bzl", "appengine_war") package(default_visibility = ["//visibility:public"]) appengine_war( name = "backend", data = [":webapp"], data_path = "webapp", jars = [":app_deploy.jar"], ) filegroup( name = "webapp", srcs = glob(["webapp/**/*"]), ) # We only need a java_library, but create a java_binary because we need a bundle # of all of the dependencies (a deploy jar) to pass to the appengine_war. So we # specify a non-existant class as the main_class (as the "binary" will never be # run directly, only used as a library). java_binary( name = "app", srcs = glob(["src/main/java/**/*.java"]), main_class = "does.not.exist", deps = [ "@io_bazel_rules_appengine//appengine:javax.servlet.api", ], )

Building the Application

We now have three parts to build and start: Node application, Java api and the Bazel dev server.

To start all there I use concurrently in an npm start task:

"web": "ibazel run //src/web:web", "cdn": "ibazel run src:devserver", "api": "ibazel run //src/backend", "start": "concurrently \"npm run web\" \"npm run api\" \"npm run cdn\""

Demo

You can find the code on Github if you are interested in trying it out.