Architecture Serverless en 2025 : Pourquoi Votre Prochaine API Devrait Etre Serverless
Salut HaWkers, vous maintenez encore des serveurs 24h/24 en attente de requetes ? Vous payez pour une capacite que vous n'utilisez pas 90% du temps ? Vous passez des nuits a configurer l'auto-scaling ?
Serverless a change la donne. En 2025, la question n'est plus "dois-je utiliser serverless ?", mais plutot "pourquoi ne l'utilise-je pas encore ?". Je vais vous montrer exactement comment ca fonctionne, quand l'utiliser, et du code reel de production.
Qu'est-ce que Serverless (Vraiment) ?
Serverless NE signifie PAS "sans serveurs". Cela signifie que vous ne gerez pas de serveurs.
Modele traditionnel :
- Vous provisionnez EC2/VPS
- Installez Node.js, PM2, nginx
- Configurez load balancer, auto-scaling
- Surveillez l'utilisation CPU/RAM
- Payez 24h/24, meme sans trafic
Modele serverless :
- Vous ecrivez une fonction
- Deploy avec une commande
- La plateforme gere tout
- Scale automatiquement (0 a des millions de requetes)
- Payez uniquement par execution reelle
Principales Plateformes en 2025
1. AWS Lambda (Le Geant)
- Plus grand ecosysteme (integration avec 200+ services AWS)
- Supporte Node.js, Python, Go, Java, .NET, Rust
- Cold start : ~100-200ms (beaucoup ameliore)
- Tarification : 0,20$ par 1M requetes + temps de calcul
2. Vercel Functions (Le Dev-Friendly)
- Deploy integre avec Git
- Reseau edge global
- Parfait pour Next.js/React
- Cold start : ~50ms
- Tarification : 100k invocations gratuites, puis 0,40$/1M
3. Cloudflare Workers (Le Plus Rapide)
- Edge computing (tourne dans 300+ villes)
- Cold start : ~0ms (toujours "chaud")
- V8 isolates (pas de containers)
- Tarification : 100k/jour gratuit, puis 0,50$/1M
4. Netlify Functions (Le Simple)
- Integration parfaite avec JAMstack
- Deploy automatique
- Bon pour les startups
- Tarification : 125k/mois gratuit
Votre Premiere Function : Hello World Reel
AWS Lambda + API Gateway :
// handler.js
exports.handler = async (event) => {
const { name = 'World' } = event.queryStringParameters || {};
return {
statusCode: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify({
message: `Hello, ${name}!`,
timestamp: new Date().toISOString(),
requestId: event.requestContext.requestId
})
};
};Deploy avec Serverless Framework :
# serverless.yml
service: hello-world-api
provider:
name: aws
runtime: nodejs20.x
region: us-east-1
functions:
hello:
handler: handler.handler
events:
- httpApi:
path: /hello
method: get
# Deploy avec une commande
# serverless deployResultat : API fonctionnant a https://xxxxxxx.execute-api.us-east-1.amazonaws.com/hello?name=Jeff

