Ce que vous saurez faire à la fin
- Créer une fonction AWS Lambda en Node.js ou Python qui s’exécute sans serveur.
- Exposer cette fonction sur internet via Amazon API Gateway HTTP avec une URL HTTPS.
- Connecter votre API à DynamoDB pour stocker les données (clients, commandes, paiements Wave/Orange Money).
- Sécuriser l’API avec une authentification par JWT ou clé API.
- 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