Setup Angular 10 Universal on AWS Lambda from scratch

Sahil Purav
Jan 5 · 7 min read
Image for post
Image for post
Deploy Angular Universal on AWS Lambda with Serverless framework from scratch

Update: Thank you very much for your overwhelming response on original article. I’ve received a lot of request on social platforms for updating the article to support Angular 10+. So here you go… this article is an edited version of setting up Angular 6–8 universal setup on serverless framework.

Introduction

Angular has a “Universal” package that solves above specific problems by generating the static application page that later converts to a fully functional SPA page by bootstrapping at the client browser. It improves the first-page load drastically that gives users a chance to view some parts of the application view before it becomes fully functional. Since the title, meta, contents, etc are generated from the server, search engines easily understand and make them SEO friendly.

Angular Universal requires the page to be generated on Server hence, in an ideal scenario, we would require a physical instance e.g. AWS EC2. Having a physical server increases infrastructure management as you may want to configure your servers with required packages and auto-scale based on the load.

In this article, we’re going to learn how to deploy your entire universal application on AWS Lambda using “Serverless Framework”. Serverless is a buzzword that has been on the rise for the past few years, and it doesn’t look like it’s going to stop, more companies are adopting it considering its benefits like “low cost”, “ease of deployment”, “infinite scalability”, “improved latency” and many more.

Why setting up from scratch?

  • Simulate lambda and API gateway on local
  • Easy termination of the entire infrastructure
  • More flexibility to customize the process
  • …and of course, you will learn the insights!

Prerequisites

If you don’t have any of the above prerequisites, please click on the links and follow the instructions to get started.

Once you installed AWS CLI, configure the access id and secret key (rest things are optional and you can keep leave them blank) by hitting:

aws configure

In a hurry?

Set up an Angular project

ng new ngx-serverless-starter --style scss --routing true
cd ngx-serverless-starter

Let’s test the application by hitting:

ng serve

Test your application by navigating to http://localhost:4200

Set up an Angular Universal

ng add @nguniversal/express-engine

The above command will install the necessary prerequisites and add new files to support the server-side rendering.

Run the following command to test your SSR application:

npm run build:ssr && npm run serve:ssr

Test your application by navigating to http://localhost:4000. If you view source the page, you will see <app-root> has real HTML elements:

Image for post
Image for post
View Source of Angular Universal Page

Install dependencies for Serverless

npm install serverless serverless-offline serverless-apigw-binary -D
npm install aws-serverless-express --save

Configure Angular Universal to support Lambda

serverless.yml

service: angular-serverless-starterplugins:
- serverless-apigw-binary
- serverless-offline
provider:
name: aws
runtime: nodejs12.x
memorySize: 192
timeout: 10
package:
exclude:
- ./**
include:
- "node_modules/aws-serverless-express/**"
- "node_modules/binary-case/**"
- "node_modules/type-is/**"
- "node_modules/media-typer/**"
- "node_modules/mime-types/**"
- "node_modules/mime-db/**"
- "dist/**"
- "lambda.js"
custom:
apigwBinary:
types:
- "*/*"
functions:
api:
handler: lambda.handler
events:
- http: GET {proxy+}
- http: GET /

The “serverless-offline” will help us to simulate the Lambda on a local machine.

Understanding default “server” configuration

"server": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/ngx-serverless-starter/server",
"main": "server.ts",
"tsConfig": "tsconfig.server.json"
},
"configurations": {
"production": {
"outputHashing": "media",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"sourceMap": false,
"optimization": true
}
}
},

Hence if you enter ng run ngx-serverless-starter:server:production this command, “server.ts” file gets compiled to a “{project}/server/main.js”.

Setting up our own serverless workflow

"serverless": {
"builder": "@angular-devkit/build-angular:server",
"options": {
"outputPath": "dist/ngx-serverless-starter/serverless",
"main": "serverless.ts",
"tsConfig": "tsconfig.serverless.json"
},
"configurations": {
"production": {
"outputHashing": "media",
"fileReplacements": [
{
"replace": "src/environments/environment.ts",
"with": "src/environments/environment.prod.ts"
}
],
"sourceMap": false,
"optimization": true
}
}
},

As seen above, the new configuration is named as “serverless” and it has different tsConfig i.e. tsconfig.serverless.json

Let’s go ahead and create tsconfig.serverless.json file with following configuration:

{
"extends": "./tsconfig.app.json",
"compilerOptions": {
"outDir": "./out-tsc/serverless",
"target": "es2016",
"types": [
"node"
]
},
"files": [
"src/main.server.ts",
"serverless.ts"
],
"angularCompilerOptions": {
"entryModule": "./src/app/app.server.module#AppServerModule"
}
}

We’ve added new file called serverless.ts to keep the default server.ts configuration intact.

Next create serverless.ts file and add the following code:

