DZone
Web Dev Zone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
  • Refcardz
  • Trend Reports
  • Webinars
  • Zones
  • |
    • Agile
    • AI
    • Big Data
    • Cloud
    • Database
    • DevOps
    • Integration
    • IoT
    • Java
    • Microservices
    • Open Source
    • Performance
    • Security
    • Web Dev
DZone > Web Dev Zone > Using Heroku for Static Web Content

Using Heroku for Static Web Content

John Vester user avatar by
John Vester
CORE ·
May. 07, 20 · Web Dev Zone · Tutorial
Like (2)
Save
Tweet
55.71K Views

Join the DZone community and get the full member experience.

Join For Free

In the "Moving Away From AWS and Onto Heroku" article, I provided an introduction of the application I wanted to migrate from Amazon's popular AWS solution to Heroku.  Subsequently, the "Destination Heroku" article illustrated the establishment of a new Heroku account and focused on introducing a Java API (written in Spring Boot) connecting to a ClearDB instance within this new platform-as-a-service (PaaS) ecosystem.  My primary goal is to find a solution that allows my limited time to be focused on providing business solutions instead of getting up to speed with DevOps processes.

Quick Recap

As a TL;DR (too long; didn't read) to the original article, I built an Angular client and a Java API for the small business owned by my mother-in-law.  After a year of running the application on Elastic Beanstalk and S3, I wanted to see if there was a better solution that would allow me to focus more on writing features and enhancements and not have to worry about learning, understanding, and executing DevOps-like aspects inherent within the AWS ecosystem.

Now with the Java API running in Heroku, it was time to focus on the client side of the application.

An AWS S3 Alternative

The Amazon AWS simple storage service (S3) is amazing.  Those interested in looking for an exciting case study, just look at services offered by Netflix or Airbnb to see how responsive and scalable the object service platform really is for high demanding applications.

While the AMHS application does not compare on any level to Netflix or Airbnb, I initially selected AWS S3 because it was the right place to locate the static files for the Angular application.  I wanted to keep the client code and the server code running on the same base service, which justified my decision.

When I started thinking about static content, I wasn't sure how things would work in the Heroku model.  In doing some quick research, it became apparent that I was not the only one with this use case.  In fact, all of the results led me to the same solution— simply utilize a Node.js Express server to host the static files for the client.  As a result, I would have an application running for the client in Heroku, just like I do the RESTful API.

Creating the amhs-angular Application

Following the same base steps in the "Destination Heroku" article, I created an application called amhs-angular to house the Angular client code.

Creating new app in Heroku

Since this will be a static web application only, there is no need to configure any additional add-ons for this service.  I could have performed the same process using the following command with the Heroku CLI:

heroku create amhs-angular

Next, I added the amhs-angular Heroku project as a remote in the git repository for the AMHS Angular client using the following command:

heroku git:remote -a amhs-angular

Which responded with the following output:

set git remote heroku to https://git.heroku.com/amhs-angular.git

The following git command validated the remote was set up properly:

$git remote -v

Update Angular to Run Inside Node Express

When using AWS S3 for the static client files, I followed the steps below in order to provide a publicly accessible version of the AMHS client:

  1. Build the distribution for Angular using the ng build --prod command
  2. Navigate to AWS | Storage | S3
  3. Single-click the AMHS bucket
  4. Drag all the files under the /dist folder into the main file screen in AWS
  5. Select all the files and grant the appropriate level of security

With Heroku, the plan is to use a Node.js Express server to host these files.  Within the terminal of the AMHS Angular client project, I executed the following command to include the express sever:

$ npm install express --save

Next, I needed to update the package.json to "start": "node server.js" and also included a "postinstall": "ng build --output-path dist" to perform the Angular build.  

Below, is a copy of the package.json after it was updated:

JSON
xxxxxxxxxx
1
 
1
"scripts": {
2
   "ng": "ng",
3
   "start": "node server.js",
4
   "build": "ng build --prod",
5
   "test": "ng test",
6
   "lint": "ng lint",
7
   "e2e": "ng e2e",
8
   "postinstall": "ng build --output-path dist"
9
},


I also needed to include an "engines" attribute, at the same level as "scripts" to the package.json:

JSON
xxxxxxxxxx
1
 
1
},
2
"engines": {
3
"node": "11.15.0",
4
"npm": "6.7.0"
5
}


