Inicio CRUD con Serverless, Lambda y DynamoDB como IaC
Post
Cancel

CRUD con Serverless, Lambda y DynamoDB como IaC

IaC serverless v4

  • Ejecutar en terminal serverless y seleccionar plantilla ❯ AWS / Node.js / HTTP API
  • npm init

serverless.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}

functions:
  hello:
    handler: handler.hello
    events:
      - httpApi:
          path: /
          method: get
  • sls deploy --verbose
  • probar endpoint
  • sls remove --verbose

Cambiar la ubicación de la función inicial

src/handlers/postProduct.mjs

1
2
3
4
5
6
7
8
exports.handler = async (event) => {
  return {
    statusCode: 200,
    body: JSON.stringify({
      message: "Lambda Post Product!",
    }),
  };
};

serverless.yml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: GET
  • sls deploy --verbose
  • probar endpoint

DynamoDB

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /producto
          method: POST
  • sls deploy --verbose
  • verificar que la tabla fue creada

Crear producto

  • npm i uuid 
  • npm install @aws-sdk/client-dynamodb
  • npm install @aws-sdk/lib-dynamodb
  • Modificar src/handlers/postProduct.mjs 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, PutCommand } from "@aws-sdk/lib-dynamodb";
import { v4 as uuid } from "uuid";

const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event, context) => {
    try {
        const product = JSON.parse(event.body);

        const newProduct = {
            ...product,
            id: uuid(),
            created_at: new Date().toLocaleString('es-SV'),
        };

        const headers = {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        };

        await dynamo.send(new PutCommand({
            TableName: "inventory",
            Item: newProduct,
        }));

        return {
            statusCode: 201,
            headers: headers,
            body: JSON.stringify(newProduct),
        };
    }
    catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: error.message }),
        };
    }
};
  • Modificar serverless.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/inventory"

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: POST
  • sls deploy --verbose
  • probar endpoint en postman e ingresar los datos:
1
2
3
4
5
6
7
{
    "name" :"PS5",
    "brand" : "SONY",
    "price" : 699.99,
    "comments" : "Consola de videojuegos",
    "stock" : 45
}

Listar Productos

  • Crear src/handlers/getProducts.mjs 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, ScanCommand } from "@aws-sdk/lib-dynamodb";

const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event, context) => {
    try {
        let products;

        const result = await dynamo.send(new ScanCommand({
            TableName: "inventory",
        }));

        products = result.Items;

        const headers = {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        };

        return {
            statusCode: 200,
            headers: headers,
            body: JSON.stringify(products),
        };
    }
    catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: error.message }),
        };
    }
};
  • Modificar serverless.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
⁠⁠org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
        - dynamodb:Scan
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/inventory"

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: POST
  getProducts:
    handler: src/handlers/getProducts.handler
    events:
      - httpApi:
          path: /products
          method: GET
  • sls deploy --verbose
  • Probar endpoint en Postman

Obtener Producto

  • Crear src/handlers/getProduct.mjs 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand } from "@aws-sdk/lib-dynamodb";

const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event, context) => {

    let product;
    const { id } = event.pathParameters;
    
    const headers = {
        'Content-Type': 'application/json',
        'Access-Control-Allow-Origin': '*'
    };
    
    try {
        const result = await dynamo.send(new GetCommand({
            TableName: "inventory",
            Key: { id }
        }));

        product = result.Item;
    }
    catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: error.message }),
        };
    }

    if (!product) {
        return {
            statusCode: 404,
            headers: headers,
            body: JSON.stringify({ message: "Producto no encontrado" }),
        };
    }

    return {
        statusCode: 200,
        headers: headers,
        body: JSON.stringify(product),
    };
};
  • Modificar serverless.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
        - dynamodb:Scan
        - dynamodb:GetItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/inventory"

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: POST
  getProducts:
    handler: src/handlers/getProducts.handler
    events:
      - httpApi:
          path: /products
          method: GET
  getProduct:
    handler: src/handlers/getProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: GET
  • sls deploy --verbose
  • Probar endpoint en Postman

Modificar Producto

  • Crear src/handlers/putProduct.mjs 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, GetCommand, UpdateCommand } from "@aws-sdk/lib-dynamodb";