import 'zone.js/dist/zone-node';import { ngExpressEngine } from '@nguniversal/express-engine';
import * as express from 'express';
import { join } from 'path';
import { AppServerModule } from './src/main.server';
import { APP_BASE_HREF } from '@angular/common';
import { existsSync } from 'fs';
export const app = express();
const distFolder = join(process.cwd(), 'dist/ngx-serverless-starter/browser');
const indexHtml = existsSync(join(distFolder, 'index.original.html')) ? 'index.original.html' : 'index';
// Our Universal express-engine (found @ https://github.com/angular/universal/tree/master/modules/express-engine)
app.engine('html', ngExpressEngine({
bootstrap: AppServerModule,
}));
app.set('view engine', 'html');
app.set('views', distFolder);
// Example Express Rest API endpoints
// app.get('/api/**', (req, res) => { });
// Serve static files from /browser
app.get('*.*', express.static(distFolder, {
maxAge: '1y'
}));
// All regular routes use the Universal engine
app.get('*', (req, res) => {
res.render(indexHtml, { req, providers: [{ provide: APP_BASE_HREF, useValue: req.baseUrl }] });
});
export * from './src/main.server';

We’ve removed the run function from default server.ts as serverless doesn’t require any running port and early exported the express server variable.

lambda.js

functions:
api:
handler: lambda.handler

Let’s start by creating the handler function inside the lamba.js file:

const awsServerlessExpress = require("aws-serverless-express");
const server = require("./dist/ngx-serverless-starter/serverless/main");
const awsServerlessExpressMiddleware = require("aws-serverless-express/middleware");
const binaryMimeTypes = [
"application/javascript",
"application/json",
"application/octet-stream",
"application/xml",
"image/jpeg",
"image/png",
"image/gif",
"text/comma-separated-values",
"text/css",
"text/html",
"text/javascript",
"text/plain",
"text/text",
"text/xml",
"image/x-icon",
"image/svg+xml",
"application/x-font-ttf",
"font/ttf",
"font/otf",
];
server.app.use(awsServerlessExpressMiddleware.eventContext());const serverProxy = awsServerlessExpress.createServer(
server.app,
null,
binaryMimeTypes
);
module.exports.handler = (event, context) =>
awsServerlessExpress.proxy(serverProxy, event, context);

We’re using the “aws-serverless-express” wrapper that gets the express app instance from distribution to serve it on lambda. MIME types can be different based on the project requirements.

Add .serverless inside .gitignore that ignores the output generated by the serverless framework.

Simulate lambda on local

"serve:sls": "serverless offline start",
"build:sls": "ng build --prod && ng run ngx-serverless-starter:serverless:production"

Test the application by running:

npm run build:sls && npm run serve:slsOutput:
> ngx-serverless-starter@0.0.0 build:sls /var/www/ngx-serverless-starter
> ng build --prod && ng run ngx-serverless-starter:serverless:production
chunk {} runtime.e227d1a0e31cbccbf8ec.js (runtime) 1.45 kB [entry] [rendered]
chunk {1} main.fa313e0e09c328d5e42b.js (main) 217 kB [initial] [rendered]
chunk {2} polyfills.a4021de53358bb0fec14.js (polyfills) 36.1 kB [initial] [rendered]
chunk {3} styles.09e2c710755c8867a460.css (styles) 0 bytes [initial] [rendered]
Date: 2020-07-26T11:23:42.482Z - Hash: 2e90a22d768fdd38c8fd - Time: 9719ms
Hash: 08135596ec5edbc91ef5
Time: 7228ms
Built at: 26/07/2020 16:53:53
Asset Size Chunks Chunk Names
main.js 2.68 MiB 0 [emitted] [big] main
Entrypoint main [big] = main.js
chunk {0} main.js (main) 5.08 MiB [entry] [rendered]
> ngx-serverless-starter@0.0.0 serve:sls /var/www/ngx-serverless-starter
> serverless offline start
offline: Starting Offline: dev/us-east-1.
offline: Offline [http for lambda] listening on http://localhost:3002
offline: [HTTP] server ready: http://localhost:3000 🚀

Just open http://localhost:3000/dev and whoa! you’ve successfully tested your application by simulating lambda on local.

Deploy on AWS

"deploy": "serverless deploy"

And run npm run build:sls && npm run deploy... It may take some time to package and upload your code but after a while, you will get the URL (https://{api-gateway-arn}.execute-api.{your-region}.amazonaws.com/dev) on the CLI, just hit it and enjoy looking at Angular served from Lambda ☁️ ⚡️

Terminate Infrastructure

"terminate": "serverless remove"

Running npm run terminate will delete all the AWS resources created for your project.

Cactus Tech Blog

Welcome to the Cactus Tech community!

Sahil Purav

Written by

Sr. Software Architect at Cactus Communications, India. I help building highly scalable architecture for Authors and help them to publish their papers

Cactus Tech Blog

Welcome to the Cactus Tech community! We’re shaping the future of scholarly and medical communications with innovative solutions and cutting-edge technology. Like what we do? You can join us too! https://tech.cactusglobal.io/

Sahil Purav

Written by

Sr. Software Architect at Cactus Communications, India. I help building highly scalable architecture for Authors and help them to publish their papers

Cactus Tech Blog

Welcome to the Cactus Tech community! We’re shaping the future of scholarly and medical communications with innovative solutions and cutting-edge technology. Like what we do? You can join us too! https://tech.cactusglobal.io/

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store