Next, a generic server.js file (referenced above) needed to be created.  The contents are listed below:

JavaScript
xxxxxxxxxx
1
12
 
1
const express = require('express');
2
const path = require('path');
3
4
const app = express();
5
6
app.use(express.static('./dist'));
7
8
app.get('/*', function(req,res) {
9
res.sendFile(path.join(__dirname,'/dist/index.html'));
10
});
11
12
app.listen(process.env.PORT || 8080);


Finally, I needed to update the environment.prod.ts Angular file to reference the correct call-back URL and the API created in the "Destination Heroku" article:

JavaScript
 




xxxxxxxxxx
1


 
1
api: 'https://amhs.herokuapp.com',
2
redirectUrl: 'https://amhs-angular.herokuapp.com/implicit/callback'



At this point, when the client is deployed, it is ready to utilize a Node.js Express server to host the files in the /dist folder, calling the /dist/index.html file when the https://amhs-angular.herokuapp.com URL is called.

Application Enhancements

In addition to making the changes above, I wanted to introduce some new features to the AMHS application as well.  Two items that were on the backlog are as follows:

  • introduce ability to delete a property
  • allow Admin user to delete a staff member

With the necessary API changes already in place, I went ahead and updated the Angular client as well.  The Property list now contains a Delete button for each property:

Delete property functionality

Deleting a property is a simple operation and included a confirmation modal before performing the actual delete:

Delete property functionality

The staff view also includes a Delete button:

Delete staff functionality

With staff members, things were a bit more complicated.  The underlying business rules are noted below:

  1. A staff member should not be deleted if the staff member has sales tied to an existing property.
  2. A staff member should not be deleted if the staff member is a manager of any active staff.
  3. In the case where either condition is not allowed, the recommendation is to make the staff member inactive.

Using this logic, the following modal appears when condition #1 is not met:

Inactive staff functionality

Similarly, failure to meet condition #2 generates the modal displayed below:

Inactive staff functionality

When a staff member can be deleted, the following confirmation modal appears:

Delete staff functionality

After validating everything was working as expected, all of the changes above were checked into the master branch of the AMHS Angular client repository.

Pushing Changes to Heroku

With the code in the AMHS Angular client repository checked and the Heroku remote setup for the amhs-angular project, the code was deployed to Heroku using the following command:

git push heroku

The following command provided updates regarding the deployment:

Shell
xxxxxxxxxx
1
146
 
1
-----> Node.js app detected
2
3
-----> Creating runtime environment
4
5
       NPM_CONFIG_LOGLEVEL=error
6
       NODE_ENV=production
7
       NODE_MODULES_CACHE=true
8
       NODE_VERBOSE=false
9
10
-----> Installing binaries
11
       engines.node (package.json):  11.15.0
12
       engines.npm (package.json):   6.7.0
13
14
       Resolving node version 11.15.0...
15
       Downloading and installing node 11.15.0...
16
       npm 6.7.0 already installed with node
17
18
-----> Restoring cache
19
       - node_modules
20
21
-----> Installing dependencies
22
       Installing node modules
23
24
       > fsevents@1.2.12 install /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/fsevents
25
       > node-gyp rebuild
26
27
       gyp info it worked if it ends with ok
28
       gyp info using node-gyp@3.8.0
29
       gyp info using node@11.15.0 | linux | x64
30
       gyp http GET https://nodejs.org/download/release/v11.15.0/node-v11.15.0-headers.tar.gz
