loading...

Backend URL ENV Variable on Angular using Heroku Config Vars

ezequielfalcon profile image Ezequiel Falcón ・3 min read

Intro

When I first started with Angular (frontend) and Node (backend) for my new app, I was amazed by how a web framework works. It was my first experience with Javascript and Typescript and I didn't like MVC frameworks (strange ha!) so I was avoiding Ruby on Rails and ASP .NET.

As I started testing my first versions, all went just fine.. then I found Heroku. It was marvelous.. It solved mostly all my problems regarding hosting and staging/production scenarios.. but it did not solve one thing (at first): My backend URL.

The app needs the URL where it can make the HTTP requests! How could I set up this variable? ENV Variables!

ENV Variables.. what about them?

Yeah, that MUST NOT DO thing of storing a config value on your code..

When you use Node, and everything runs server-side, it´s really easy to access ENV variables: process.env.
But when you're on client-side (like Angular) that becomes more difficult. All my 'configs' and 'parameters' where stored on the backend, so it was all through the API... but it was only 1 thing I needed to be able to reach that API... the URL!

Server-side / Client-side

At first, I didn't get that "server-side" and "client-side" really well. I spent hours searching for a way to access ENV variables from within Angular's typescript... and of course, you just can't, you're running on a browser, not on a server... the server only provides for the app, the browser does the "thing".

Angular app on Heroku

As you may know, if you want to host an Angular app on heroku, you have to set up an http server to host it there. The common way to go is to use a little express app that hosts the Angular app. Here is the code:

// Express app
const express = require('express');
const app = express();
// HTTPS only middleware
const forceSSL = function() { 
    return function(req, res, next) {
        if (req.headers['x-forwarded-proto'] !== 'https') {
            return res.redirect(
                ['https://', req.get('Host'), req.url].join('')
            );
        }
        next();
    }
};
app.use(forceSSL());
// Actual host of the static Angular content
app.use(express.static(__dirname + '/dist'));
app.listen(process.env.PORT || 5000, function() {
    console.log("Angular app running!");
});

As you can see, this is pretty straightforward: A tiny angular app that serves the Angular app content (the output of the ng build: dist folder).

But now, I can take advantage of this little app!! Use it as an API for my own frontend!

Solution!

Now, I've set up a Config var in Heroku named BACKEND_URL with the actual URL of the backend host. Let's add the ENV var to this express app and serve it through HTTP:

// Express app
const express = require('express');
const app = express();
// HTTPS only middleware
const forceSSL = function() { 
    return function(req, res, next) {
        if (req.headers['x-forwarded-proto'] !== 'https') {
            return res.redirect(
                ['https://', req.get('Host'), req.url].join('')
            );
        }
        next();
    }
};
app.use(forceSSL());
// Actual host of the static Angular content
app.use(express.static(__dirname + '/dist'));

// Nice and done!
app.get('/backend', (req, res) => {
  res.json({url: process.env.BACKEND_URL})
});

app.listen(process.env.PORT || 5000, function() {
    console.log("Angular app running!");
});

That's it... more or less...

Now, the only thing I have to do is to have a early Angular service to dig for this variable and save it in my sessionStorage!
For example, the LoginService:

import { Injectable } from '@angular/core';
import {Http, Headers, Response, URLSearchParams} from '@angular/http';
import 'rxjs/add/operator/map';

@Injectable()
export class LoginService {

  private urlPrefix: string;

  constructor(
    private http: Http
  ) {
    this.http.get(window.location.origin + '/backend').map((response: Response) => response.json()).subscribe(urlBackend => {
      sessionStorage.setItem('url_backend', urlBackend.url);
    }, () => {
      console.log('Can´t find the backend URL, using a failover value');
      sessionStorage.setItem('url_backend', 'https://failover-url.com');
    });
  }

  someLoginMethod() {
  //...
  }
}

Now I have my backend URL on sessionStorage. I can continue to the Login process and all my data services!

It's not the most elegant solution, but it works for me.

Note on security: As you can see, there's no authentication for accessing the /backend path of the frontend API. There's no need in this case because the backend URL needs to be public (It's the actual app API!). You should never use this method to share secrets!

Thanks for reading!

Note on me: This is a fairly simple problem that most of you surely have already solved, but for me it was a real issue of which I didn't found much about.
And of course, I'm open to any kind of suggestion or improvement. This is my first article ever!

Discussion

pic
Editor guide
Collapse
nicolasomar profile image
Nicolás Omar González Passerino

Ezequiel, i am searching a way to overwrite the environment.ts variables using the ones localted on heroku side, but so far i couldn´t reach a solution.
Do you know a possible way to make it work?

Collapse
pawelpanek81 profile image
pawelpanek81

Thanks!! I love you!

Collapse
vicent90 profile image
vicent90

Excellent post, really usefull!!