Skip to content

App para la gestión de clientes implementando NodeJs, Serverless V3, AWS CLI , SSM, Api Gateway, DynamoDB y Otras Tecnologías

License

Notifications You must be signed in to change notification settings

andresWeitzel/AppClientes_ServerlessDynamoDB

Repository files navigation

AppClientes_ServerlessDynamoDB

App para la gestión de clientes implementando NodeJs, Serverless V3, AWS CLI, SSM, Api Gateway, DynamoDB y Otras Tecnologías.


Índice 📜

Ver

Sección 1) Descripción, Tecnologías y Dependencias

Sección 2) Proyecto Serverless desde cero

Sección 3) DynamoDB



Sección 1) Descripción, Tecnologías y Dependencias

1.0) Descripción 🔝

Ver
  • ipsum

1.1) Ejecución del Proyecto 🔝

Ver
  • Crear un entorno de trabajo a través de algún IDE
  • Clonar el Proyecto (git clone https://github.com/andresWeitzel/AppClientes_ServerlessDynamoDB)
  • Dentro del directorio instalar todos los plugins implementados
    • npm install serverless-offline --save-dev
    • npm install -g serverless
    • pip install awscli
    • npm install serverless-offline-ssm --save-dev
    • npm install serverless-dynamodb-local --save
    • serverless dynamodb install
    • serverless dynamodb start --migrate
    • Reiniciar el IDE caso de tenerlo ejecutado.
  • Levantar Serverless en Local (sls offline start)
  • Comprobar respuestas de los endpoints generados a través de alguna herramienta Cliente Http (Ej:Postman)

1.2) Tecnologías 🔝

Ver

Tecnologías Implementadas

Tecnologías Versión Finalidad
SDK 4.3.2 Inyección Automática de Módulas para Lambdas
Serverless Framework Core 3.23.0 Core Servicios AWS
Serverless Plugin 6.2.2 Librerías para la Definición Modular
AWS CLI 1.27.8 Interfaz de línea de comandos unificada para Amazon Web Servicios.
Systems Manager Parameter Store (SSM) 3.0 Manejo de Variables de Entorno
Amazon Api Gateway 2.0 Gestor, Autenticación, Control y Procesamiento de la Api
DynamoDB - SGDB NoSQL
NodeJS 14.18.1 Librería JS
VSC 1.72.2 IDE
Postman 10.11 Cliente Http
CMD 10 Símbolo del Sistema para linea de comandos
Git 2.29.1 Control de Versiones

1.3) Plugins 🔝

Ver
Plugin Descarga
serverless-offline https://www.serverless.com/plugins/serverless-offline
serverless-offline-ssm https://www.npmjs.com/package/serverless-offline-ssm
serverless-dynamodb-local https://www.npmjs.com/package/serverless-dynamodb-local
awscli https://pypi.org/project/awscli/

1.4) Extensiones VSC 🔝

Ver
Extensión
Prettier - Code formatter
YAML - Autoformatter .yml (alt+shift+f)
DotENV


Sección 2) Proyecto Serverless desde cero

Arquitectura Lambda Function y Api gateway

Ver

2.0) Instalación y Configuración de Serverless Local 🔝

Ver
  • Una vez abierto el proyecto instalamos serverless de forma Global npm install -g serverless

  • Seguidamente creamos toda la config de serverless para nuestro proyecto(en mi caso el nombre del proyecto es project-dynamodb) serverless create --template aws-nodejs --path project-dynamodb && cd project-dynamodb

  • Luego inicializamos el package.json en el proyecto npm init -y.

  • Instalamos el plugin serverless-offline npm i serverless-offline

  • Comprobamos versión serverless --version

  • Salida Esperada ..

     Framework Core: 3.23.0
     Plugin: 6.2.2
     SDK: 4.3.2
    
  • Agregamos el plugin instalado de serverless-offline al archivo serverless.yml

  • Configuramos los diversos parámetros necesarios del provider

  • Creamos el directorio src/lambdas/ y todas las funciones lambdas las trabajaremos dentro del mismo

  • Cambiamos el nombre de nuestra lambda handler.js por hello.js y la llevamos al directorio de lambdas

  • Nuestra lambda quedaría....

     'use strict';
    
     module.exports.test = async (event) => {
       return {
         statusCode: 200,
         body: JSON.stringify(
           {
             message: 'HI, LAMBDA ACTIVE',
             input: event,
           },
           null,
           2
         ),
       };
    
     };
  • Configuramos tipo de método y path a través de httpApi para la lambda.

  • Configuramos el puerto http

  • Archivo serveless.yml..

     service: project-dynamodb
    
     frameworkVersion: '3'
    
     provider:
       name: aws
       runtime: nodejs14.x
       stage: dev
       region : us-west-1
       memorySize: 512
       timeout : 20
    
     plugins:
       - serverless-offline 
    
     custom: 
       serverless-offline:
         httpPort: 4000   
    
     functions:
       hello:
         handler: src/lambdas/hello.test
         events:
           - httpApi:
               method: GET
               path: hello
    