31
       gyp http 200 https://nodejs.org/download/release/v11.15.0/node-v11.15.0-headers.tar.gz
32
       gyp http GET https://nodejs.org/download/release/v11.15.0/SHASUMS256.txt
33
       gyp http 200 https://nodejs.org/download/release/v11.15.0/SHASUMS256.txt
34
       gyp info spawn /usr/bin/python2
35
       gyp info spawn args [ '/tmp/build_d2400638d424ad7a3269162acc30fb7e/.heroku/node/lib/node_modules/npm/node_modules/node-gyp/gyp/gyp_main.py',
36
       gyp info spawn args   'binding.gyp',
37
       gyp info spawn args   '-f',
38
       gyp info spawn args   'make',
39
       gyp info spawn args   '-I',
40
       gyp info spawn args   '/tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/fsevents/build/config.gypi',
41
       gyp info spawn args   '-I',
42
       gyp info spawn args   '/tmp/build_d2400638d424ad7a3269162acc30fb7e/.heroku/node/lib/node_modules/npm/node_modules/node-gyp/addon.gypi',
43
       gyp info spawn args   '-I',
44
       gyp info spawn args   '/app/.node-gyp/11.15.0/include/node/common.gypi',
45
       gyp info spawn args   '-Dlibrary=shared_library',
46
       gyp info spawn args   '-Dvisibility=default',
47
       gyp info spawn args   '-Dnode_root_dir=/app/.node-gyp/11.15.0',
48
       gyp info spawn args   '-Dnode_gyp_dir=/tmp/build_d2400638d424ad7a3269162acc30fb7e/.heroku/node/lib/node_modules/npm/node_modules/node-gyp',
49
       gyp info spawn args   '-Dnode_lib_file=/app/.node-gyp/11.15.0/<(target_arch)/node.lib',
50
       gyp info spawn args   '-Dmodule_root_dir=/tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/fsevents',
51
       gyp info spawn args   '-Dnode_engine=v8',
52
       gyp info spawn args   '--depth=.',
53
       gyp info spawn args   '--no-parallel',
54
       gyp info spawn args   '--generator-output',
55
       gyp info spawn args   'build',
56
       gyp info spawn args   '-Goutput_dir=.' ]
57
       gyp info spawn make
58
       gyp info spawn args [ 'BUILDTYPE=Release', '-C', 'build' ]
59
       make: Entering directory '/tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/fsevents/build'
60
         SOLINK_MODULE(target) Release/obj.target/.node
61
         COPY Release/.node
62
       make: Leaving directory '/tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/fsevents/build'
63
       gyp info ok
64
65
       > node-sass@4.14.0 install /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/node-sass
66
       > node scripts/install.js
67
68
       Downloading binary from https://github.com/sass/node-sass/releases/download/v4.14.0/linux-x64-67_binding.node
69
       Download complete
70
       Binary saved to /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/node-sass/vendor/linux-x64-67/binding.node
71
72
       > node-sass@4.14.0 postinstall /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/node-sass
73
       > node scripts/build.js
74
75
       Binary found at /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/node-sass/vendor/linux-x64-67/binding.node
76
       Testing binary
77
       Binary is fine
78
79
       > ejs@2.7.4 postinstall /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/ejs
80
       > node ./postinstall.js
