Serverless website using Angular, AWS S3, Lambda, DynamoDB and API Gateway Part II

Spread the love
  • 9
    Shares

In the previous article, we created backend system using AWS Lambda, DynamoDB and API Gateway. Unlike the traditional way of hosting code on an application server, we leveraged AWS serverless technologies to host and deploy APIs. In this article, we will develop frontend code, deploy it using S3 static website hosting and finally access the APIs.

We will be using Angular to develop our frontend but I encourage you to use other frameworks to develop UI.

Serverless web application

Serverless web application

UI Project Setup

I assume you have already installed Angular CLI and it is up to date. Go to the directory where an Angular project should be generated and enter following command in command prompt.

ng new serverless

Note that ng is a command line interface from Angular to generate UI Project using ready-made templates. This will save our lot of efforts to set up UI project. It will also download required runtime and development dependencies. Once the command is completed, switch to the serverless directory and enter the following command

ng serve

Open any browser and type http://localhost:4200. You should see a welcome message in the browser with Image and sample links. This step ensures that we have successfully created the Angular project from templates.

User listing

The default text will be removed from the index page and it will be replaced with User listing component. Open the project in Visual Studio Code for editing. You should see three folders – e2e, node_modules, src. We will begin with the generation of a user_listing component. To do that, switch to console and enter the following command

ng generate component user-listing

Design user listing table

The previous command will generate component in src\app\user-listing directory. Note that generated files will not contain any logic but only placeholders to develop the functionality. Open user-listing-component.html file from src\app\user-listing and replace the content with following HTML.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { UserDomainService } from '../services/user-domain.service';


@Component({
  selector: 'app-user-listing',
  templateUrl: './user-listing.component.html',
  styleUrls: ['./user-listing.component.css']
})
export class UserListingComponent implements OnInit {

  constructor(private userService: UserDomainService, private router:Router) { }

  public users;

  ngOnInit() {
    this.loadUsers();
  }

  private loadUsers() {
    this.userService.getUsers().subscribe(
      data => { this.users = data },
      err => console.error(err),
      () => console.log("users loaded.")
    );
  }

  deleteUser(userEmail: string) {
    this.userService.deleteUser(userEmail).subscribe(() => {
      this.loadUsers();
    });
  }

}

Open user-listing.component.html file and replace the contents with following

<h2>User Listing</h2>

<button routerLink="/new-user">New User</button>
<table>
<tr>
<th>First Name</th>
<th>Last Name</th>
<th>Email</th>
<th>Gender</th>
<th>Action</th>
</tr>
<tr *ngFor="let user of users">
<td>{{user.firstName}}</td>
<td>{{user.lastName}}</td>
<td>{{user.email}}</td>
<td>{{user.gender}}</td>
<td><button (click)="deleteUser(user.email)">Delete</button></td>
</tr>
</table>

Our page will look like below.

User Listing

User Listing

Page for adding new users

To add a new user, we must create a new component – new-user. Execute the following command at the console to generate the component

ng generate component new-user

You should now have one more directory under src\app as new-user with 4 different files. Open new-user.component.ts file and replace the contents with following.

import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';

import { User } from '../model/user';
import { UserDomainService } from '../services/user-domain.service';

@Component({
  selector: 'app-new-user',
  templateUrl: './new-user.component.html',
  styleUrls: ['./new-user.component.css']
})
export class NewUserComponent implements OnInit {

  user: User;

  constructor(private userService: UserDomainService,
    private router: Router) { }

  ngOnInit() {
    this.user = new User();
  }

  saveUser() {
    this.userService.saveUser(this.user).toPromise().then(() => {
      this.router.navigate(['user-listing']);
    });
  }

}

Also, open new-user.component.html file and replace the contents with following

<h3>New User</h3>
<form>
<div class="form-group">
<label for="firstName">First Name</label>
<input type="text" name="firstName" [(ngModel)]="user.firstName">
</div>
<div class="form-group">
<label for="lastName">Last Name</label>
<input type="text" name="lastName" [(ngModel)]="user.lastName">
</div>
<div class="form-group">
<label for="email">Email</label>
<input type="text" name="email" [(ngModel)]="user.email">
</div>
<div class="form-group">
<label for="gender">Gender</label>
<input type="text" name="gender" [(ngModel)]="user.gender">
</div>
<div class="form-group">
<button type="submit" (click)="saveUser()">Save User</button>
</div>

</form>

The page will look like below

New User

New User

The UI looks blunt as we haven’t applied any styling to the components. Angular has its own concept of Material Design. You can explore that to style the components and improve the overall look of the page.

Model to hold Data

We must create a class to define the User model structure. Create a new folder ‘model’ under src\app and then create new file user.ts. Replace the contents of the file with following.

export class User {
    firstName: string;
    lastName: string;
    email: string;
    gender: string;
    id?: string;
}

Service for fetching users

While we can write a code inside the component file to fetch user details from AWS API Gateway, it is recommended to separate the code into a service file. This practice is known as Single Responsibility from a popular coding principal SOLID. The component file will declare its dependency on service to fetch the details. It gives us the capability to test the component code without a need of launching services.

Create new folder services under src\app. Switch to the command prompt and enter the following command

ng generate service services/user-domain

Open the user-domain.service.ts file and replace it with following contents.

