Lesson 21-Serverless and Frontend Technology Stack

Serverless and Static Websites

Static Website Hosting with Serverless

Comparison of Static Website Hosting Solutions

SolutionAdvantagesDisadvantagesUse Cases
S3 + CloudFrontLow cost, global CDN accelerationRequires manual deployment managementSimple static websites
Netlify/VercelAutomated deployment, continuous integrationVendor lock-in riskDeveloper-friendly projects
Serverless FrameworkFull-stack integration, flexible customizationHigh configuration complexityComplex frontend applications

S3 Static Website Hosting Configuration Example

# serverless.yml
service: static-website

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

resources:
  Resources:
    WebsiteBucket:
      Type: AWS::S3::Bucket
      Properties:
        BucketName: ${self:service}-${self:provider.stage}-website
        AccessControl: PublicRead
        WebsiteConfiguration:
          IndexDocument: index.html
          ErrorDocument: index.html

    CloudFrontDistribution:
      Type: AWS::CloudFront::Distribution
      Properties:
        DistributionConfig:
          Origins:
            - DomainName: ${self:resources.Resources.WebsiteBucket.DomainName}
              Id: S3Origin
              S3OriginConfig: {}
          Enabled: true
          DefaultCacheBehavior:
            TargetOriginId: S3Origin
            ViewerProtocolPolicy: redirect-to-https
          ViewerCertificate:
            CloudFrontDefaultCertificate: true

Deployment Command

serverless deploy --stage prod

API Gateway and Frontend Interaction

API Gateway Configuration Key Points

  1. CORS Configuration
functions:
  apiHandler:
    handler: handler.apiHandler
    events:
      - http:
          path: api/data
          method: get
          cors:
            origin: '*'
            headers:
              - Content-Type
              - Authorization
            allowCredentials: false
  1. API Gateway Type Selection
  • REST API: Suitable for simple RESTful interfaces
  • HTTP API: Lower cost (70% cheaper), faster performance
  • WebSocket API: Real-time bidirectional communication

Frontend Call Example (React)

import axios from 'axios';

const fetchData = async () => {
  try {
    const response = await axios.get('https://api.example.com/data', {
      headers: {
        'Authorization': `Bearer ${localStorage.getItem('token')}`
      }
    });
    setData(response.data);
  } catch (error) {
    console.error('API request failed:', error);
  }
};

Frontend Deployment (Netlify/Vercel)

Netlify Deployment Process

  1. Install Netlify CLI
npm install -g netlify-cli
  1. Login and Configure
netlify login
netlify init
  1. Deployment Command
netlify deploy --prod

Vercel Deployment Advantages

  1. Automatic Builds: Recognizes build commands in package.json
  2. Preview Environments: Creates preview URLs for each commit
  3. Custom Domains: One-click custom domain binding

Vercel Configuration Example (vercel.json)

{
  "rewrites": [
    { "source": "/api/(.*)", "destination": "/api-proxy/$1" }
  ],
  "redirects": [
    { "source": "/old-path", "destination": "/new-path", "permanent": true }
  ]
}

Serverless and Frontend Frameworks

React with Serverless

Integrating Serverless into a React Project

  1. Create React Application
npx create-react-app my-app
cd my-app
  1. Install Serverless Components
npm install serverless serverless-finch --save-dev
  1. Configure serverless.yml
service: react-frontend

provider:
  name: aws
  runtime: nodejs14.x
  region: us-east-1

plugins:
  - serverless-finch

custom:
  client:
    bucketName: react-frontend-${self:provider.stage}
    distributionFolder: build
    indexDocument: index.html
    errorDocument: index.html
  1. Deployment Command
serverless client deploy

React Component Example

import React, { useState, useEffect } from 'react';
import axios from 'axios';

function App() {
  const [data, setData] = useState(null);

  useEffect(() => {
    const fetchData = async () => {
      try {
        const response = await axios.get('/api/data');
        setData(response.data);
      } catch (error) {
        console.error('Error fetching data:', error);
      }
    };
    
    fetchData();
  }, []);

  return (
    <div className="App">
      {data ? <pre>{JSON.stringify(data, null, 2)}</pre> : 'Loading...'}
    </div>
  );
}

export default App;

Vue.js with Serverless

Vue CLI Project Integration

  1. Create Vue Project
vue create my-vue-app
cd my-vue-app
  1. Configure Serverless Deployment
npm install serverless serverless-finch --save-dev
  1. Modify vue.config.js
module.exports = {
  publicPath: process.env.NODE_ENV === 'production' 
    ? '/vue-app/' 
    : '/'
};
  1. serverless.yml Configuration
service: vue-frontend

provider:
  name: aws
  runtime: nodejs14.x

plugins:
  - serverless-finch

custom:
  client:
    bucketName: vue-frontend-${self:provider.stage}
    distributionFolder: dist
    indexDocument: index.html
    errorDocument: index.html

Vue Component Example

<template>
  <div id="app">
    <div v-if="loading">Loading...</div>
    <div v-else>
      <pre>{{ data }}</pre>
    </div>
  </div>
</template>

<script>
import axios from 'axios';

export default {
  data() {
    return {
      loading: true,
      data: null
    };
  },
  async created() {
    try {
      const response = await axios.get('/api/data');
      this.data = response.data;
    } catch (error) {
      console.error('Error:', error);
    } finally {
      this.loading = false;
    }
  }
};
</script>

Angular with Serverless

Angular CLI Project Integration

  1. Create Angular Application
ng new angular-app
cd angular-app
  1. Configure Serverless Deployment
npm install serverless serverless-finch --save-dev
  1. Modify angular.json
"architect": {
  "build": {
    "options": {
      "outputPath": "dist/angular-app"
    }
  }
}
  1. serverless.yml Configuration