Cas Reel : API d'E-commerce
Construisons une API complete de produits avec CRUD, validation et cache.
// api/products/index.js
import { DynamoDBClient } from '@aws-sdk/client-dynamodb';
import { DynamoDBDocumentClient, ScanCommand } from '@aws-sdk/lib-dynamodb';
const client = new DynamoDBClient({});
const docClient = DynamoDBDocumentClient.from(client);
export const handler = async (event) => {
const { httpMethod, pathParameters, body } = event;
try {
switch (httpMethod) {
case 'GET':
return pathParameters?.id
? await getProduct(pathParameters.id)
: await listProducts();
case 'POST':
return await createProduct(JSON.parse(body));
case 'PUT':
return await updateProduct(pathParameters.id, JSON.parse(body));
case 'DELETE':
return await deleteProduct(pathParameters.id);
default:
return response(405, { error: 'Method not allowed' });
}
} catch (error) {
console.error('Error:', error);
return response(500, { error: error.message });
}
};
// Helper : Reponse standardisee
function response(statusCode, data) {
return {
statusCode,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*'
},
body: JSON.stringify(data)
};
}
// GET /products
async function listProducts() {
const command = new ScanCommand({
TableName: process.env.PRODUCTS_TABLE
});
const result = await docClient.send(command);
return response(200, {
products: result.Items,
count: result.Count
});
}
// GET /products/:id
async function getProduct(id) {
const { GetCommand } = await import('@aws-sdk/lib-dynamodb');
const command = new GetCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id }
});
const result = await docClient.send(command);
if (!result.Item) {
return response(404, { error: 'Product not found' });
}
return response(200, result.Item);
}
// POST /products
async function createProduct(data) {
const { PutCommand } = await import('@aws-sdk/lib-dynamodb');
// Validation
const errors = validateProduct(data);
if (errors.length > 0) {
return response(400, { errors });
}
const product = {
id: crypto.randomUUID(),
...data,
createdAt: new Date().toISOString(),
updatedAt: new Date().toISOString()
};
const command = new PutCommand({
TableName: process.env.PRODUCTS_TABLE,
Item: product
});
await docClient.send(command);
return response(201, product);
}
// PUT /products/:id
async function updateProduct(id, data) {
const { UpdateCommand } = await import('@aws-sdk/lib-dynamodb');
const errors = validateProduct(data, true);
if (errors.length > 0) {
return response(400, { errors });
}
// Construire l'expression d'update dynamiquement
const updateExpressions = [];
const expressionAttributeNames = {};
const expressionAttributeValues = {};
Object.keys(data).forEach((key, index) => {
updateExpressions.push(`#field${index} = :value${index}`);
expressionAttributeNames[`#field${index}`] = key;
expressionAttributeValues[`:value${index}`] = data[key];
});
updateExpressions.push('#updatedAt = :updatedAt');
expressionAttributeNames['#updatedAt'] = 'updatedAt';
expressionAttributeValues[':updatedAt'] = new Date().toISOString();
const command = new UpdateCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id },
UpdateExpression: `SET ${updateExpressions.join(', ')}`,
ExpressionAttributeNames: expressionAttributeNames,
ExpressionAttributeValues: expressionAttributeValues,
ReturnValues: 'ALL_NEW'
});
const result = await docClient.send(command);
return response(200, result.Attributes);
}
// DELETE /products/:id
async function deleteProduct(id) {
const { DeleteCommand } = await import('@aws-sdk/lib-dynamodb');
const command = new DeleteCommand({
TableName: process.env.PRODUCTS_TABLE,
Key: { id }
});
await docClient.send(command);
return response(204, null);
}
// Validation de produit
function validateProduct(data, isUpdate = false) {
const errors = [];
if (!isUpdate && !data.name) {
errors.push('Name is required');
}
if (data.name && data.name.length < 3) {
errors.push('Name must be at least 3 characters');
}
if (!isUpdate && data.price === undefined) {
errors.push('Price is required');
}
if (data.price !== undefined && (data.price < 0 || isNaN(data.price))) {
errors.push('Price must be a positive number');
}
return errors;
}
Optimisations Essentielles
1. Reduire les Cold Starts :
// ❌ Mauvais : Import dans la fonction
export const handler = async () => {
const AWS = require('aws-sdk'); // Import a chaque execution
const db = new AWS.DynamoDB.DocumentClient();
// ...
};
// ✅ Bon : Import en haut (reutilise)
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
const docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
export const handler = async () => {
// docClient deja instancie
};
// ✅ Encore mieux : Lazy loading
let docClient;
function getDocClient() {
if (!docClient) {
const { DynamoDBClient } = require('@aws-sdk/client-dynamodb');
const { DynamoDBDocumentClient } = require('@aws-sdk/lib-dynamodb');
docClient = DynamoDBDocumentClient.from(new DynamoDBClient({}));
}
return docClient;
}2. Provisioned Concurrency (Pour APIs critiques) :
# serverless.yml
functions:
api:
handler: handler.handler
provisionedConcurrency: 2 # Toujours 2 instances "chaudes"
reservedConcurrency: 100 # Limite la concurrence max3. Caching Intelligent :
// Cache en memoire (persiste entre invocations de la meme instance)
let cachedProducts = null;
let cacheExpiry = 0;
async function getProductsCached() {
const now = Date.now();
if (cachedProducts && now < cacheExpiry) {
console.log('Cache hit!');
return cachedProducts;
}
console.log('Cache miss, fetching...');
const products = await fetchProductsFromDB();
cachedProducts = products;
cacheExpiry = now + (5 * 60 * 1000); // Cache 5 minutes
return products;
}4. Bundle Size Optimise :
// ❌ Mauvais : Import complet
const _ = require('lodash');
// ✅ Bon : Import specifique
const debounce = require('lodash/debounce');
// ✅ Encore mieux : Tree-shaking avec ES modules
import { debounce } from 'lodash-es';
Integrations Puissantes
1. S3 Triggers (Traiter les uploads) :
// Redimensionner images automatiquement
export const handler = async (event) => {
const s3 = new S3Client({});
const sharp = require('sharp');
for (const record of event.Records) {
const bucket = record.s3.bucket.name;
const key = record.s3.object.key;
// Telecharger l'image
const { Body } = await s3.send(new GetObjectCommand({
Bucket: bucket,
Key: key
}));
const buffer = await streamToBuffer(Body);
// Redimensionner
const resized = await sharp(buffer)
.resize(800, 600, { fit: 'inside' })
.jpeg({ quality: 80 })
.toBuffer();
// Upload de la version redimensionnee
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: `thumbnails/${key}`,
Body: resized,
ContentType: 'image/jpeg'
}));
console.log(`✓ Resized ${key}`);
}
};2. EventBridge Scheduled (Cron jobs) :
functions:
sendDailyReport:
handler: handlers/reports.daily
events:
- schedule:
rate: cron(0 9 * * ? *) # Tous les jours a 9h UTC
enabled: true// handlers/reports.js
export const daily = async () => {
const users = await fetchActiveUsers();
const report = generateReport(users);
await sendEmail({
to: 'admin@example.com',
subject: 'Daily Report',
body: report
});
console.log(`✓ Sent report to ${users.length} users`);
};3. SQS Queues (Traitement asynchrone) :
// Producer : Envoie messages a la file
export const createOrder = async (event) => {
const sqs = new SQSClient({});
const order = JSON.parse(event.body);
await sqs.send(new SendMessageCommand({
QueueUrl: process.env.ORDERS_QUEUE_URL,
MessageBody: JSON.stringify(order)
}));
return response(202, { message: 'Order queued for processing' });
};
// Consumer : Traite messages de la file
export const processOrder = async (event) => {
for (const record of event.Records) {
const order = JSON.parse(record.body);
try {
await processPayment(order);
await sendConfirmationEmail(order);
await updateInventory(order);
console.log(`✓ Order ${order.id} processed`);
} catch (error) {
console.error(`✗ Order ${order.id} failed:`, error);
// Message retourne dans la file (retry automatique)
throw error;
}
}
};Couts Reels : Comparaison
Scenario : API avec 1M requetes/mois, 200ms en moyenne
| Solution | Cout/mois | Details |
|---|---|---|
| EC2 t3.small (24/7) | 17$ | + 5$ Load Balancer = 22$ |
| AWS Lambda | 0,40$ | 0,20$ req + 0,20$ compute |
| Vercel Functions | 0,40$ | Apres 100k gratuit |
| Cloudflare Workers | 5$ | Plan paye (5$/mois) |
Avantage serverless : Economise 80-90% sur trafic faible/moyen.
Quand serverless devient cher :
- Trafic constant eleve (>10M req/mois)
- Fonctions longue duree (>15min)
Quand NE PAS Utiliser Serverless
Evitez pour :
- WebSockets persistants (utilisez EC2/ECS)
- Traitement lourd (ML training, video encoding)
- Latence critique (<10ms consistant)
- Etat persistant en memoire (caches grands)
Alternative hybride :
// API serverless + Worker traditionnel
// Lambda pour endpoints
export const api = async (event) => {
// Traite requete legere
// Pour taches lourdes, envoie a la file
await sqs.sendMessage({
queueUrl: WORKER_QUEUE,
body: JSON.stringify({ task: 'heavy-compute' })
});
};
// EC2 worker traite la file
// Tourne 24/7, optimise pour taches lourdesL'Avenir du Serverless
Tendances 2025 :
- Edge computing (Cloudflare Workers, Vercel Edge)
- Serverless containers (AWS Fargate, Cloud Run)
- Streaming responses (donnees progressives)
- IA integree (embeddings, summarization)
Pour mieux comprendre les patterns asynchrones essentiels en serverless, consultez Decouvrir la Puissance d'Async/Await en JavaScript.

