Donnez de la mémoire à votre API: Créez un CRUD complet avec DynamoDB et CDK

Donnez de la mémoire à votre API: Créez un CRUD complet avec DynamoDB et CDK"

Dans les précedents articles, nous vous avons introduit progressivement au serverless, en passant par la création et le déploiement d'une fontcion lambda pour le premier acticle, l'ajout d'une api Gateway pour le déclenchement de la lambda poue le second article dans les deux cas, les données retounées étaient des simple mocks. vous avez ainsi aquis de bonnes bases pour passer au niveau supperieur : la persistance des données.
Comme énoncer dans le titre nos allons utiliser DynamoDB et npus resterons toujours dans la meme logique la decouverte de aws cdk.

Cette Api englobe les elements essentielle a la creation des applications moderne robuste et scalable.

Prérequis

Avant de de commencer il est souhaitable que :

1. Initialiser le projet CDK

créez un dossier pour votre projet:

BASH
mkdir serverless-rest-api-demo
cd serverless-rest-api-demo
Cliquez pour développer et voir plus

initialisez un projet CDK TypeScript:

BASH
cdk init app --language typescript
Cliquez pour développer et voir plus

CDK crée la structure suivante:

PGSQL
/bin    --------------> point d'entrée
/lib    --------------> définition des stacks
cdk.json -------------> configuration CDK
tsconfig.json
package.json
Cliquez pour développer et voir plus

2. Création de la table DynamoDB

dans notre fichier /lib/serverless-rest-api-demo-stack.ts déclarons notre table Tasks

TS
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';


export class ServerlessRestApiDemoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const tasksTable = new dynamodb.Table(this, 'TasksTable', {
      partitionKey: { name: 'taskId', type: dynamodb.AttributeType.STRING },
      tableName: 'Tasks',
      removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST
    });
  }
}
Cliquez pour développer et voir plus

Dans cette section de notre Stack CDK, nous definissons notre table de stockage. Voici les details de ceque fait chaque ligne de code:

3. Créer les fonctions Lambda

Créer un dossier /lambda/ et les fichers:

SH
npm install @aws-sdk/lib-dynamodb
npm install uuid
Cliquez pour développer et voir plus

1. Création de la tâche