2.1) Configuración de Api Gateway 🔝

Ver
  • API Gateway gestiona todas las tareas relacionadas con la aceptación y el procesamiento de centenares de miles de llamadas simultáneas a la API. Estas tareas incluyen la administración del tráfico, el control de la autorización y el acceso, el monitoreo y la administración de versiones de la API.

  • No es necesario la instalación de ningún paquete adicional, este servicio viene incluido en la instalación principal de serverless.

  • Para cada lambda es necesario adicionar el parametro private: true dentro de - httpApi para que se aplique la restricción de acceso correctamente.

  • Vamos a generar una sección de resources . Esta es la plantilla de CloudFormation (Servicio de recursos de AWS) para declarar los recursos de serverless a utilizar.

  • En este caso vamos a extender los diversos manejos de recursos para nuestra Api Gateway. (Tipos, Templates y Códigos de Respuesta).

  • Para desacoplar código de nuestro serverless.yml para a generar un archivo .yml aparte para nuestro api gateway.

  • Creamos dicho archivo dentro de la siguiente jerarquia dentro de nuestro proyecto src/resources/api-gateway.yml

  • Nuestro api-gateway.yml quedaría..

     Resources:
        ApiGatewayRestApi:
        Type: AWS::ApiGateway::RestApi
        Properties:
          Name: apiGatewayRestApi
        #### Gateway Response INIT
      GatewayResponseDefault400:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: DEFAULT_4XX
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-4XX-generic","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseDefault500:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: DEFAULT_5XX
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-5XX-generic","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseAccessDeied:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: ACCESS_DENIED
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-403-access-denied","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseApiConfigurationError:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: API_CONFIGURATION_ERROR
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-500-api-configuration-error","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseAuthorizerConfigurationError:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: AUTHORIZER_CONFIGURATION_ERROR
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-500-authorizer-configuration-error","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseAuthorizerFailure:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: AUTHORIZER_FAILURE
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-500-authorizer-failure","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseBadRequestBody:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: BAD_REQUEST_BODY
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-400-bad-request-body","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseBadRequestParameters:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: BAD_REQUEST_PARAMETERS
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-400-bad-request-parameters","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseExpiredToken:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: EXPIRED_TOKEN
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-403-expired-token","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseIntegrationFailure:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: INTEGRATION_FAILURE
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-504-integration-failure","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseIntegrationTimeout:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: INTEGRATION_TIMEOUT
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-504-integration-timeout","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseInvalidApiKey:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: INVALID_API_KEY
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-403-invalid-api-key","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseInvalidSignature:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: INVALID_SIGNATURE
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-403-invalid-signature","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseMissingAuthenticationToken:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: MISSING_AUTHENTICATION_TOKEN
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-403-missing-authentication-token","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseQuotaExceeded:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: QUOTA_EXCEEDED
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-429-quota-exceeded","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseRequestTooLarge:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: REQUEST_TOO_LARGE
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-413-request-too-large","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseResourceNotFound:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: RESOURCE_NOT_FOUND
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-404-resource-not-found","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseThrottled:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: THROTTLED
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-429-throttled","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseUnauthorized:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: UNAUTHORIZED
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-401-unauthorized","message":$context.error.messageString},"requestId":"$context.requestId"}'
      GatewayResponseUnauthorizedMediType:
        Type: "AWS::ApiGateway::GatewayResponse"
        Properties:
          RestApiId:
            Ref: "ApiGatewayRestApi"
          ResponseType: UNSUPPORTED_MEDIA_TYPE
          ResponseTemplates:
            application/json: '{"error":{"code":"custom-415-unsupported-media-type","message":$context.error.messageString},"requestId":"$context.requestId"}'
        #### Gateway Response END
    
  • Seguidamente referenciamos el archivo api-gateway.yml al serverless.yml

  • La configuración General de nuestro serverless.yml quedaría...

    service: project-dynamodb
    
    frameworkVersion: "3"
    
    provider:
    name: aws
    runtime: nodejs14.x
    stage: dev
    region : us-west-1
    memorySize: 512
    timeout : 10
    
    plugins:
    - serverless-offline
    
    custom:
    serverless-offline:
      httpPort: 4000
    
    functions:
    hello:
      handler: src/lambdas/hello.test
      events:
        - httpApi:
            method: GET
            path: hello
            private: true
    
    resources:
    - ${file(src/resources/api-gateway.yml)}
    


