Getting Started with Serverless Angular Universal on AWS Lambda
Time to read: 8 minutes
You can build search-engine optimized (SEO) friendly single page applications (SPA’s) with Angular Universal, a technology that runs your Angular application on the server. And you can reduce the cost of running those applications with AWS Lambda, an event-driven, serverless computing platform provided as a part of Amazon Web Services (AWS). This post will show you how to build and deploy Angular Universal projects on AWS Lambda using Serverless Framework, an open-source command line interface for building and deploying serverless applications.
In this post we will:
- Create an Angular application that contains two routes and makes calls to an external API
- Add server-side rendering for SEO purposes
- Set up a Serverless Framework framework configuration
- Deploy the app on AWS Lambda
To accomplish the tasks in this post you will need to create an AWS account and install the following:
- Node.js and npm (The Node.js installation will also install npm.)
- Angular CLI
- AWS CLI
To configure the AWS CLI you’ll need the following from your AWS account:
- Access Key ID
- Secret Key
- Default region
Your root user will have all the rights necessary to deploy and run the Angular app as a Lambda function. You can get an Access Key ID and a Secret Key for your root user by generating a new pair in the Identity and Access Management (IAM) console.
If you do not want to use your root user ID for this project you can Set Up an IAM Role and Policy for an API to Invoke Lambda Functions. This process has a number of steps and is not recommended if you are new to AWS.
Be sure you have completed all these tasks successfully before proceeding.
Set up the Angular project and run Hello World!
The first step we must take in every Angular project is initialization and installation of the package:
Let’s make small changes with the generated project. First, let’s add some style to the <app-root>
in the src/index.html
file. Replace the existing code with the following:
Next, replace the existing content of the src/app/app.component.html
with:
Finally, add the following to src/styles.css
:
Note, in the above, that we are using my favorite fancy background:
Download it, and place it in src/assets/img
catalog.
Let’s run the application by typing:
After opening provided by command output URL, with the browser, you should see:
You can find all the code up to this point in a GitHub repository which you can clone:
Components, routing, and services
Our app isn’t really complicated so far. Let’s add some routes, other components, and a service to it:
Ok, we have a lot of files. We will start with src/app/first/first.component.ts
; it will be really straightforward. Replace the default contents with the following:
Edit the second component, src/app/second/second.component.ts
, which is a little bit more complex:
Note: Your development environment or linter may call out makeCall()
. Don’t worry about this: we’ll resolve it below when we create the service.
We introduced few mechanisms here. First is the use of an external template and an external style (templateUrl
and stylesUrls
). Angular gives us the ability to create HTML templates and CSS stylesheets outside of the component class.
Here is how the template for the second component, src/app/second/second.component.html
, should look:
And the stylesheet,src/app/second/second.component.css
, should look like this:
Another mechanism we introduced in this component is dependency injection. If you take a close look at the constructor in second.component.ts
you will see a parameter of type EchoService
. Angular will try to initialize and pass an object of the EchoService
type to our SecondComponent
class when it is initialized. Dependency injection is a technique used to implement an architectural paradigm known as inversion of control (IoC).
We also introduced an Observable
type and an async
pipe in the template. Are you familiar with Promises? An Observable is one step further. This asynchronous type emits values pushed to it by other functions. You can reuse it as many times as you want, subscribe multiple listeners, and more (map, filter, pass to another observable, etc.) You can read more about it at the RxJS GitHub page.
The ‘async’ pipe in second.component.html
template is a special Angular mechanism to display our variable in the view template only when it is evaluated. In other words, the value pushed to the HTML at runtime is sent by the EchoService
observable.
Last, but not least, we implemented the OnInit
interface and the ngOnInit
lifecycle hook. Interfaces in TypeScript work the same way as interfaces in other languages; if you implement it, you must implement all the methods declared in it. In this particular case, we need to implement the ngOnInit()
method. This method is one of the “Angular lifecycle hooks”, methods called automatically by the Angular engine at different stages of view initialization, destruction and other events. According to the Angular documentation:
ngOnInit - Initialize the directive/component after Angular first displays the data-bound properties and sets the directive/component's input properties. Called once, after the first ngOnChanges().
We injected a service into the second component. Now we can create it. Replace the default contents of src/app/echo.service.ts
with the following:
This service contains only one method, which makes a GET request to the https://jsonplaceholder.typicode.com/posts/1
URL.
Ok. That magic looks awesome. But you are probably asking “How does Angular know what to inject? Where to inject it? Where are all those classes initialized?”. And those are great questions. The answer is: NgModule
, the entry point of our app. Time to take a look at what is going on inside of it and import one more module which is necessary for EchoService
: HttpClientModule
(src/app/app.module.ts
):
What do we have here …?
- Imports - links to other NgModules
- Declarations - a list of components used in the application
- Bootstrap - the name of the component we want to load as a “main” one
- Providers - a list of services used across the application
Ok, now it’s time to declare routing. Start by creating a routing module:
We can now add our routes to src/app/app-routing.module.ts
, export RouterModule
from it, and remove redundant code (the declarations
array and CommonModule
import):
We can add some links in the MenuComponent
file, src/app/menu/menu.component.ts
:
Do you see a connection between routerLink
and routes declared in AppModule? Great! This is how we are linking stuff in Angular: routerLink
is an Angular built-in directive which takes path
as a parameter and matches it with path
declared in RouterModule.forRoot()
. When there is a match, it loads the given component into a <router-outlet>
component, which we are going to add into src/app/app.component.html
right now. Replace the code from this file with:
Our app is ready. Time to launch it:
This is what you should see in your browser:
When you click the Second component button you’ll see this if everything is working correctly:
Find all the code up to this point in this GitHub repository you can clone:
Search-engine Optimization (SEO)
Our app looks ready to deploy. But there are some issues if we "think" like a network crawler.
Run our application:
Take a look at our website. You can do this by inspecting the page source or by running the cURL command below (if you have cURL installed):
What’s the problem here? How do single page apps work? In fact, they are pure HTML, with tons of JavaScript attached and executed in the user’s browser.
Are crawlers able to do the same? GoogleBot can render JavaScript, but other crawlers (like Facebook, LinkedIn, Twitter, Bing) can't. Also, websites that “look” like static ones, and don’t expect extra resource requirements from the crawler to read them, are positioned higher in search engine ranking because of their better performance.
How could we solve this issue? Super simple! Type following in your command line:
What just happened? @ng-toolkit updated our project with Angular Universal functionality, technology that runs your Angular application on the server. We have a couple of new files created.
Changes have been made to the src/app/app.module.ts
file, which was the entry point of our app. What @ng-toolkit did is remove the bootstrap
attribute from the @NgModule annotation, removed BrowserModule
from imports array and add a couple new there, NgtUniversalModule
and CommonModule
.
Where our app is bootstrapped now? In fact, it depends. If you are looking for the bootstrap used by the browser, we should navigate to the src/app/app.browser.module.ts
, this is where it resides:
What @ng-toolkit has also done, is the creation of the src/app/app.server.module.ts
. This is an entry point for the code which will be executed on the server side.
Now we can take a look at the project configuration file, angular.json
. What we will find there is a new builder added to our project.
(Ellipsis (“...
”) in a code block indicates a section redacted for brevity.)
As you can see, the entry file for this build is src/main.server.ts
, which has been added to our project as well. By looking at this file we can determine the entry point used by the Angular compiler to create the server side build:
Here it is: src/app/app.server.module.ts
, which is the server-side rendering equivalent of src/app/app.browser.module.ts
, the module that bootstraps the app for browser rendering.
As you probably noticed, @ng-toolkit
also made changes in the package.json
file. We have a couple new scripts there:
The two most important are at the end of the list:
build:prod
, which runs the Angular compiler against the browser and server builds and then creates aserver.js
file, using the Webpack configuration added by @ng-toolkit.server
, which is used to start Node.js with the compiled application.
Give them a try:
We can try to behave like a network crawler:
And boom! If we inspect the page source we can see the generated on the server side. Look for the links generated for the buttons, shown below:
If you don’t see the expected results or encountered an error, you can find all the code up to this point in this GitHub repository, which you can clone:
Deploy!
Awesome! Our app is fully developed and SEO friendly. We are 95% ready for deployment.
We should discuss the remaining 5%.
Usually, we would build our application and publish everything that is under the dist
folder to some hosting service (for example, Amazon S3). The “problem” is that we introduced the server-side rendering mechanism, which needs Node.js running on the backend machine. Do we need a cost-consuming EC2 instance running for 24 hours per day?
Nope. We are going to use AWS Lambda, a Function as a Service (FaaS) environment together with Serverless Framework. By using @ng-toolkit again, we will set up basic configuration for the Serverless Framework.
This command creates the serverless.yml
file, which provides configuration for the Serverless Framework, and the lambda.js
file, which provides the entry point for the AWS Lambda function. It also makes some minor changes in the framework.
To configure the project to run in your default AWS region, edit the serverless.yml
file and replace the value of region:
with the name of your default AWS region. For example, replace ‘eu-central-1 with ‘us-east-2
.
We also have new scripts in package.json
. Make use of one of those:
You should be able to navigate to your application at the URL found in the deploy command output, like that shown above. The URL will include your default region if you changed the value in serverless.yml
.
When you click the Second component button the output should look like this:
As a last step, check that URL with the curl command or by inspecting the page source:
Perfect. Our app is live on the web.
You can find all the code up to this point in a GitHub repository which you can clone:
Summary
Today we have successfully developed and deployed an Angular 7 application on AWS Lambda. You have learned how introduce routing, modules, and services in Angular 7, all of that with server-side rendering for SEO optimization purposes. These techniques enable you to deploy your Angular application on the web in a way that’s accessible to search engines while minimizing your operational costs. With AWS Lambda you pay just for the compute time required to serve your visitor’s requests instead of paying for each minute the server is online.
Resources
GitHub repository: https://github.com/maciejtreder/angular-seo/tree/tutorial1_step4
Also, check out: https://github.com/maciejtreder/ng-toolkit for more Angular and Angular + Serverless Framework features; and my previous article How to Deploy JavaScript & Node.js Applications to AWS Lambda. I'm Maciej Treder, and you can contact me via email at contact@maciejtreder.com or @maciejtreder (GitHub, Twitter, StackOverflow, LinkedIn).
Related Posts
Related Resources
Twilio Docs
From APIs to SDKs to sample apps
API reference documentation, SDKs, helper libraries, quickstarts, and tutorials for your language and platform.
Resource Center
The latest ebooks, industry reports, and webinars
Learn from customer engagement experts to improve your own communication.
Ahoy
Twilio's developer community hub
Best practices, code samples, and inspiration to build communications and digital engagement experiences.