const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event, context) => {
    try {
        const id = event.pathParameters?.id;
        const { name, brand, price, comments, stock } = JSON.parse(event.body);

        // Verificar si el registro existe antes de actualizarlo
        const result = await dynamo.send(new GetCommand({
            TableName: "inventory",
            Key: { id: id },
        }));

        if (!result.Item) {
            return {
                statusCode: 404,
                body: JSON.stringify({ message: "Registro no encontrado" }),
            };
        }

        const headers = {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        };

        // Actualizar el registro existente
        await dynamo.send(new UpdateCommand({
            TableName: "inventory",
            Key: { id: id },
            UpdateExpression: "SET  #name = :name, brand = :brand, price = :price, comments = :comments, stock = :stock",
            ExpressionAttributeNames: {
                "#name": "name",
            },
            ExpressionAttributeValues: {
                ":name": name,
                ":brand": brand,
                ":price": price,
                ":comments": comments,
                ":stock": stock,
            },
            ReturnValues: "ALL_NEW",
        }));
    }

    catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: error.message }),
        };
    }

    return {
        statusCode: 200,
        body: JSON.stringify({ message: "Registro actualizado exitosamente" }),
    };
};
  • Modificar serverless.yml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:UpdateItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/inventory"

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: POST
  getProducts:
    handler: src/handlers/getProducts.handler
    events:
      - httpApi:
          path: /products
          method: GET
  getProduct:
    handler: src/handlers/getProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: GET
  putProduct:
    handler: src/handlers/putProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: PUT
  • sls deploy --verbose
  • Probar endpoint en Postman

Elmininar Producto

  • Crear src/handlers/deleteProduct.mjs 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, DeleteCommand, GetCommand } from "@aws-sdk/lib-dynamodb";

const dynamo = DynamoDBDocumentClient.from(new DynamoDBClient({}));

export const handler = async (event) => {
    try {
        const id = event.pathParameters?.id;

        // Verificar si el registro existe antes de eliminarlo
        const result = await dynamo.send(new GetCommand({
            TableName: "inventory",
            Key: { id: id },
        }));

        if (!result.Item) {
            return {
                statusCode: 404,
                body: JSON.stringify({ message: "Registro no encontrado" }),
            };
        }

        const headers = {
            'Content-Type': 'application/json',
            'Access-Control-Allow-Origin': '*'
        };

        // Eliminar el registro si existe
        await dynamo.send(new DeleteCommand({
            TableName: "inventory",
            Key: { id: id },
        }));

        return {
            statusCode: 200,
            headers: headers,
            body: JSON.stringify({ message: "Registro eliminado exitosamente" }),
        };
    } catch (error) {
        console.log(error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: error.message }),
        };
    }
};
  • Modificar serverless.yml 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
org: jrcoding
service: iac-serverless

provider:
  name: aws
  runtime: nodejs20.x
  region: us-east-1
  memorySize: 128
  stage: ${opt:stage, 'dev'}
  iamRoleStatements:
    - Effect: Allow
      Action:
        - dynamodb:PutItem
        - dynamodb:Scan
        - dynamodb:GetItem
        - dynamodb:UpdateItem
        - dynamodb:DeleteItem
      Resource: "arn:aws:dynamodb:${self:provider.region}:*:table/inventory"

resources:
  Resources:
    InventoryTable:
      Type: AWS::DynamoDB::Table
      Properties:
        TableName: inventory
        AttributeDefinitions:
          - AttributeName: id
            AttributeType: S
        KeySchema:
          - AttributeName: id
            KeyType: HASH
        BillingMode: PAY_PER_REQUEST

functions:
  postProduct:
    handler: src/handlers/postProduct.handler
    events:
      - httpApi:
          path: /product
          method: POST
  getProducts:
    handler: src/handlers/getProducts.handler
    events:
      - httpApi:
          path: /products
          method: GET
  getProduct:
    handler: src/handlers/getProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: GET
  putProduct:
    handler: src/handlers/putProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: PUT
  deleteProduct:
    handler: src/handlers/deleteProduct.handler
    events:
      - httpApi:
          path: /product/{id}
          method: DELETE
  • sls deploy --verbose
  • Probar endpoint en Postman
  • Remover stack de aws con sls remove --verbose
This post is licensed under CC BY 4.0 by the author.