ITSkillsCenter
Blog

AWS Lambda + API Gateway : serverless API

8 min de lecture

Ce que vous saurez faire à la fin

  1. Créer une fonction AWS Lambda en Node.js ou Python qui s’exécute sans serveur.
  2. Exposer cette fonction sur internet via Amazon API Gateway HTTP avec une URL HTTPS.
  3. Connecter votre API à DynamoDB pour stocker les données (clients, commandes, paiements Wave/Orange Money).
  4. Sécuriser l’API avec une authentification par JWT ou clé API.
  5. Déployer le tout en infrastructure-as-code via SAM ou Terraform et observer les coûts (souvent 0 FCFA/mois sous 1 million de requêtes).

Durée : 5h. Pré-requis : Compte AWS avec carte bancaire, AWS CLI v2 configurée (région eu-west-3 recommandée), AWS SAM CLI installée (gratuite), Node.js 20 ou Python 3.12 sur votre poste, un éditeur de code (VS Code), connaissance basique de REST et JSON.

Étape 1 — Comprendre l’architecture serverless

AWS Lambda exécute votre code sur des micro-VMs Firecracker démarrées à la demande. Vous payez uniquement le temps d’exécution réel (par tranche de 1 ms) et la mémoire allouée. API Gateway transforme une requête HTTPS en événement JSON envoyé à Lambda, puis renvoie la réponse au client.

Pour une PME sénégalaise, l’intérêt est immédiat : pas de serveur à provisionner, pas de patch sécurité à appliquer, scalabilité automatique de 0 à des milliers de requêtes par seconde, et facturation basée sur l’usage réel. Une API recevant 50 000 requêtes/mois coûte environ 0 FCFA grâce au free tier permanent (1 million de requêtes Lambda gratuites par mois).

Étape 2 — Installer AWS SAM CLI

SAM (Serverless Application Model) est une extension de CloudFormation qui simplifie la création de Lambdas et API Gateway.

# macOS
brew install aws-sam-cli

# Linux
wget https://github.com/aws/aws-sam-cli/releases/latest/download/aws-sam-cli-linux-x86_64.zip
unzip aws-sam-cli-linux-x86_64.zip -d sam-installation
sudo ./sam-installation/install

# Windows
choco install aws-sam-cli

# Vérifier
sam --version
# SAM CLI, version 1.115.0

aws --version

Étape 3 — Initialiser un projet SAM

sam init \
    --runtime nodejs20.x \
    --architecture x86_64 \
    --app-template hello-world \
    --name api-pme-dakar \
    --no-tracing

cd api-pme-dakar

# Structure générée :
# api-pme-dakar/
#   hello-world/         # code Lambda
#     app.js
#     package.json
#   template.yaml        # définition SAM
#   events/              # événements de test

Étape 4 — Écrire la première fonction Lambda

// hello-world/app.js
exports.handler = async (event) => {
  const method = event.requestContext?.http?.method || 'GET';
  const path = event.rawPath || '/';

  console.log(`Requête ${method} ${path}`);

  return {
    statusCode: 200,
    headers: {
      'Content-Type': 'application/json',
      'Access-Control-Allow-Origin': '*'
    },
    body: JSON.stringify({
      message: 'API PME Dakar opérationnelle',
      method: method,
      path: path,
      timestamp: new Date().toISOString(),
      currency: 'XOF'
    })
  };
};

Étape 5 — Décrire l’infrastructure dans template.yaml

AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: API serverless pour PME Dakar

Globals:
  Function:
    Timeout: 10
    MemorySize: 256
    Runtime: nodejs20.x
    Environment:
      Variables:
        REGION: !Ref AWS::Region
        ENV: production

Resources:
  ApiPmeFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: hello-world/
      Handler: app.handler
      Events:
        ApiHello:
          Type: HttpApi
          Properties:
            Path: /hello
            Method: GET
        ApiCatchAll:
          Type: HttpApi
          Properties:
            Path: /{proxy+}
            Method: ANY

Outputs:
  ApiUrl:
    Description: URL HTTPS publique de l'API
    Value: !Sub https://${ServerlessHttpApi}.execute-api.${AWS::Region}.amazonaws.com/

Étape 6 — Tester localement avant déploiement

# Lancer un serveur API local
sam local start-api --port 3000

# Dans un autre terminal
curl http://localhost:3000/hello

# Invoquer une seule fonction avec un événement
sam local invoke ApiPmeFunction --event events/event.json

# Construire (transpile, installe deps)
sam build

Étape 7 — Déployer sur AWS

# Premier déploiement guidé (interactif)
sam deploy --guided

