Getting Started with Serverless Angular Universal on AWS Lambda
Tags: angular, serverless, javascript
Published: 2019-09-19 12:37:43

---
title: Getting Started with Serverless Angular Universal on AWS Lambda
published: true
tags: angular, serverless, javascript
canonical_url: https://www.twilio.com/blog/angular-universal-javascript-node-js-aws-lambda
---

_Originally published on the [Twilio Blog](https://www.twilio.com/blog/angular-universal-javascript-node-js-aws-lambda)_

You can build search-engine optimized (SEO) friendly single page applications (SPA’s) with [Angular Universal](https://angular.io/guide/universal), a technology that runs your Angular application on the server. And you can reduce the cost of running those applications with [AWS Lambda](https://aws.amazon.com/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](https://serverless.com/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](https://serverless.com/) framework configuration
- Deploy the app on AWS Lambda

To accomplish the tasks in this post you will need to [create an AWS account](http://aws.amazon.com/) and install the following:

- [Node.js and npm](https://nodejs.org/) (The Node.js installation will also install npm.)
- [Angular CLI](https://cli.angular.io/)
- [AWS CLI](http://docs.aws.amazon.com/cli/latest/userguide/installing.html)

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](https://docs.aws.amazon.com/apigateway/latest/developerguide/integrating-api-with-aws-services-lambda.html#api-as-lambda-proxy-setup-iam-role-policies). 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:

```
ng new angularSeo --style css --routing false
cd angularSeo
```

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:

```
<!doctype html>
<html lang="en">
<head>
 <meta charset="utf-8">
 <title>AngularSeo</title>
 <base href="/">

 <meta name="viewport" content="width=device-width, initial-scale=1">
 <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
 <app-root class="content"></app-root>
 <footer>Built with <a href="https://github.com/maciejtreder/ng-toolkit">ng-toolkit</a> by <a href="https://www.maciejtreder.com">maciejtreder.com</a></footer>
</body>
</html>
```

Next, replace the existing content of the `src/app/app.component.html` with:

```
<h1>Hello World!</h1>
```

Finally, add the following to `src/styles.css`:

```
body {
   margin: 0 auto;
   max-width: 1000px;
   background: url('assets/img/sandbox.png') no-repeat center;
   display: flex;
   flex-direction: column;
   height: 100%;
   font-family: 'Source Sans Pro', calibri, Arial, sans-serif !important;
   min-height: 550px;
}
.content {
   flex: 1 0 auto;
}
footer {
   padding: 10px 0;
   text-align: center;
}
```

Note, in the above, that we are using my favorite fancy background:

![Background](https://thepracticaldev.s3.amazonaws.com/i/q49c7fjs2jik7bs8pxgn.png)

Download it, and place it in `src/assets/img` catalog.

Let’s run the application by typing:

```
ng serve

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **

Date: 2018-10-29T08:58:37.685Z
Hash: cb54e4608cfb1115882b
Time: 7682ms
chunk {main} main.js, main.js.map (main) 10.7 kB [initial] [rendered]
chunk {polyfills} polyfills.js, polyfills.js.map (polyfills) 227 kB [initial] [rendered]
chunk {runtime} runtime.js, runtime.js.map (runtime) 5.22 kB [entry] [rendered]
chunk {styles} styles.js, styles.js.map (styles) 15.9 kB [initial] [rendered]
chunk {vendor} vendor.js, vendor.js.map (vendor) 3.29 MB [initial] [rendered]
```

After opening provided by command output URL, with the browser, you should see:

![Browser screenshot](https://thepracticaldev.s3.amazonaws.com/i/sltf6hhyqxu8bnp3mccb.png)

You can find all the code up to this point in a GitHub repository which you can clone:

```
git clone -b tutorial1_step1 https://github.com/maciejtreder/angular-seo.git angularSeo
cd angularSeo/
npm install
ng serve
```

## Components, routing, and services

Our app isn’t really complicated so far. Let’s add some routes, other components, and a service to it:

```
ng g c first
ng g c second
ng g c menu
ng g s echo
```

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:

```
import { Component } from '@angular/core';

@Component({
   template: '<h1>Hello World!</h1>'
})
export class FirstComponent {

}
```

Edit the second component, `src/app/second/second.component.ts`, which is a little bit more complex:

```
import { Component, OnInit } from '@angular/core';
import { EchoService } from '../echo.service';
import { Observable } from 'rxjs';

@Component({
  templateUrl: `second.component.html`,
  styleUrls: ['./second.component.css']
})
export class SecondComponent implements OnInit {
  public response: Observable<any>;

  constructor(private echoService: EchoService) {}

  public ngOnInit(): void {
      this.response = this.echoService.makeCall();
  }
}
```

_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:

```
<h1>Second component</h1>
<h2>This component injects EchoService</h2>
Response is: <span>{{response | async | json}}</span>
```

And the stylesheet, `src/app/second/second.component.css`, should look like this:

```
span {   
   color: purple;
   display: block;
   background: #ccc;
   padding: 5px;
}
```

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](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Promise)? 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](https://github.com/ReactiveX/rxjs).

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:

```
import { HttpClient } from '@angular/common/http';
import { Observable } from 'rxjs';
import { Injectable } from '@angular/core';

@Injectable({
 providedIn: 'root'
})
export class EchoService {
   constructor(private httpClient: HttpClient) {}

   public makeCall(): Observable<any> {
       return this.httpClient.get<any>('https://jsonplaceholder.typicode.com/posts/1');
   }
 }
```

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`):

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

import { AppComponent } from './app.component';
import { FirstComponent } from './first/first.component';
import { SecondComponent } from './second/second.component';
import { MenuComponent } from './menu/menu.component';
import { AppRoutingModule } from './app-routing.module';
import { HttpClientModule } from '@angular/common/http';

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

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:

```
ng generate module app-routing --flat --module=app
```

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):

```
import { NgModule } from '@angular/core';
import { RouterModule } from '@angular/router';
import { FirstComponent } from './first/first.component';
import { SecondComponent } from './second/second.component';

@NgModule({
 imports: [
   RouterModule.forRoot([
     { path: '', redirectTo: '/firstComponent', pathMatch: 'full' },
     { path: 'firstComponent', component: FirstComponent },
     { path: 'secondComponent', component: SecondComponent }
   ])
 ],
 exports: [ RouterModule ]
})
export class AppRoutingModule { }
```

We can add some links in the `MenuComponent` file,  `src/app/menu/menu.component.ts`:

```
import { Component } from '@angular/core';

@Component({
  selector: 'app-menu',
  template: `
      <ul>
          <li><a routerLink="firstComponent">First component</a></li>
          <li><a routerLink="secondComponent">Second component</a></li>
      </ul>
  `,
  styles: [`
      :host {margin: 0; padding: 0}
      ul {list-style-type: none; padding: 0;}
      li {display: inline-block;}
      a {
          border: 1px solid #666666;
          background: #aaaaaa; border-radius: 5px;
          box-shadow: 1px 1px 5px black;
          color: white;
          font-weight: bold;
          padding: 5px;
          text-decoration: none;
       }
      li + li a {margin-left: 20px;}
  `]
})
export class MenuComponent {
}
```

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:

```
<app-menu></app-menu><router-outlet></router-outlet>
```

Our app is ready. Time to launch it:

```
ng serve -o
```

This is what you should see in your browser:

![Browser screenshot](https://thepracticaldev.s3.amazonaws.com/i/n820n0ilobuialsyukot.png)

When you click the *Second component* button you’ll see this if everything is working correctly:

![Browser screenshot](https://thepracticaldev.s3.amazonaws.com/i/0uwdn1gbzm0wb4sf8shn.png)

Find all the code up to this point in this GitHub repository you can clone:

```
git clone -b tutorial1_step2 https://github.com/maciejtreder/angular-seo.git angularSeo
cd angularSeo/
npm install
ng serve -o
```

## 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:

```
ng serve
```

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):

```
curl localhost:4200

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>AngularSeo</title>
  <base href="/">

  <meta name="viewport" content="width=device-width, initial-scale=1">
  <link rel="icon" type="image/x-icon" href="favicon.ico">
</head>
<body>
  <app-root class="content"></app-root>
  <footer>Built with <a href="https://github.com/maciejtreder/ng-toolkit">ng-toolkit</a> by <a href="https://www.maciejtreder.com">maciejtreder.com</a></footer>
<script type="text/javascript" src="runtime.js"></script><script type="text/javascript" src="polyfills.js"></script><script type="text/javascript" src="styles.js"></script><script type="text/javascript" src="vendor.js"></script><script type="text/javascript" src="main.js"></script></body>
</html>
```

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:

```
ng add @ng-toolkit/universal
```

What just happened? @ng-toolkit updated our project with [Angular Universal](https://angular.io/guide/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:

```
import { AppComponent } from './app.component';
import { AppModule } from './app.module';
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';

@NgModule({
   bootstrap: [AppComponent],
   imports: [
       BrowserModule.withServerTransition({appId: 'app-root'}),
       AppModule,
   ]
})
export class AppBrowserModule {}
```

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.)

```
...
"server": {
  "builder": "@angular-devkit/build-angular:server
    "options": {
      "outputPath": "dist/server",
      "main": "src/main.server.ts",
      "tsConfig": "src/tsconfig.server.json"
    }
  }
}
...
```

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:

```
import { enableProdMode } from '@angular/core';
import { environment } from './environments/environment';

if (environment.production) {
 enableProdMode();
}

export {AppServerModule} from './app/app.server.module';
```

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:

```
...
"build:server:prod": "ng run angularSeo:server && webpack --config webpack.server.config.js --progress --colors",
"build:browser:prod": "ng build --prod",
"build:prod": "npm run build:server:prod && npm run build:browser:prod", "server": "node local.js"
...
```

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 a `server.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:

```
npm run build:prod
npm run server
```

We can try to behave like a network crawler:

```
curl localhost:8080
```

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:

```
...
<app-root class="content" _nghost-sc0="" ng-version="6.1.10"><menu _ngcontent-sc0="" _nghost-sc1="">
<ul _ngcontent-sc1="">
<li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="firstComponent" href="/firstComponent">First component</a></li>
<li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="secondComponent" href="/secondComponent">Second component</a></li>
</ul></menu>
<router-outlet _ngcontent-sc0=""></router-outlet><ng-component><h1>Hello World!</h1></ng-component></app-root>
...
```

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:

```
git clone -b tutorial1_step3 https://github.com/maciejtreder/angular-seo.git angularSeo
cd angularSeo/
npm install
npm run build:prod
npm run server
```

## 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](https://serverless.com/).

```
ng add @ng-toolkit/serverless
```

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:

```
npm run build:serverless:deploy

...
endpoints:
  ANY - https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production/{proxy+}
  ANY - https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production
functions:
  api: angularseo-production-api
```

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:

![Alt Text](https://thepracticaldev.s3.amazonaws.com/i/hbleq1n3mj5hn53zij2i.png)

As a last step, check that URL with the curl command or by inspecting the page source:

```
curl https://5k42947te1.execute-api.eu-central-1.amazonaws.com/production
...
<app-root class="content" _nghost-sc0="" ng-version="6.1.10"><menu _ngcontent-sc0="" _nghost-sc1="">
<ul _ngcontent-sc1="">
<li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="firstComponent" href="/production/firstComponent">First component</a></li>
<li _ngcontent-sc1=""><a _ngcontent-sc1="" routerlink="secondComponent" href="/production/secondComponent">Second component</a></li>
</ul>
</menu><router-outlet _ngcontent-sc0=""></router-outlet>
<ng-component><h1>Hello World!</h1></ng-component></app-root>
...
```

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:

```
git clone -b tutorial1_step4 https://github.com/maciejtreder/angular-seo.git angularSeo
cd angularSeo/
npm install
npm run build:serverless:deploy
```

## 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](https://github.com/maciejtreder/angular-seo/tree/tutorial1_step4)

Also, check out: [https://github.com/maciejtreder/ng-toolkit](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](https://www.twilio.com/blog/2017/09/serverless-deploy-nodejs-application-on-faas-without-pain.html). I'm Maciej Treder, and you can contact me via email at contact@maciejtreder.com or @maciejtreder ([GitHub](https://github.com/maciejtreder), [Twitter](https://twitter.com/MaciejTreder), [StackOverflow](https://stackoverflow.com/users/2849613/maciej-treder), [LinkedIn](https://www.linkedin.com/in/maciej-treder/)).