App para la gestión de clientes implementando NodeJs, Serverless V3, AWS CLI, SSM, Api Gateway, DynamoDB y Otras Tecnologías.
Ver
- 1.0) Descripción del Proyecto.
- 1.1) Ejecución del Proyecto.
- 1.2) Tecnologías.
- 1.3) Plugins.
- 1.4) Extensiones VSC.
- 2.0) Instalación y Configuración de Serverless Local
- 2.1) Configuración de Api Gateway
- 2.2) Ejecución de Serverless Local
- 2.3) SSM y IAM
- 3.0) Arquitectura Lambda, Api Gateway y DynamoDB
- 3.1) Instalación y Configuración de DynamoDB Local
- 3.2) Ejecución de DynamoDB Local
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 | 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 |
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
porhello.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
- Guía Oficial : https://www.serverless.com//blog/serverless-framework-v3-is-live)
- Guía Recomendada : https://medium.com/@patricio.aranguiz/serverless-offline-aws-lambda-api-gateway-15a4dfdfbc16
- Config Parámetros Provider : https://www.tutorialspoint.com/serverless/serverless_regions_memory_size_timeouts.htm
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
alserverless.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
oserverless 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 endpointhttp://localhost:3000/hello
, seguidamente pestañaHeaders
enKEY
colocamosx-api-key
y enVALUE
colocamos el token generadod41d8cd98f00b204e9800998ecf8427e
. -
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.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 delserverless.yml
agregando el parametroenviroment
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 archivoserverless.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" } }
3.0) Arquitectura Lambda, Api Gateway y DynamoDB 🔝
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 archivodynamo-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
-
https://dynobase.dev/run-dynamodb-locally/#serverless-framework
-
https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-dynamo-db.html
-
https://dynobase.dev/run-dynamodb-locally/#connecting-dynamodb-offline-cli
-
CRUD DynamoDB : https://medium.com/dev-jam/serverless-simple-crud-application-in-10-minutes-on-aws-69e022298e80
-
ÚLTIMA DOC IMPLEMENTADA CRUD DynamoDB : https://dev.to/rajandmr/dynamodb-crud-with-nodejs-and-lambda-inn