TS
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { PutCommand ,DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb";
import { v4 as uuidv4 } from 'uuid';

const client= new DynamoDBClient({});
const docClient =  DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || 'Tasks';

export const handler = async (event: any) => {
    const requestBody = JSON.parse(event.body);

    const taskItem = {
        taskId: uuidv4(),
        title: requestBody.title,
        taskStatus: requestBody.taskStatus,
        createdAt: new Date().toISOString()
    };

    try {
        await docClient.send(new PutCommand({
            TableName: TABLE_NAME,
            Item: taskItem
        }));

        return {
            statusCode: 201,
            body: JSON.stringify({ message: 'Task created successfully', task: taskItem })
        };
    } catch (error) {
        console.error('Error creating task:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: 'Failed to create task' })
        };
    }
}
Cliquez pour développer et voir plus

2. Liste des tâches

TS
import { DynamoDBClient, ScanCommand } from "@aws-sdk/client-dynamodb";
import {  DynamoDBDocumentClient} from "@aws-sdk/lib-dynamodb";


const client= new DynamoDBClient({});
const docClient =  DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || 'Tasks';

export const handler = async (event: any) => {
   try {
       // Scan the DynamoDB table to get all tasks
       const data= await docClient.send( new ScanCommand({
           TableName: TABLE_NAME
       }));

       return {
           statusCode: 200,
           body: JSON.stringify({ tasks: data.Items })
       };
   } catch (error) {
       console.error('Error listing tasks:', error);
       return {
           statusCode: 500,
           body: JSON.stringify({ message: 'Failed to list tasks' })
       };
   }
}
Cliquez pour développer et voir plus

Explication du code: Lister toutes les tâches Cette fonction agit comme un inventaire de votre base de données:

  1. ScanCommand: C’est l’instruction qui demande à DynamoDB de lire l’intégralité de la table. Cest la méthode la plus direct pour récuperer toutes les donées d’un coup.
  2. data.Items: Le résultat du scan contient untableau nommé Items. C’est là que se trouvent toutes nos tâches ( ID,titre,status etc…)
  3. Gestion d'érreur: Si la table est inaccessible ou si le nom est incorect.

3. Récupération d’une tâche

TS
// code for lambda/get-task.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient ,GetCommand} from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || 'Tasks';

export const handler = async (event: any) => {
    const taskId = event.pathParameters?.taskId;

    if (!taskId) {
        return {
            statusCode: 400,
            body: JSON.stringify({ message: 'Task ID is required' })
        };
    }

    try {
        const data = await docClient.send(new GetCommand({
            TableName: TABLE_NAME,
            Key: { taskId }
        }));

        if (!data.Item) {
            return {
                statusCode: 404,
                body: JSON.stringify({ message: 'Task not found' })
            };
        }

        return {
            statusCode: 200,
            body: JSON.stringify({ task: data.Item })
        };
    } catch (error) {
        console.error('Error getting task:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: 'Failed to get task' })
        };
    }
}
Cliquez pour développer et voir plus

Cette fonction permet de récuperer un element precis de notre BD.

3. Suppression d’une tâche

TS
// code for lambda/delete-task.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, DeleteCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || 'Tasks';

export const handler = async (event: any) => {
    const taskId = event.pathParameters?.taskId;

    if (!taskId) {
        return {
            statusCode: 400,
            body: JSON.stringify({ message: 'Task ID is required' })
        };
    }

    try {
        await docClient.send(new DeleteCommand({
            TableName: TABLE_NAME,
            Key: { taskId }
        }));

        return {
            statusCode: 200,
            body: JSON.stringify({ message: 'Task deleted successfully' })
        };
    } catch (error) {
        console.error('Error deleting task:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: 'Failed to delete task' })
        };
    }
}
Cliquez pour développer et voir plus

Explication La suppression est l’action finale de notre cycle de données. Elle ressemble énormément à la récupération (Get), mais avec une intention differente.

4. Mise à jour Dynamique

TS
// code for lambda/update-task.ts
import { DynamoDBClient } from "@aws-sdk/client-dynamodb";
import { DynamoDBDocumentClient, UpdateCommand } from "@aws-sdk/lib-dynamodb";

const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
const TABLE_NAME = process.env.TABLE_NAME || 'Tasks';

export const handler = async (event: any) => {
    const taskId = event.pathParameters?.taskId;
    const requestBody = JSON.parse(event.body);

    if (!taskId) {
        return {
            statusCode: 400,
            body: JSON.stringify({ message: 'Task ID is required' })
        };
    }

    const updateExpressionParts = [];
    const expressionAttributeValues: any = {};

    if (requestBody.title) {
        updateExpressionParts.push('title = :title');
        expressionAttributeValues[':title'] = requestBody.title;
    }

    if (requestBody.taskStatus) {
        updateExpressionParts.push('taskStatus = :taskStatus');
        expressionAttributeValues[':taskStatus'] = requestBody.taskStatus;
    }

    if (updateExpressionParts.length === 0) {
        return {
            statusCode: 400,
            body: JSON.stringify({ message: 'No valid fields to update' })
        };
    }

    const updateExpression = 'SET ' + updateExpressionParts.join(', ');

    try {
        const data = await docClient.send(new UpdateCommand({
            TableName: TABLE_NAME,
            Key: { taskId },
            UpdateExpression: updateExpression,
            ExpressionAttributeValues: expressionAttributeValues,
            ReturnValues: 'ALL_NEW'
        }));

        return {
            statusCode: 200,
            body: JSON.stringify({ message: 'Task updated successfully', task: data.Attributes })
        };
    } catch (error) {
        console.error('Error updating task:', error);
        return {
            statusCode: 500,
            body: JSON.stringify({ message: 'Failed to update task' })
        };
    }
}   
Cliquez pour développer et voir plus

Explication Contrairement au PutCommand qui remplace tout l’objet, l’UpdateCommand permet de ne modifer que les champs envoyés par les utilisateurs (le titre,le status,ou les deux).

4. Création des routes

Dans cette section nous allons créer notre Api Gateway puis associer chaque lambda à une route precise.

TS
import * as cdk from 'aws-cdk-lib/core';
import { Construct } from 'constructs';
import * as dynamodb from 'aws-cdk-lib/aws-dynamodb';
import * as apiGateway from 'aws-cdk-lib/aws-apigateway';
import* as lambdaNodejs from 'aws-cdk-lib/aws-lambda-nodejs';

export class ServerlessRestApiDemoStack extends cdk.Stack {
  constructor(scope: Construct, id: string, props?: cdk.StackProps) {
    super(scope, id, props);

    const tasksTable = new dynamodb.Table(this, 'TasksTable', {
      partitionKey: { name: 'taskId', type: dynamodb.AttributeType.STRING },
      tableName: 'Tasks',
      removalPolicy: cdk.RemovalPolicy.DESTROY, // NOT recommended for production code
      billingMode: dynamodb.BillingMode.PAY_PER_REQUEST
    });
    
    // Lambda function to create a task
    const createTaskFunction = new lambdaNodejs.NodejsFunction(this, 'CreateTaskFunction', {
      entry: 'lambda/create-task.ts',
      handler: 'handler',
      functionName: 'CreateTaskFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });

    // Grant the Lambda function permissions to write to the DynamoDB table
    tasksTable.grantWriteData(createTaskFunction);
    
    // Lambda function to list tasks
    const listTasksFunction = new lambdaNodejs.NodejsFunction(this, 'ListTasksFunction', {
      entry: 'lambda/list-tasks.ts',
      handler: 'handler',
      functionName: 'ListTasksFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });

    // Grant the Lambda function permissions to read from the DynamoDB table
    tasksTable.grantReadData(listTasksFunction);

    // Lambda function to get a single task
    const getTaskFunction = new lambdaNodejs.NodejsFunction(this, 'GetTaskFunction', {
      entry: 'lambda/get-task.ts',
      handler: 'handler',
      functionName: 'GetTaskFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });

    // Grant the Lambda function permissions to read from the DynamoDB table
    tasksTable.grantReadData(getTaskFunction);

    // Lambda function to delete a task
    const deleteTaskFunction = new lambdaNodejs.NodejsFunction(this, 'DeleteTaskFunction', {
      entry: 'lambda/delete-task.ts',
      handler: 'handler',
      functionName: 'DeleteTaskFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });

    // Grant the Lambda function permissions to delete from the DynamoDB table
    tasksTable.grantWriteData(deleteTaskFunction);

    // Lambda function to update a task
    const updateTaskFunction = new lambdaNodejs.NodejsFunction(this, 'UpdateTaskFunction', {
      entry: 'lambda/update-task.ts',
      handler: 'handler',
      functionName: 'UpdateTaskFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });
    
    // Grant the Lambda function permissions to update the DynamoDB table
    tasksTable.grantWriteData(updateTaskFunction);

    // create Api Gateway and Lambda functions here
    const api = new apiGateway.RestApi(this, 'TasksApi', {
      restApiName: 'Tasks Service',
      description: 'This service serves tasks.'
    });
    // create tasks resource
    const tasks = api.root.addResource('tasks');

    // POST /tasks - create a new task
    tasks.addMethod('POST', new apiGateway.LambdaIntegration(createTaskFunction));

    // GET /tasks - list all tasks
    tasks.addMethod('GET', new apiGateway.LambdaIntegration(listTasksFunction));

    // /tasks/{taskId} resource
    const singleTask = tasks.addResource('{taskId}');

    // GET /tasks/{taskId} - get a single task
    singleTask.addMethod('GET', new apiGateway.LambdaIntegration(getTaskFunction));

    // DELETE /tasks/{taskId} - delete a task
    singleTask.addMethod('DELETE', new apiGateway.LambdaIntegration(deleteTaskFunction));

    // PUT /tasks/{taskId} - update a task
    singleTask.addMethod('PUT', new apiGateway.LambdaIntegration(updateTaskFunction));

    // Output the API endpoint URL
    new cdk.CfnOutput(this, 'ApiUrl', {
      value: api.url,
      description: 'The URL of the Tasks API'
    });
  }
}
Cliquez pour développer et voir plus

Explications: Comme nouvauté ici nous avons

TS
 // Lambda function to create a task
    const createTaskFunction = new lambdaNodejs.NodejsFunction(this, 'CreateTaskFunction', {
      entry: 'lambda/create-task.ts',
      handler: 'handler',
      functionName: 'CreateTaskFunction',
      environment: {
        TABLE_NAME: tasksTable.tableName
      }
    });

    // Grant the Lambda function permissions to write to the DynamoDB table
    tasksTable.grantWriteData(createTaskFunction);
Cliquez pour développer et voir plus

En resumé

5. Déployer la Lambda

avant de deployer:

SH
cdk bootstrap
cdk synth   //pour générer le template Cloudformation du projet

cdk deploy 
Cliquez pour développer et voir plus

5. Tester les fonctions

Apres le deploiement dans la console on a lien pour appeler les fonctions lambda.

déployment

Se rendre dans la console aws pour decouvrir la ressource API Gateway

Api gateway

Test Via les outils comme Postman

Api gateway Get Task

6. Nettoyer

SH
cdk destroy
Cliquez pour développer et voir plus

cela supprime les fontions lambda les ressource Api Gateway et la table DYnamoDB.

La prochaine etape de notre parcour sera le déploiement d’un site statique. Comme nous avons crée une api Rest, il est temps de déployer une application Angular ou React pour pouvoir interagir avec notre API Rest.

Commentaires

Commencer la recherche

Saisissez des mots-clés pour rechercher des articles

↑↓
ESC
⌘K Raccourci