Tag: serverless

  • Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Upload file dung lượng lớn tới S3 với Multipart và Presign-url

    Nếu như bình thường ta thực hiện single upload tới s3 thì sẽ có 2 cách sau:

    • Upload file lên s3 qua server của chúng ta: Cái này thì reject vì chúng ta phải tốn thời gian upload lên server của mình rồi từ đó mới upload lên S3. Với large file thì gần như là không nên.

    • Upload từ client bằng cách sử dụng presign-url. Thay vì phải qua server trung gian thì chúng ta upload thẳng lên S3. Tốc độ cải thiện rất nhiều vì cách 1 đa phần thời gian tốn ở khâu upload lên server của chúng ta.

    Nhưng nói gì thì nói single upload to S3 thì sẽ có những hạn chế sau dưới Maximum size chỉ là 5GB . Điều này thực sự hạn chế. Hơn nữa AWS cũng suggest chúng ta là file >100MB thì nên sử dụng Multipart Upload . Vậy ưu điểm chính của nó là:

    • Maximum size là 5TB
    • Tốc độ upload tốt hơn

    Điều đó mình lựa chọn multipart và presign-url cho bài toán upload file dung lượng lớn. Server ở đây mình sử dụng là python. Client thì dùng angular.

    part1

    Server

    Phần server này mình sử dụng kiến trúc serverless để triển khai với ngôn ngữ Python Flask trên môi trường AWS Cloud

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Phần Server chủ yếu sẽ có 3 api chính:

    • Đầu tiên là api start-upload, với logic là request tới s3 để lấy uploadId

      @app.route("/start-upload", methods=["GET"])
      def start_upload():
          file_name = request.args.get('file_name')
          response = s3.create_multipart_upload(
              Bucket=BUCKET_NAME, 
              Key=file_name
          )
      
          return jsonify({
              'upload_id': response['UploadId']
          })
      
    • Tiếp theo là api get-upload-url để lấy presignurl cho từng part của file khi upload

      @app.route("/get-upload-url", methods=["GET"])
      def get_upload_url():
           file_name = request.args.get('file_name')
           upload_id = request.args.get('upload_id')
           part_no = request.args.get('part_no')
           signed_url = s3.generate_presigned_url(
               ClientMethod ='upload_part',
               Params = {
                   'Bucket': BUCKET_NAME,
                   'Key': file_name, 
                   'UploadId': upload_id, 
                   'PartNumber': int(part_no)
               }
           )
      
           return jsonify({
               'upload_signed_url': signed_url
           })
      
    • Cuối cùng đây là api để kiểm tra xem việc upload đã hoàn thành chưa.

       @app.route("/complete-upload", methods=["POST"])
       def complete_upload():
           file_name = request.json.get('file_name')
           upload_id = request.json.get('upload_id')
           print(request.json)
           parts = request.json.get('parts')
           response = s3.complete_multipart_upload(
               Bucket = BUCKET_NAME,
               Key = file_name,
               MultipartUpload = {'Parts': parts},
               UploadId= upload_id
           )
           
           return jsonify({
               'data': response
           })
      

    Client

    Phần client này mình dùng Angular để triển khai 1 trang web upload file đơn giản.

    Các bạn tham khảo project trên Github của mình để hiểu cách setup nhé.

    Khi có action upload đầu tiên ta sẽ gọi tới function uploadMultipartFile. Function uploadMultipartFile có chức năng với các step sau:

    • Call api start-upload để lấy được uploadId.

      const uploadStartResponse = await this.startUpload({
          fileName: file.name,
          fileType: file.type
      });
      
    • Split file upload thành các chunks, ở đây chia thành 10MB/chunk nhé. Ở đây mình chia là 10MB vì minimum size của Multipart Upload là 10MB.

    • Thực hiện call api get-upload-url để lấy preSignurl cho từng chunk ở trên.

    • Upload các chunks bằng signurl.

    • Khi tất cả các chunks upload xong sẽ thực hiện call api complete-upload để xác nhận với S3 mình đã upload đầy đủ các part. Done.

      try {
          const FILE_CHUNK_SIZE = 10000000; // 10MB
          const fileSize = file.size;
          const NUM_CHUNKS = Math.floor(fileSize / FILE_CHUNK_SIZE) + 1;
          let start, end, blob;
      
          let uploadPartsArray = [];
          let countParts = 0;
      
          let orderData = [];
      
          for (let index = 1; index < NUM_CHUNKS + 1; index++) {
            start = (index - 1) * FILE_CHUNK_SIZE;
            end = (index) * FILE_CHUNK_SIZE;
            blob = (index < NUM_CHUNKS) ? file.slice(start, end) : file.slice(start);
      
            // (1) Generate presigned URL for each part
            const uploadUrlPresigned = await this.getPresignUrl({
              fileName: file.name,
              fileType: file.type,
              partNo: index.toString(),
              uploadId: uploadStartResponse.upload_id
            });
      
            // (2) Puts each file part into the storage server
      
            orderData.push({
              presignedUrl: uploadUrlPresigned.upload_signed_url,
              index: index
            });
      
            const req = new HttpRequest('PUT', uploadUrlPresigned.upload_signed_url, blob, {
              reportProgress: true
            });
      
            this.httpClient
              .request(req)
              .subscribe((event: HttpEvent<any>) => {
                switch (event.type) {
                  case HttpEventType.UploadProgress:
                    const percentDone = Math.round(100 * event.loaded / FILE_CHUNK_SIZE);
                    this.uploadProgress$.emit({
                      progress: file.size < FILE_CHUNK_SIZE ? 100 : percentDone,
                      token: tokenEmit
                    });
                    break;
                  case HttpEventType.Response:
                    console.log('Done!');
                }
      
                // (3) Calls the CompleteMultipartUpload endpoint in the backend server
      
                if (event instanceof HttpResponse) {
                  const currentPresigned = orderData.find(item => item.presignedUrl === event.url);
      
                  countParts++;
                  uploadPartsArray.push({
                    ETag: event.headers.get('ETag').replace(/[|&;$%@"<>()+,]/g, ''),
                    PartNumber: currentPresigned.index
                  });
                  if (uploadPartsArray.length === NUM_CHUNKS) {
                    console.log(file.name)
                    console.log(uploadPartsArray)
                    console.log(uploadStartResponse.upload_id)
                    this.httpClient.post(`${this.url}/complete-upload`, {
                      file_name: encodeURIComponent(file.name),
                      parts: uploadPartsArray.sort((a, b) => {
                        return a.PartNumber - b.PartNumber;
                      }),
                      upload_id: uploadStartResponse.upload_id
                    }).toPromise()
                      .then(res => {
                        this.finishedProgress$.emit({
                          data: res
                        });
                      });
                  }
                }
              });
          }
        } catch (e) {
          console.log('error: ', e);
        }
      

    Có những chú ý sau.

    • Minimum size của Multipart Upload là 10MB. Maximum là 5TB
    • Được upload tối đã 10000 chunks cho 1 file thôi. Ví dụ ở đây mình chia 1 chunk là 10MB thì mình chỉ upload tối đa 200GB. Tương tự 1 chunk là 5GB => upload tối đa là 5TB.

    Demo

    • Cài đặt server với serverless. Vào project serverless và gõ lệnh

      sls deploy
      

      Có những chú ý sau:

      • Sau khi cài đặt NodeJS thì cài Serverless framework

        npm i -g serverless
        
      • Cần có account AWS

      • Config ở file serverless đang sử dụng aws profile mà mình đã setup ở local(khanhcd92). Nếu bạn dùng default hoặc cấu hình profile riêng thì hãy thay đổi nó nhé. Cách cấu hình aws profile.

        provider:
          name: aws
          runtime: python3.6
          stage: dev
          region: ap-southeast-1
          profile: khanhcd92
        
    • Thay đường dẫn API endpoint từ output của cài đặt server cho url trong class UploadService của project web

    • Cài đặt thư viện trong project web

      npm i
      
    • Chạy web angular ở local với lệnh

      npm start
      

      part2

    • Kéo file cần upload vào trình duyệt. Chỗ này mình sẽ dùng 1 file có dụng lượng khoảng 75M để upload.

      part3

    • Kiểm tra kết quả trên S3 part4

    Chi tiết source code các bạn xem ở đây nhé.

  • Case study ứng dụng serverless trên AWS phục vụ Olympic Tokyo

    Case study ứng dụng serverless trên AWS phục vụ Olympic Tokyo

    Lời nói đầu

    Ở đâu đó có thể các bạn đã nghe thấy khái niệm serverless hay chạy ứng dụng không mà không cần sử dụng một server nào (non-server). Hiện nay với sự phát triển mạnh mẽ của các nền tảng public cloud như AWS, Azure, Alibaba.., khái niệm serverless đang dần trở nên thân thuộc hơn với những lập trình viên. Tuy nhiên bạn đã bao giờ tự tay xây dựng một hệ thống API mà không phải sử dụng server bao giờ chưa? Theo mình thấy thì hiện tại sự trải nghiệm của các dev với serverless thực sự chưa nhiều, một phần có lẽ do người ta vẫn tin tưởng ở server truyền thống hơn(Cái gì sờ thấy được cũng chắc chắn hơn). Ở loạt bài này, mình sẽ trình bày về một dự án team mình xây dựngAPI 100% sử dụng serverless . Mình sẽ tập trung vào kiến trúc hệ thống, giải thích các thành phần và framework hỗ trợ deploy serverless nhé!

    Nội dung

    Bối cảnh

    Khách hàng của mình đã xây dựng hệ thống trên môi trường AWS, sử dụng EC2 làm server, ngôn ngữ là Java và sử dụng framework là Struts . Hệ thống hiện tại chi phí đang quá lớn (Bao gồm cả chi phí AWS cũng như các chi phí liên quan khác), thời gian sử dụng và chạy job trong ngày là không cố định(do nghiệp vụ), nhiều khi không có người sử dụng cũng như không có job nào chạy nhưng cũng phải trả tiền cho 1 server API và 1 server Job. Khách hàng đã yêu cầu chuyển hệ thống cũ sang serverless và phát triển thêm tính năng dựa trên kiến trúc mới này. Hệ thống này mình xây dựng hoàn toàn trên Amazon Web Service, nên các dịch vụ cứ mặc định là của AWS nhé!

    Mô hình hệ thống Serverless

    Có lẽ nhiều người cũng đã nhìn qua kiến trúc serverless như thế này:



    Đúng, nó là 1 kiến trúc chung thường thấy của 1 serverless system triển khai trên AWS. Flow sẽ là:

    • App call API qua API Gateway
    • API Gateway trigger lambda
    • Lambda query data từ DB, trả về kết quả
    • API Gateway response data cho client

    Bla…Bla..

    Tuy nhiên, để ứng dụng nó vào 1 dự án cụ thể cần nhiều hơn thế này, theo dõi phần tiếp theo nhé!

    Hệ thống Serverless trong thực tế

    Mỗi hệ thống sẽ có những điểm giống và khác nhau tuỳ thuộc vào bài toán cần giải quyết. Không loằng ngoằng mình sẽ đưa ra kiến trúc mình đã xây dựng luôn (Đã lược bỏ một số chi tiết, tập trung chính vào phần serverless)

    Overview hệ thống này nhé:

    • Phần màu đỏ là hosting cho Frontend(được viết bằng Angular xxx). Phần FrontEnd sẽ bao gồm một S3 Bucket được setting làm static web, 1 Cloudfront Distribution để cache lại các resource tĩnh GLOBAL.
    • Phần màu xanh là hệ thống API serverless. Chúng ta sẽ quan tâm đến phần này nhiều hơn vì nó là trọng tâm của bài viết này. Nó bao gồm những dịch vụ gì, đi lần lượt nhé:
      • WAF: Web Application Firewall – Đây được coi là bức tường lửa đầu tiên để bảo vệ web. Nhiêm vụ của nó là bảo vệ app qua rule do người dùng thiết lập, ví dụ Whitelist IP, Blacklist IP… Quan trọng hơn là nó có thể phát hiện và chặn những request có dấu hiệu tấn công như XSS, SQL Injection….
      • API Gateway: Điểm nhận tất cả các request từ phía client. AWS cho phép route từng path của request đến những handler tương ứng.
      • Cognito: Dịch vụ này cung cấp phương thức xác thực, phân quyền và quản lý người dùng.
      • Lambda (Authenticate): Vì app của mình có tính năng authen hơi đặc biệt, do vậy mình phải dùng lambda function này để add thêm 1 số feature mà Cognito không đáp ứng đủ. Lambda function này sẽ được đính trực tiếp vào API Gateway, đóng vai trò tương tự như 1 middleware, cũng đặt trong private subnet nhé, nhưng vẽ như thế để tránh rối
      • VPC, Public subnet và private subnet: Cái này nếu ai đã làm qua với AWS và network của nó thì có thể nắm được rồi. Public subnet thì có thể internet facing, private subet là nơi đặt các server EC2, RDS, Lambda là private. Không thể truy cập trực tiếp từ internet vào các dịch vụ được đặt trong private subnet.
      • InternetGateway cho phép VPC có thể truy cập Internet, VPC Endpoint cho phép kết nối đến các dịch vụ khác của AWS mà ko qua đường truyền internet
      • Squid Proxy Server: Đóng vai trò là proxy cho phép các resource từ private subet kết nối ra ngoài Internet(Nhiều người sẽ dùng NAT Gateway hoặc NAT Instance).
      • Lambda (Đặt trong private subnet): Đây chính là linh hồn của Serverless, đóng vai trò tương tự 1 server. Mỗi path của API Gateway sẽ được xử lý bởi 1 lambda function. Lambda sẽ nhận request từ API Gateway, xử lý, trả response về API Gateway -> Response về Client
      • S3: Nếu ko có server thì file được lưu trữ ở đâu, up/down thế nào? Thông thường nếu hệ thống sử dụng autoscale thì cũng cần 1 nơi lưu trữ file chung (EFS hoặc S3 ….). Với Lambda cũng vậy, mình chọn S3 để lưu trữ file. Nhưng làm thế nào để upload và download file qua lambda nhỉ. Câu trả lời là sẽ không up/download file qua lambda, lambda chỉ là trung gian, generate Pre-signed URL để client thực hiện upload và download trực tiếp với S3.
      • DynamoDB: Đây là 1 database dạng NoSQL do AWS phát triển. Lưu data dạng Key-Value. Nếu cần thiết phải sử dụng CSDL quan hệ, mình khuyến khích dùng AWS Aurora serverless(MySQL hoặc PostgreSQL), hỗ trợ tốt nếu sử dụng serverless
      • CloudWatch: Phần này có 1 số dịch vụ nhỏ hơn. Tuy nhiên có 2 service chính là Logs và Rules. Logs là nơi xem, truy vấn log mà Lambda function đã ghi ra trong quá trình chạy, Rules được sử dụng để lập lịch cho 1 số job chạy cố định hàng ngày, khi đến thời gian nó sẽ gọi lambda function tương ứng.
      • SQS: Queue được dùng cho sử dụng cho những job muốn chạy ngay lập tức. SQS trigger đến Lambda function(Job) mỗi khi có message mới được đẩy vào queue.
      • X-Ray: Service này khá hay, nó giúp monitor ứng dụng một cách chi tiết hơn, visualize nó lên trên dashboard AWS, giúp gỡ lỗi ứng dụng, phán đoán lỗi cũng như cải tiến ứng dụng tốt hơn. Ví dụ: Thời gian query data từ DynamoDb, thời gian upload file S3…….
      • SNS: Gửi notification.

    Serverless framework

    Nếu đã từng làm việc với lambda, mọi người sẽ biết được rằng mỗi Lambda function là độc lập với nhau, source code vì vậy cũng hoàn toàn riêng biệt. Vậy với 1 project lớn bao gồm hàng trăm API, làm thế nào chúng ta quản lý source code và deploy, không thể build và upload bản build cho từng lambda function được. Vì vậy team đã quyết định sử dụng framework là serverless(https://www.serverless.com)

    Serverless framework là fw hỗ trợ nhiều cloud provider phổ biến như AWS, Azure, GPC, Alibaba… Nó cung cấp cho chúntg ta 1 công cụ để quản lý full life cycle cho ứng dụng serverless. Serverless framework cũng hỗ trợ nhiều ngôn ngữ như Java, Nodejs, Go, Python …

    Cấu hình serverless:
    Tư tưởng của framework hiểu đơn giản là chúng ta cần mapping Path của API Gateway với class, file xử lý logic cho function tương ứng.

    Đây là 1 file cấu hình sample của project serverless. Một số thành phần quan trọng bao gồm:

    • provider
      • name: Tên cloud provider(aws, gpc, azure)
      • runtime: Môi trường thực thi(java8, java11, nodejs12..)
    • package:
      • artifact: Đường dẫn trỏ đến file build
    • functions: List API của project
      • Tên lambda function:
        • handler: class xử lý logic cho API
        • events:
          • http: (nếu là HTTP thì lambda function này sẽ được trigger từ API Gateway)
            • path: Đường dẫn API
            • method: HTTP method(get, post …)

    Mình chỉ giới thiệu qua cấu hình cơ bản của serverless. Lợi ích của nó là giúp chúng ta dễ dàng quản lý life cycle của project serverless, sử dụng các architype có sẵn khi tạo project.

    Tổng kết lại

    Túm lại, để nói về 1 serverless system thì 1 bài viết là không đủ. Ở phần này mình chỉ overview hệ thống, các dịch vụ và vai trò của nó . Mong rằng qua bài viết này các bạn có thể nắm được cơ bản về kiến trúc 1 hệ thống không sử dụng server truyền thống nó như thế nào, đánh giá xem có thể apply trực tiếp vào dự án tiếp theo được không. Mong rằng sẽ có nhiều hơn dự án sử dụng serverless trong đơn vị để mọi người có thêm những trải nghiệm mới.

  • [AWS] Serverless và SAM, bạn chọn dùng cái nào?

    [AWS] Serverless và SAM, bạn chọn dùng cái nào?

    Mình đã viết khá nhiều bài sử dụng Serverless, tại sao mình lại viết bài này. Thực ra mình cũng mới bắt đầu làm AWS Lambda được một thời gian ngắn. Dự án đầu tiên mình làm Lambda thì đã các bạn đi được đã chọn Serverless để phát triển. Dự án thứ hai mình làm với AWS Lambda thì khách hàng đưa cho mình bộ mã nguồn đã được cấu hình sử dụng Serverless. Mọi thứ đều có vẻ ổn cho đến một ngày mình quyết định thử debug Lambda bằng Visual Studio Code. Mọi thứ trở nên phức tạp và mình tìm thấy SAM, dường như nó đã giải quyết vấn đề của mình nên mình quyết định viết bài này để cho các bạn nếu mới đến với thế giới AWS thì có thể dễ dàng lựa chọn thứ mình cần.

    Chọn Serverless hay SAM?

    Dự án đầu tiên mình dùng Serverless và viết bằng JavaScript, mọi thứ đều ổn vì mình chỉ dùng Serverless có kết hợp với Serverless Offline để chạy các hàm Lambda API trên máy tính cá nhân được. Việc debug cũng không gặp trở ngại gì do Serverless Offline có hỗ trợ debug. Thế nhưng đến dự án tiếp theo, ngôn ngữ được chọn làm môi trường thực thi là Python và mình thực sự gặp khó khăn. Mình vẫn có thể chạy được các hàm Lambda trên máy tính cá nhân nhưng không thể debug đươc. Và thế là mình bắt đầu tìm hiểu để giải quyết vấn đề này. Rồi mình tìm thấy SAM và mọi thứ dường như được giải quyết.

    Ngôn ngữ nào được hỗ trợ?

    • Serverless Offline hỗ trợ những ngôn ngữ sau:
      • Python
      • Ruby
      • Node
    • SAM hỗ trợ nhưng ngôn ngữ sau:
      • Python
      • Ruby
      • Node
      • Java
      • .NET Core

    Được hỗ trợ như thế nào?

    • Serverless Offline là plugin được cá nhân phát triển. Nó không phải gói được hỗ trợ chính thức từ AWS.

    • SAM được hỗ trợ chính thức từ AWS.

    Hỗ trợ debug như thế nào?

    • Serverless Offline chỉ hỗ trợ debug với Node.

    • SAM thì có vẻ như đã hỗ trợ tất cả các trình thực thi ở trên. Mình đã thử debug với Java thì thấy vẫn OK.

    Môi trường thực thi

    • Serverless chạy trực triếp trên máy host.
    • SAM thì sử dụng container trong docker để thực thi.

    Các bạn có thể tham khảo hướng dẫn sử dụng Serverless ở các tài liệu sau nhé:

    Kết luận

    SAM dường như có những lợi thế hơn hẳn so với Serverless. Nếu bạn quyết định phát triển bằng Node thì bạn sẽ không gặp nhiều khó khăn khi dùng Serverless hay SAM. Nếu bạn chọn một môi trường thực thi khác như Python hay Ruby hay bất kỳ môi trường nào khác thì lựa chọn SAM sẽ là quyết định sáng suốt hơn đấy. Mình sẽ hướng dẫn các bạn sử dụng SAM trong loạt bài viết về SAM sau nhé.

  • Mô phỏng AWS Lambda & API Gateway bằng Serverless Offline

    Mô phỏng AWS Lambda & API Gateway bằng Serverless Offline

    Khi phát triển ứng dùng bằng AWS Lambda không phải lúc nào chúng ta cũng có thể phát triển trực tiếp trên AWS được. Do đó việc giả lập môi trường AWS để có thể chạy được Lambda và API Gateway là cần thiết. Nó không chỉ giúp chúng ta có thể học mà còn giúp cho quá trình phát triển nhanh hơn. Trong bài viết này tôi sẽ hướng dẫn các bạn giả lập AWS Lambda và API Gateway bằng Serverless Offline

    Các công cụ cần thiết

    Trước tiên bạn cần cài đặt các tool cần thiết, bạn có thể tham khảo hướng dẫn cài đặt trong các bài viết sau:

    Bạn có thể dùng lệnh sau để cài serverless

    hieunv@HieuNV lambda % yarn global add serverless
    yarn global v1.22.0
    [1/4] ?  Resolving packages...
    [2/4] ?  Fetching packages...
    [3/4] ?  Linking dependencies...
    [4/4] ?  Building fresh packages...
    success Installed "[email protected]" with binaries:
          - serverless
          - slss
          - sls
    ✨  Done in 14.23s.
    

    Tạo một project mới

    • Tạo project với yarn
    hieunv@HieuNV hieunv % mkdir lambda
    hieunv@HieuNV hieunv % cd lambda
    hieunv@HieuNV lambda % yarn init
    yarn init v1.22.0
    question name (lambda):
    question version (1.0.0):
    question description:
    question entry point (index.js):
    question repository url:
    question author:
    question license (MIT):
    question private:
    success Saved package.json
    ✨  Done in 3.53s.
    
    • Cài đặt serverless-offline
    hieunv@HieuNV lambda % yarn add serverless-offline -D
    
    • Cài đặt serverless-python-requirements để viết lambda handler bằng python
    hieunv@HieuNV lambda % yarn add serverless-python-requirements -D
    

    Cấu hình serverless.yml

    serverless.yml

    service: lambda
    
    frameworkVersion: '>=1.1.0 <2.0.0'
    
    provider:
      name: aws
      runtime: python3.7
    custom:
      serverless-offline:
        port: 4000
    plugins:
      - serverless-offline
      - serverless-python-requirements
    

    Cấu hình lambda handler đầu tiên trong serverless.yml

    Chúng ta tạo một Rest API sử dụng lambda bằng cách thêm đoạn sau vào file serverless.yml

    functions:
      test:
        handler: src.api.test.lambda_handler
        events:
          - http:
              method: get
              path: api/test
              cors: true
    

    Ở đây chúng ta tạo ra một Rest API với phướng thức GET và path /api/test. Các bạn nhìn thấy handler: src.api.test.lambda_handler đúng không. Đây là cấu hình hàm lamda sẽ được gọi bởi API Gateway

    Viết code cho lambda handler

    src/api/test.py

    import json
    
    
    def lambda_handler(event, context):
        headers = {"Access-Control-Allow-Origin": "*", "Accept": "application/json"}
        return {
            "statusCode": 200,
            "headers": headers,
            "body": json.dumps({"status": "success", "data": {}}),
        }
    

    Tạo script để run server

    Thêm đoạn sau vào package.json

        "scripts": {
            "start": "sls offline start"
        },
    

    Giờ thì chạy thôi nào các thanh niên

    hieunv@HieuNV lambda % yarn start
    yarn run v1.22.0
    $ sls offline start
    Serverless: Starting Offline: dev/us-east-1.
    
    Serverless: Routes for test:
    Serverless: GET /api/test
    Serverless: POST /{apiVersion}/functions/lambda-dev-test/invocations
    
    Serverless: Offline [HTTP] listening on http://localhost:4000
    Serverless: Enter "rp" to replay the last request
    

    Dùng Postman để call api vừa tạo nhé:

    Cám ơn các bạn đã theo dõi bài viết. Hy vọng bài viết có thể giúp các bạn tiếp tục học và làm việc cùng với AWS Lambda và API Gateway trong các dự án của mình.