# Stack Name [api-pme-dakar]: api-pme-dakar
# AWS Region [eu-west-3]: eu-west-3
# Confirm changes before deploy [y/N]: y
# Allow SAM CLI IAM role creation [Y/n]: y
# Save arguments to samconfig.toml [Y/n]: y

# Déploiements suivants (non-interactifs)
sam deploy

# Récupérer l'URL publique
aws cloudformation describe-stacks \
    --stack-name api-pme-dakar \
    --query "Stacks[0].Outputs[?OutputKey=='ApiUrl'].OutputValue" \
    --output text

# Tester
curl https://abc123def.execute-api.eu-west-3.amazonaws.com/hello

Étape 8 — Ajouter une table DynamoDB pour stocker les clients

# Compléter template.yaml
Resources:
  ClientsTable:
    Type: AWS::DynamoDB::Table
    Properties:
      TableName: pme-clients
      BillingMode: PAY_PER_REQUEST
      AttributeDefinitions:
        - AttributeName: clientId
          AttributeType: S
      KeySchema:
        - AttributeName: clientId
          KeyType: HASH
      PointInTimeRecoverySpecification:
        PointInTimeRecoveryEnabled: true

  ApiPmeFunction:
    Type: AWS::Serverless::Function
    Properties:
      # ... config existante
      Policies:
        - DynamoDBCrudPolicy:
            TableName: !Ref ClientsTable
      Environment:
        Variables:
          CLIENTS_TABLE: !Ref ClientsTable

Étape 9 — Implémenter les opérations CRUD

// hello-world/app.js
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const {
  DynamoDBDocumentClient, PutCommand, GetCommand,
  ScanCommand, DeleteCommand
} = require('@aws-sdk/lib-dynamodb');
const { randomUUID } = require('crypto');

const ddb = DynamoDBDocumentClient.from(new DynamoDBClient({}));
const TABLE = process.env.CLIENTS_TABLE;

exports.handler = async (event) => {
  const { method } = event.requestContext.http;
  const path = event.rawPath;

  try {
    if (method === 'POST' && path === '/clients') {
      const body = JSON.parse(event.body);
      const item = {
        clientId: randomUUID(),
        nom: body.nom,
        telephone: body.telephone,
        ville: body.ville || 'Dakar',
        createdAt: new Date().toISOString()
      };
      await ddb.send(new PutCommand({ TableName: TABLE, Item: item }));
      return response(201, item);
    }

    if (method === 'GET' && path === '/clients') {
      const result = await ddb.send(new ScanCommand({ TableName: TABLE }));
      return response(200, result.Items);
    }

    if (method === 'GET' && path.startsWith('/clients/')) {
      const id = path.split('/')[2];
      const result = await ddb.send(new GetCommand({
        TableName: TABLE, Key: { clientId: id }
      }));
      return response(result.Item ? 200 : 404, result.Item || { error: 'Not found' });
    }

    return response(404, { error: 'Route inconnue' });
  } catch (e) {
    console.error(e);
    return response(500, { error: e.message });
  }
};

const response = (code, body) => ({
  statusCode: code,
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify(body)
});

Étape 10 — Sécuriser avec une clé API

# template.yaml
Resources:
  ApiAuthorizer:
    Type: AWS::Serverless::HttpApi
    Properties:
      Auth:
        DefaultAuthorizer: ApiKeyAuth
        Authorizers:
          ApiKeyAuth:
            FunctionArn: !GetAtt AuthFunction.Arn
            Identity:
              Headers:
                - x-api-key

  AuthFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: auth/
      Handler: auth.handler
// auth/auth.js
exports.handler = async (event) => {
  const apiKey = event.headers['x-api-key'];
  const validKey = process.env.API_KEY; // stocké dans Secrets Manager

  return {
    isAuthorized: apiKey === validKey,
    context: { source: 'pme-api' }
  };
};

Étape 11 — Stocker la clé API dans Secrets Manager

# Générer une clé robuste
API_KEY=$(openssl rand -hex 32)
echo "Clé générée : $API_KEY"

# Stocker dans Secrets Manager
aws secretsmanager create-secret \
    --name pme-api-key \
    --secret-string "$API_KEY" \
    --region eu-west-3

# Récupérer la valeur (jamais en logs en prod)
aws secretsmanager get-secret-value \
    --secret-id pme-api-key \
    --query SecretString \
    --output text

Étape 12 — Configurer un domaine personnalisé

# Demander un certificat ACM dans la même région que API Gateway
aws acm request-certificate \
    --domain-name api.monentreprise.sn \
    --validation-method DNS \
    --region eu-west-3