2.2) Ejecución de Serverless Local 🔝

Ver
  • Por defecto tenemos configurado una lambda llamada hello a través de su función .test que se ha modificado( por defecto serverless crea una lambda handler.js)

  • Además tenemos configurada la seguridad y manejo de responses por parte de la Api Gateway, esta nos provera un token de acceso (x-api-key) para el acceso a cada lambda.

  • Levantamos serverless con el comando sls offline start o serverless offline start

  • Visualizamos el endpoint local que serverless nos genera..

    Starting Offline at stage dev (us-east-1)
    
    Offline [http for lambda] listening on http://localhost:3002
    Function names exposed for local invocation by aws-sdk:
             * hello: project-dynamodb-dev-hello
    Remember to use 'x-api-key' on the request headers.
    Key with token: 'd41d8cd98f00b204e9800998ecf8427e'           
             
    GET | http://localhost:3000/hello   
    POST | http://localhost:3000/2015-03-31/functions/hello/invocations 
    
    Server ready: http://localhost:4000
  • Abrimos alguna herramienta para generar peticiones http (ej: Postman) y generamos una request al endpoint generado junto con la api key de nuestro gateway

  • Seleccionamos método GET, luego escribimos el endpoint http://localhost:3000/hello, seguidamente pestaña Headers en KEY colocamos x-api-key y en VALUE colocamos el token generado d41d8cd98f00b204e9800998ecf8427e.

  • Procedemos a ejecutar la request y podemos comprobar la metadata de la lambda en la consola de postman

  • También obtenemos la respuesta por consola..

      GET /hello (λ: hello)
     (λ: hello) RequestId: 63fc1719-ae56-4d56-8296-87d45b44fc96  Duration: 124.64 ms  Billed Duration: 125 ms

2.3) SSM e IAM 🔝

2.3.0) Arquitectura Lambda Function y SSM

Ver

2.3.1) Instalación y Configuración de SSM Local 🔝

Ver
  • Instalamos el plugin npm install serverless-offline serverless-offline-ssm --save-dev

  • Agregamos el complemento dentro del serverless.yml. Es importante mantener el orden siguiente (serverless-offline siempre último por temas de compatibilidad).

      plugins:
         - serverless-offline-ssm
         - serverless-offline
  • Dentro del proyecto creamos el archivo serverless.env.yml que será el que contenga los valores de las variables de entorno de nuestro proyecto

  • Dentro de dicho archivo colocamos una variable test para comprobar su funcionamiento..

      HELLO_TEST : ${ssm:/hello_test}  
  • Relacionamos el arhivo serverless.env.yml dentro del serverless.yml agregando el parametro enviroment junto a dicho archivo..

     provider:
       name: aws
       runtime: nodejs14.x
       stage: dev
       region : us-west-1
       memorySize: 512
       timeout : 10
       environment: ${file(serverless.env.yml)}  
  • Seguidamente vamos a configurar el ssm dentro del bloque custom para el archivo serverless.yml

  • Agregamos el stage (IMPORTANTE RESPETAR EL STAGE DEL PROVIDER Y DEL SSM) y la variable test, quedando..

      custom:
       serverless-offline-ssm:
         stages:
           - dev
         ssm:
           '/hello_test': 'HELLO SSM'
       serverless-offline:
         httpPort: 4000  
  • Por último utilizamos dicha variable en la lambda declarada, para invocar una variable de entorno utilizamos process.env.variable

      'use strict';
    
      module.exports.test = async (event) => {
        return {
          statusCode: 200,
          body: JSON.stringify(
            {
              message: process.env.HELLO_TEST,
              input: event,
            },
            null,
            2
          ),
        };
    
      };
  • Ejecutamos el programa, realizamos la petición get a través de postman a nuestra lambda y podemos visualizar la metadata de la misma junto con nuestra variable de entorno.

  • Salida Esperada..

       {
         "message": "HELLO SSM",
         "input": {
             "body": null,
             "cookies": [],
             "headers": {
                 "x-api-key": "d41d8cd98f00b204e9800998ecf8427e",
                 "content-type": "application/json",
                 "user-agent": "PostmanRuntime/7.29.2",
                 "accept": "*/*",
                 "cache-control": "no-cache",
                 "postman-token": "ea2e186f-732f-450e-abf2-4572b3e0206b",
                 "host": "localhost:4000",
                 "accept-encoding": "gzip, deflate, br",
                 "connection": "keep-alive",
                 "content-length": "163"
             },
             "isBase64Encoded": false,
             "pathParameters": null,
             "queryStringParameters": null,
             "rawPath": "/hello",
             "rawQueryString": "",
             "requestContext": {
                 "accountId": "offlineContext_accountId",
                 "apiId": "offlineContext_apiId",
                 "authorizer": {
                     "jwt": {}
                 },
                 "domainName": "offlineContext_domainName",
                 "domainPrefix": "offlineContext_domainPrefix",
                 "http": {
                     "method": "GET",
                     "path": "/hello",
                     "protocol": "HTTP/1.1",
                     "sourceIp": "127.0.0.1",
                     "userAgent": "PostmanRuntime/7.29.2"
                 },
                 "requestId": "offlineContext_resourceId",
                 "routeKey": "GET hello",
                 "stage": "$default",
                 "time": "31/Oct/2022:23:49:02 -0300",
                 "timeEpoch": 1667270942415
             },
             "routeKey": "GET hello",
             "stageVariables": null,
             "version": "2.0"
         }
     }