81
82
       Thank you for installing EJS: built with the Jake JavaScript build tool (https://jakejs.com/)
83
84
85
       > core-js@2.6.11 postinstall /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/core-js
86
       > node -e "try{require('./postinstall')}catch(e){}"
87
88
       Thank you for using core-js ( https://github.com/zloirock/core-js ) for polyfilling JavaScript standard library!
89
90
       The project needs your help! Please consider supporting of core-js on Open Collective or Patreon:
91
       > https://opencollective.com/core-js
92
       > https://www.patreon.com/zloirock
93
94
       Also, the author of core-js ( https://github.com/zloirock ) is looking for a good job -)
95
96
97
       > uws@9.14.0 install /tmp/build_d2400638d424ad7a3269162acc30fb7e/node_modules/uws
98
       > node-gyp rebuild > build_log.txt 2>&1 || exit 0
99
100
101
       > amhs-client@0.0.0 postinstall /tmp/build_d2400638d424ad7a3269162acc30fb7e
102
       > ng build --output-path dist
103
104
105
       Date: 2020-04-29T14:51:08.447Z
106
       Hash: 3d551622b66d1beb5645
107
       Time: 16403ms
108
       chunk {main} main.js, main.js.map (main) 238 kB [initial] [rendered]
109
       chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 428 kB [initial] [rendered]
110
       chunk {runtime} runtime.js, runtime.js.map (runtime) 6.22 kB [entry] [rendered]
111
       chunk {styles} styles.js, styles.js.map (styles) 60.8 kB [initial] [rendered]
112
       chunk {vendor} vendor.js, vendor.js.map (vendor) 4.96 MB [initial] [rendered]
113
       added 1304 packages in 78.585s
114
115
-----> Build
116
       Running build
117
118
       > amhs-client@0.0.0 build /tmp/build_d2400638d424ad7a3269162acc30fb7e
119
       > ng build --prod
120
121
122
       Date: 2020-04-29T14:52:24.535Z
123
       Hash: 459ef7d3fda55011a399
124
       Time: 72281ms
125
       chunk {0} runtime.06daa30a2963fa413676.js (runtime) 1.44 kB [entry] [rendered]
126
       chunk {1} main.478fe235ec2084c25ab2.js (main) 867 kB [initial] [rendered]
127
       chunk {2} polyfills.aeab97ddd8f1df8ccaa1.js (polyfills) 103 kB [initial] [rendered]
128
       chunk {3} styles.495d5a4089b8a2584a59.css (styles) 30.9 kB [initial] [rendered]
129
130
-----> Caching build
131
       - node_modules
132
133
-----> Pruning devDependencies
134
       removed 1204 packages and audited 182 packages in 18.694s
135
       found 3 vulnerabilities (1 low, 1 moderate, 1 high)
136
         run `npm audit fix` to fix them, or `npm audit` for details
137
138
-----> Build succeeded!
139
-----> Discovering process types
140
       Procfile declares types     -> (none)
141
       Default types for buildpack -> web
142
-----> Compressing...
143
       Done: 53.1M
144
-----> Launching...
145
       Released v1
146
       https://amhs-angular.herokuapp.com/ deployed to Heroku


Looking at the Heroku console, the following information was displayed:

Successful build in Heroku

Now, the following URL is ready and available:

https://amhs-angular.herokuapp.com

When used, would integrate with Okta to present the login screen for the AMHS application:

Integration with Okta

Conclusion

Using Heroku for the AMHS Angular client, deployments are reduced from a number of manual steps to a single git command:

git push heroku

In fact, using the CI/CD functionality within GitLab (where the AMHS source code is housed), the process can be automated using a very basic pipeline that is fired when the master branch of the repository changes.

While I am sure there is a way to automate the same steps in AWS S3, more time would time would have been required for learn aspects of technology which is not focused on providing features and functionality to my client.

In the final article of this series, I will cover the following points:

  • Detailing the New Design
  • How Things Have Changed
  • Supportability and Maintainability Using Heroku
  • Lessons Learned

Have a really great day!

application AWS AngularJS Web Service Express Command (computing)

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • Password Authentication. How to Correctly Do It.
  • Stupid Things Orgs Do That Kill Productivity w/ Netflix, FloSports & Refactoring.club
  • JUnit 5 Tutorial: Nice and Easy [Video]
  • How To Integrate Event Streaming Into Your Applications

Comments

Web Dev Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • MVB Program
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends:

DZone.com is powered by 

AnswerHub logo