service: angular-frontend

provider:
  name: aws
  runtime: nodejs14.x

plugins:
  - serverless-finch

custom:
  client:
    bucketName: angular-frontend-${self:provider.stage}
    distributionFolder: dist/angular-app
    indexDocument: index.html
    errorDocument: index.html

Angular Service Example

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

@Injectable({
  providedIn: 'root'
})
export class DataService {
  private apiUrl = '/api/data';

  constructor(private http: HttpClient) { }

  getData(): Observable<any> {
    return this.http.get(this.apiUrl);
  }
}

Serverless and Real-Time Communication

WebSocket Implementation in Serverless

WebSocket API Gateway Configuration

functions:
  connect:
    handler: handler.connect
    events:
      - websocket: $connect

  disconnect:
    handler: handler.disconnect
    events:
      - websocket: $disconnect

  default:
    handler: handler.message
    events:
      - websocket: $default

Connection Management Example

// Store connection ID on connect
const connections = new Map();

exports.connect = async (event) => {
  const connectionId = event.requestContext.connectionId;
  connections.set(connectionId, true);
  
  return {
    statusCode: 200,
    body: 'Connected'
  };
};

exports.disconnect = async (event) => {
  const connectionId = event.requestContext.connectionId;
  connections.delete(connectionId);
  
  return {
    statusCode: 200
  };
};

Message Broadcasting Example

const AWS = require('aws-sdk');

exports.message = async (event) => {
  const body = JSON.parse(event.body);
  const message = body.message;
  
  // Broadcast message to all connections
  for (const [connectionId, _] of connections.entries()) {
    try {
      await sendToConnection(connectionId, message, event);
    } catch (err) {
      console.error(`Failed to send to ${connectionId}:`, err);
      connections.delete(connectionId);
    }
  }
  
  return {
    statusCode: 200,
    body: 'Message sent'
  };
};

async function sendToConnection(connectionId, message, event) {
  const apigwManagementApi = new AWS.ApiGatewayManagementApi({
    apiVersion: '2018-11-29',
    endpoint: event.requestContext.domainName + '/' + event.requestContext.stage
  });
  
  return apigwManagementApi.postToConnection({
    ConnectionId: connectionId,
    Data: JSON.stringify({ message })
  }).promise();
}

Server-Sent Events (SSE) Practice

SSE Implementation Example

exports.handler = async (event) => {
  // Set SSE response headers
  const headers = {
    'Content-Type': 'text/event-stream',
    'Cache-Control': 'no-cache',
    'Connection': 'keep-alive'
  };
  
  // Check if it's an SSE request
  if (event.httpMethod === 'GET' && event.path === '/events') {
    // Keep connection open
    const response = {
      statusCode: 200,
      headers,
      body: '',
      isBase64Encoded: false
    };
    
    // Simulate streaming data
    const interval = setInterval(() => {
      const data = JSON.stringify({ time: new Date().toISOString() });
      // In a real environment, send via WebSocket or other mechanism
      console.log(`data: ${data}\n\n`);
    }, 1000);
    
    // Simplified handling for Serverless environment
    return response;
  }
  
  return {
    statusCode: 404,
    body: 'Not Found'
  };
};

Frontend SSE Client

const eventSource = new EventSource('/api/events');

eventSource.onmessage = (e) => {
  const data = JSON.parse(e.data);
  console.log('New event:', data);
};

eventSource.onerror = (err) => {
  console.error('EventSource failed:', err);
  eventSource.close();
};

Real-Time Data Push Optimization

Optimization Strategies

  1. Connection Reuse
// Use a single WebSocket connection for multiple message types
const socket = new WebSocket('wss://api.example.com/ws');

function sendMessage(type, data) {
  if (socket.readyState === WebSocket.OPEN) {
    socket.send(JSON.stringify({ type, data }));
  }
}
  1. Message Batching
let messageQueue = [];
let isSending = false;

async function processQueue() {
  if (isSending || messageQueue.length === 0) return;
  
  isSending = true;
  const batch = messageQueue.splice(0, 10); // Max 10 messages per batch
  
  try {
    await sendBatch(batch);
  } finally {
    isSending = false;
    processQueue(); // Process next batch
  }
}

function enqueueMessage(message) {
  messageQueue.push(message);
  processQueue();
}
  1. Heartbeat Mechanism
// Client-side heartbeat detection
let pingInterval;

function startHeartbeat() {
  pingInterval = setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
      socket.send(JSON.stringify({ type: 'ping' }));
    }
  }, 30000); // Every 30 seconds
}

function stopHeartbeat() {
  clearInterval(pingInterval);
}
  1. Reconnection Logic
let reconnectAttempts = 0;
const maxReconnectAttempts = 5;

function connect() {
  const socket = new WebSocket('wss://api.example.com/ws');
  
  socket.onclose = () => {
    if (reconnectAttempts < maxReconnectAttempts) {
      reconnectAttempts++;
      setTimeout(connect, 1000 * Math.pow(2, reconnectAttempts)); // Exponential backoff
    }
  };
  
  // Other event handlers...
}

connect();
  1. Data Compression
// Compress messages with gzip
const pako = require('pako');

function compressMessage(message) {
  const jsonString = JSON.stringify(message);
  return pako.deflate(jsonString, { to: 'string' });
}

function sendMessage(socket, message) {
  const compressed = compressMessage(message);
  socket.send(compressed);
}
  1. Smart Routing
// Route messages based on type
function handleMessage(event) {
  const data = JSON.parse(event.body);
  
  switch(data.type) {
    case 'chat':
      return handleChatMessage(data);
    case 'notification':
      return handleNotification(data);
    default:
      return { statusCode: 400, body: 'Unknown message type' };
  }
}

Share your love