Sección 3) DynamoDB

3.0) Arquitectura Lambda, Api Gateway y DynamoDB 🔝

Ver

Index app


3.1) Instalación y Configuración de DynamoDB Local 🔝

Ver
  • Dentro del directorio del proyecto ejecutamos npm i serverless-dynamodb-local --save

  • Agregamos el plugin en el .yml

       plugins:
          - serverless-dynamodb-local
  • Luego instalamos los complementos de dynamo serverless dynamodb install

  • Dentro de src/resources/ creamos el archivo dynamo-table.yml.

  • Agregamos la config de la db (tablas, propiedades, etc) dentro de dicho .yml.

  • Nuestro archivo dynamo-table.yml quedaría....

     Resources:
      usersTable:
        Type: AWS::DynamoDB::Table
        Properties:
          TableName: usersTable
          AttributeDefinitions:
            - AttributeName: year
              AttributeType: N
            - AttributeName: title
              AttributeType: S
          KeySchema:
            - AttributeName: year
              KeyType: HASH
            - AttributeName: title
              KeyType: RANGE
          ProvisionedThroughput:
            ReadCapacityUnits: 10
            WriteCapacityUnits: 10
    
  • Agregamos dicho archivo al serverless.yml en la sección de resources..

     resources:
        - ${file(src/resources/api-gateway.yml)}
        - ${file(src/resources/dynamo-table.yml)}
    
  • Verificamos que Dynamo haya sido configurado correctamente, ejecutamos dicho sgdb... serverless dynamodb start --migrate

  • Salida esperada..

     Dynamodb Local Started, Visit: http://localhost:8000/shell
     DynamoDB - created table usersTable
    

3.2) Ejecución de DynamoDB Local 🔝

Ver
  • Para poder visualizar nuestro esquema de tablas y datos en local debemos utilizar aws cli.

  • Instalamos dicho paquete pip install awscli

  • Comprobamos la versión aws --version

  • Salida Esperada...

      aws-cli/1.27.8 Python/3.9.5 Windows/10 botocore/1.29.8
  • Configuramos credenciales ficticias. DynamoDB offline no las validará, solo hay que proporcionarlas, no puede estar en blanco.

  • export AWS_ACCESS_KEY_ID=223344

  • export AWS_SECRET_ACCESS_KEY=wJalrXUtTHISI/DYNAMODB/bPxRfiCYEXAMPLEKEY

  • Seguidamente en otra bash/terminal levantamos Dynamodb serverless dynamodb start --migrate

  • Salida esperada..

     Dynamodb Local Started, Visit: http://localhost:8000/shell
     DynamoDB - created table usersTable
    
  • Regresando a la consola anterior levantamos aws cli con el endpoint que se nos ha generado (http://localhost:8000) y pasandole la región.

  • Ejecutamos aws dynamodb list-tables --endpoint-url http://localhost:8000 --region=us-east-1

  • Salida Esperada..

        {
         "TableNames": [
             "usersTable"
         ]
     }
    
  • Hemos creado, configurado y ejecutado correctamente lo básico para poder trabajar con DynamoDB



About

App para la gestión de clientes implementando NodeJs, Serverless V3, AWS CLI , SSM, Api Gateway, DynamoDB y Otras Tecnologías

Topics

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Contributors 3

  •  
  •  
  •