# Créer le mapping de domaine
aws apigatewayv2 create-domain-name \
    --domain-name api.monentreprise.sn \
    --domain-name-configurations CertificateArn=arn:aws:acm:...

# Lier l'API au domaine
aws apigatewayv2 create-api-mapping \
    --domain-name api.monentreprise.sn \
    --api-id abc123def \
    --stage '$default'

Ajoutez ensuite un CNAME chez votre registrar pointant api.monentreprise.sn vers le DNS régional retourné par API Gateway.

Étape 13 — Surveiller logs et métriques CloudWatch

# Suivre les logs en temps réel
sam logs --name ApiPmeFunction --tail

# Filtrer les erreurs
aws logs filter-log-events \
    --log-group-name /aws/lambda/api-pme-dakar-ApiPmeFunction-XXX \
    --filter-pattern "ERROR" \
    --start-time $(($(date +%s) - 3600))000

# Métriques utiles
aws cloudwatch get-metric-statistics \
    --namespace AWS/Lambda \
    --metric-name Duration \
    --dimensions Name=FunctionName,Value=ApiPmeFunction \
    --start-time 2026-04-22T00:00:00Z \
    --end-time 2026-04-23T00:00:00Z \
    --period 3600 \
    --statistics Average,Maximum

Étape 14 — Estimer le coût mensuel

Volume Lambda API Gateway HTTP DynamoDB Total estimé
50 000 req/mois 0 FCFA 0 FCFA (free tier) 0 FCFA 0 FCFA
500 000 req/mois 0 FCFA ~150 FCFA ~300 FCFA ~450 FCFA
5 millions req/mois ~600 FCFA ~3 000 FCFA ~3 500 FCFA ~7 100 FCFA
50 millions req/mois ~12 000 FCFA ~30 000 FCFA ~35 000 FCFA ~77 000 FCFA

Activez un budget AWS avec alerte par email à 5 000 FCFA pour rester maître des coûts. Pour une PME, vous resterez généralement dans le free tier ou en dessous de 10 000 FCFA/mois.

Erreurs courantes à éviter

  • Cold start de 2-3 secondes : packagez votre code minimaliste, évitez les SDK lourds. Pour une API critique, activez Provisioned Concurrency (compte 6 000 FCFA/mois pour 1 instance prête en permanence).
  • Timeout 3 secondes par défaut : passez à 10 ou 30 secondes via Globals dans template.yaml selon le travail à effectuer.
  • Memory trop faible : 128 Mo est lent. 256 ou 512 Mo donne souvent le meilleur rapport perf/coût (CPU proportionnel à la mémoire).
  • Permissions IAM manquantes : Lambda ne peut pas écrire dans DynamoDB sans la policy adéquate. Utilisez les policies SAM (DynamoDBCrudPolicy, S3ReadPolicy…).
  • Logs CloudWatch qui explosent : configurez la rétention à 7 ou 14 jours, sinon la facture log dépasse celle des Lambdas. aws logs put-retention-policy --log-group-name /aws/lambda/X --retention-in-days 7.
  • CORS oublié : ajoutez les en-têtes Access-Control-Allow-Origin dans toutes les réponses ou configurez-les au niveau API Gateway.
  • Clé API en clair dans le code : toujours via Secrets Manager ou Parameter Store, jamais commitée dans Git.

Checklist de mise en production

  • [ ] AWS SAM CLI installée et configurée
  • [ ] Compte AWS configuré en eu-west-3 ou eu-west-1
  • [ ] Code Lambda testé localement avec sam local
  • [ ] Template SAM versionné dans Git
  • [ ] DynamoDB en mode PAY_PER_REQUEST avec PITR activé
  • [ ] IAM policies minimales (principe du moindre privilège)
  • [ ] Authentification par clé API ou JWT en place
  • [ ] Secrets stockés dans Secrets Manager
  • [ ] Rétention des logs CloudWatch configurée (7-14 jours)
  • [ ] Domaine personnalisé HTTPS lié via ACM + Route 53
  • [ ] Alertes CloudWatch sur erreurs 5xx et latence P99
  • [ ] Budget AWS avec notification email à 5 000 FCFA
  • [ ] CORS testé depuis le frontend (navigation privée)
  • [ ] Documentation Postman / OpenAPI partagée avec l’équipe
Besoin d'un site web ?

Confiez-nous la Création de Votre Site Web

Site vitrine, e-commerce ou application web — nous transformons votre vision en réalité digitale. Accompagnement personnalisé de A à Z.

À partir de 250.000 FCFA
Parlons de Votre Projet
Publicité