import { Injectable } from '@angular/core';
import { HttpClient, HttpHeaders } from '@angular/common/http';

import {Observable } from 'rxjs/Rx';

import 'rxjs/add/operator/map';

import { User } from '../model/user';

//Change the following URL with your own API Gateway URL.
const API_URL:string = 'https://dummy_name.execute-api.us-east-1.amazonaws.com/Dev/user';

@Injectable()
export class UserDomainService {

  constructor(private http: HttpClient) { }

  getUsers() {
    return this.http.get(API_URL)
    .map((users: Array)=>{
      let userMap:User[] = [];
      users && users.forEach((user)=>{
        userMap.push({
          firstName: user['au_first_name'],
          lastName: user['au_last_name'],
          email: user['au_email'],
          gender: user['au_gender'],
          id: user['id']
        });
      });
      return userMap;
    });
  }

  deleteUser(userEmail: string) {
    return this.http.delete(API_URL,
      {
        params: {
          "email": userEmail
        }
      });
  }

  saveUser(user: User) {
    let headers = new HttpHeaders({ 'Content-Type': 'application/json' });
    let options = { headers: headers };
    return this.http.post(API_URL, user, options);
  }

}

Make sure to change the value of API_URL constant with your own API gateway URL.

Update the main module

Now that we are done with the definition of components and service, we must modify main application module. We will be using an Angular router to switch between the pages. Open src\app\app.component.html file and replace the content with following

<div style="text-align:center">
<h1>
Welcome to {{ title }}!
</h1>

</div>

<router-outlet></router-outlet>

We must make reference to all the components and define settings for routers to work. Open src\app\app.module.ts file and replace the contents with following

import { BrowserModule } from '@angular/platform-browser';
import { NgModule } from '@angular/core';
import { HttpClientModule } from '@angular/common/http';
import { RouterModule, Routes } from '@angular/router';
import { FormsModule } from '@angular/forms';


import { AppComponent } from './app.component';
import { UserListingComponent } from './user-listing/user-listing.component';

import { UserDomainService } from './services/user-domain.service';
import { NewUserComponent } from './new-user/new-user.component';


const appRoutes: Routes = [
  { path: '', redirectTo: '/user-listing', pathMatch: 'full' },
  { path: 'user-listing', component: UserListingComponent },
  { path: 'new-user', component: NewUserComponent }
];


@NgModule({
  declarations: [
    AppComponent,
    UserListingComponent,
    NewUserComponent
  ],
  imports: [
    BrowserModule,
    FormsModule,
    HttpClientModule,
    RouterModule.forRoot(
      appRoutes,
      {enableTracing: true}
    )
  ],
  exports: [
    RouterModule
  ],
  providers: [UserDomainService],
  bootstrap: [AppComponent]
})
export class AppModule { }

Test our application

It is time to test our application. Go to console and type following command

ng serve

The command will take some time to transpile TypeScript definitions to JavaScript. Once the command is complete, go to a browser and enter URL – http://localhost:4200. You should now see the screen with some table headings and a button to create new User. Go ahead, create new users, delete them.

Build the Angular Code

We must build the final code of Angular. The build can be triggered using the following command

ng build

Above command will combine the scripts we have developed into various JS files along with dependencies. The files produced out of this activity are efficient for distribution. Note the command will take some time to complete. After the completion of the command, you should see new directory – dist.

Hosting the website – Serverless

Once your application is working on local machine. Go to AWS Management Console and open Services > Storage > S3. You should see an option to create new bucket. Click the button and enter following details

Bucket Name: serverlessangular (Note the same name may not be available as it is unique across global, so you can choose a different name.)
Region: US East N. Virginia

Click Next twice. We will leave the properties section as is. In Permissions step, locate Manage public permissions section and change the option to Grant public read access to this bucket. Click next and then Create Bucket button. You should now have bucket created. Click on bucket name and you should see a tabbed interface. Click Properties tab and select Static Website hosting.

Choose Use this bucket to host a website. A new form will be displayed to configure website default document. Enter index.html as Index document and click Save button.

Go back to Overview tab and click Upload button. Drag the files from dist folder and drop it on the popup. Click Next button and in Permissions step, locate Manage public permissions section and change the option to Grant public read access to this object(s). Leave rest of the settings as is and complete the wizard.

It’s time to access our site, using following URL

http://serverlessangular.s3-website-us-east-1.amazonaws.com/user-listing

Note the format for the s3 static website is either of the following

<bucket-name>.s3-website-<AWS-region>.amazonaws.com

<bucket-name>.s3-website.<AWS-region>.amazonaws.com

Takeaway

Congratulation!!! We have just deployed our site on Serverless platform. No need to worry about managing server, patching them and scaling it. Based on the technologies we have used, the entire infrastructure is managed by the AWS. But you are not constrained by AWS alone, you can do the similar exercise on Google cloud or Microsoft Azure.

The key takeaways from this article are, as a developer or architect you stand to gain following benefits

  • Focus only on code and not worry about managing infrastructure.
  • Efficient use of resources, pay for what you use and how much you use. No need to provision servers in advance and pay for idle capacity.
  • Reduce the operation’s overhead of monitoring, patching servers.¬†Let experts handle management of infrastructure.
  • Quickly scale your business across the globe without constraining your imagination to CAPEX.

Bonus you can now view entire Angular Source code on StackBlitz

Leave a Comment.