Skip to content

Type de ressource : Cloudflare Worker (Microservice FaaS / Queue Consumer)
Fichier source : services/submission-processor/src/index.ts
Statut : Actif

Worker Cloudflare qui consomme les messages de la Queue submissions-queue et crée des documents dans Sanity. Traite les soumissions de formulaire de maniÚre asynchrone via un Queue consumer. Inclut un endpoint de test direct (/test-direct) pour le développement local.

  • Consommer les soumissions de formulaire depuis la Queue Cloudflare
  • CrĂ©er des documents Sanity pour chaque soumission
  • Fournir un endpoint de test direct pour le dĂ©veloppement local
  • GĂ©rer les erreurs avec retry exponentiel
Propriété Valeur
Type Microservice métier / Queue Consumer
Trigger Queue consumer, HTTP fetch
Route publique /health, /test-direct
Environnements Development, Production
Consommateurs Form-handler (producer), Sanity CMS
[Entrée : Queue Cloudflare]
│ ‱ Message de soumission
▌
[Submission-processor Worker]
│ ‱ Queue consumer
│ ‱ Initialisation du client Sanity
│ ‱ CrĂ©ation du document Sanity
▌
[Sanity CMS]
│ ‱ Document submission créé
▌
[Confirmation (ack/retry)]
[Client]
│
▌
[Astro Frontend]
│
▌
[Form Handler Worker]
│ ‱ Producer Queue
▌
[Queue Cloudflare]
│
▌
[Submission-processor Worker]
│ ‱ Consumer Queue
▌
[Sanity CMS]
Composant RĂŽle Type
SUBMISSIONS_QUEUE Queue asynchrone Cloudflare Binding
Sanity Client Client API Sanity BibliothĂšque (@sanity/client)
Sanity API Stockage des soumissions Service tiers
Form-handler Producer de la Queue Cloudflare Worker
Variable Description Obligatoire Valeur par défaut
ENVIRONMENT Environnement (dev/production) ✅ dev
SANITY_PROJECT_ID ID du projet Sanity ✅ InjectĂ© via secret
SANITY_DATASET Nom du dataset Sanity ✅ InjectĂ© via secret
SANITY_API_WRITE_TOKEN Token d’écriture Sanity ✅ InjectĂ© via secret
Binding Type Nom Description
SUBMISSIONS_QUEUE Queue Consumer SUBMISSIONS_QUEUE Queue pour les soumissions
services/submission-processor/wrangler.toml
name = "amn-submission-processor"
main = "src/index.ts"
compatibility_date = "2025-07-18"
compatibility_flags = ["nodejs_compat"]
[vars]
ENVIRONMENT = "dev"
[env.dev]
name = "amn-submission-processor-dev"
vars = { ENVIRONMENT = "dev" }
[[env.dev.queues.consumers]]
queue = "submissions-queue-dev"
max_batch_size = 10
max_batch_timeout = 30
[env.production]
name = "amn-submission-processor-prod"
vars = { ENVIRONMENT = "production" }
[[env.production.queues.consumers]]
queue = "submissions-queue-prod"
max_batch_size = 10
max_batch_timeout = 30
Terminal window
# Injection des secrets via Wrangler CLI
wrangler secret put SANITY_PROJECT_ID --env production
wrangler secret put SANITY_DATASET --env production
wrangler secret put SANITY_API_WRITE_TOKEN --env production

ou via le script @inject_secret_to_cloudflare.sh du projet.

import { createClient } from '@sanity/client'
export interface Env {
ENVIRONMENT: string;
SANITY_PROJECT_ID: string;
SANITY_DATASET: string;
SANITY_API_WRITE_TOKEN?: string;
}
export interface SubmissionMessage {
id: string;
name: string;
email: string;
message: string;
submittedAt: string;
environment: string;
}
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === '/health' && request.method === 'GET') {
return new Response(JSON.stringify({
status: 'healthy',
service: 'submission-processor',
environment: env.ENVIRONMENT ?? 'dev',
sanityProjectIdPrefix: env.SANITY_PROJECT_ID ? env.SANITY_PROJECT_ID.slice(0, 4) + '
' : 'NOT SET',
sanityDataset: env.SANITY_DATASET ?? 'NOT SET',
timestamp: new Date().toISOString(),
}), {
status: 200,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
}
// Test endpoint for direct Sanity integration (bypasses Queue for local testing)
if (url.pathname === '/test-direct' && request.method === 'POST') {
try {
const data = await request.json() as { name: string; email: string; message: string };
const sanityClient = createClient({
projectId: env.SANITY_PROJECT_ID,
dataset: env.SANITY_DATASET,
apiVersion: '2024-01-01',
token: env.SANITY_API_WRITE_TOKEN,
useCdn: false,
});
const result = await sanityClient.create({
_type: 'submission',
name: data.name,
email: data.email,
message: data.message,
status: 'pending',
submittedAt: new Date().toISOString(),
processedAt: new Date().toISOString(),
});
return new Response(JSON.stringify({
success: true,
sanityId: result._id,
message: 'Direct test: submission stored in Sanity'
}), {
headers: { 'Content-Type': 'application/json' },
});
} catch (error) {
console.error('Direct test error:', error);
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
return new Response(JSON.stringify({
success: false,
error: errorMessage,
}), {
status: 500,
headers: { 'Content-Type': 'application/json' },
});
}
}
if (request.method === 'OPTIONS') {
return new Response(null, {
status: 204,
headers: {
'Access-Control-Allow-Origin': '*',
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type',
},
});
}
return new Response(JSON.stringify({
error: 'Not Found',
hint: 'Try GET /health or POST /test-direct for direct Sanity testing'
}), {
status: 404,
headers: {
'Content-Type': 'application/json',
'Access-Control-Allow-Origin': '*',
},
});
},
async queue(batch: MessageBatch<SubmissionMessage>, env: Env): Promise<void> {
console.log(`Processing batch of ${batch.messages.length} submissions`);
// Initialize Sanity client
const sanityClient = createClient({
projectId: env.SANITY_PROJECT_ID,
dataset: env.SANITY_DATASET,
apiVersion: '2024-01-01',
token: env.SANITY_API_WRITE_TOKEN,
useCdn: false,
});
for (const message of batch.messages) {
try {
const submission = message.body as SubmissionMessage;
console.log(`Processing submission ${submission.id} from ${submission.email}`);
// Create document in Sanity
const result = await sanityClient.create({
_type: 'submission',
name: submission.name,
email: submission.email,
message: submission.message,
status: 'pending',
submittedAt: submission.submittedAt,
processedAt: new Date().toISOString(),
});
console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);
// Acknowledge message
message.ack();
} catch (error) {
console.error(`Failed to process submission:`, error);
// Retry with exponential backoff
message.retry({
delaySeconds: Math.min(60, Math.pow(2, batch.messages.indexOf(message))),
});
}
}
},
};
// Interface des bindings
export interface Env {
ENVIRONMENT: string;
SANITY_PROJECT_ID: string;
SANITY_DATASET: string;
SANITY_API_WRITE_TOKEN?: string;
}
// Type de message de soumission
export interface SubmissionMessage {
id: string;
name: string;
email: string;
message: string;
submittedAt: string;
environment: string;
}
Mécanisme Implémentation Rationale
Secrets injectés Secrets Sanity via wrangler secret put Protection des credentials
CORS Headers CORS permissifs Permettre les requĂȘtes cross-origin
Masquage de secrets Prefixe du project ID masquĂ© dans /health Protection contre l’exposition

Non applicable - les données sont déjà validées par form-handler.

  • ✅ Les secrets ne sont jamais dans le code source
  • ✅ Injection via wrangler secret put ou @inject_secret_to_cloudflare.sh
  • ✅ Rotation rĂ©guliĂšre des secrets (tous les 90 jours recommandĂ©)
Scénario Stratégie Rationale
Sanity API error Retry exponentiel (max 60s) Tolérance aux pannes temporaires
Sanity API success Ack message Confirmation du traitement

Non implémenté - pas de lock configuré.

  • ✅ Timeout global : DĂ©pend de Cloudflare (30s Free / 50s Paid)
  • ✅ Timeout par service tiers : DĂ©pend de Cloudflare
  • ✅ Retry exponentiel pour les erreurs Sanity
Limite Valeur StratĂ©gie d’optimisation
Mémoire RAM 128 MB Pas de traitement lourd en mémoire
CPU time 30s (Free) / 50s (Paid) Batch size limité à 10
Nombre de sous-requĂȘtes 50 LimitĂ© par batch size
Taille requĂȘte/rĂ©ponse 100 MB Payload de soumission limitĂ©
  • ✅ Traitement par batch (max 10 messages)
  • ✅ Retry exponentiel pour les erreurs
  • ✅ Endpoint de test direct pour le dĂ©veloppement local
// Logs console.log basiques
console.log(`Processing batch of ${batch.messages.length} submissions`);
console.log(`Processing submission ${submission.id} from ${submission.email}`);
console.log(`Successfully stored submission ${submission.id} in Sanity with ID: ${result._id}`);
console.error(`Failed to process submission:`, error);

Non implĂ©mentĂ© - aucune mĂ©trique ou alerte n’est configurĂ©e.

Terminal window
# Logs en temps réel
wrangler tail submission-processor
# Vérification des bindings
wrangler deployments list
# Test de l'endpoint health
curl http://localhost:8789/health
# Test de l'endpoint test-direct
curl -X POST http://localhost:8789/test-direct \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"Test message"}'
Terminal window
# Build
npm run build --workspace=services/submission-processor
# Déploiement staging
wrangler deploy --env staging
# Déploiement production
wrangler deploy --env production
Terminal window
# Liste des déploiements
wrangler deployments list
# Rollback vers une version précédente
wrangler deployments rollback --deployment-id=[id]
Terminal window
# Test HTTP (health check)
curl http://localhost:8789/health
# Test HTTP (test-direct)
curl -X POST http://localhost:8789/test-direct \
-H "Content-Type: application/json" \
-d '{"name":"Test","email":"test@example.com","message":"Test message"}'
# Test Queue (via wrangler)
wrangler queues tail submissions-queue-dev
SymptĂŽme Cause probable Solution
Sanity API error Token invalide ou manquant Vérifier les secrets injectés
Queue non consommée Binding mal configuré Vérifier wrangler.toml
Test-direct échoue Credentials Sanity manquants Vérifier .env local
Code HTTP / Erreur Signification Action
500 Erreur Sanity API Vérifier les logs et les credentials
404 Endpoint inexistant Vérifier le pathname
  • ✅ DĂ©coupler le traitement de la validation
  • ✅ Utiliser les bindings Cloudflare plutĂŽt que des appels HTTP internes
  • ✅ Traitement par batch pour optimiser les performances
  • ✅ Typer strictement les bindings et les rĂ©ponses
  • ✅ GĂ©rer explicitement les erreurs (try/catch)
  • ✅ Logger les erreurs et les succĂšs
  • ✅ Endpoint de test direct pour le dĂ©veloppement local
  • ✅ Ne jamais exposer les secrets cĂŽtĂ© client
  • ✅ Masquer les secrets dans les logs
  • ✅ Utiliser @inject_secret_to_cloudflare.sh pour l’injection
  • ✅ Traitement par batch (max 10 messages)
  • ✅ Retry exponentiel pour les erreurs
  • ✅ Timeout de batch (30s)
  • Configuration de Logpush pour les logs
  • Ajout de mĂ©triques et alertes
  • Ajout de structured logging (JSON)
  • AmĂ©lioration de la gestion des erreurs (DLQ)
  • ❌ CORS permissif (*) - Ă  restreindre pour la production
  • ❌ Pas de Dead Letter Queue (DLQ)
  • ❌ Pas de logs structurĂ©s
  • ❌ Pas de monitoring configurĂ©
  • ⚠ Endpoint /test-direct Ă  dĂ©sactiver en production
  • Documentation Cloudflare Workers : Getting Started
  • Documentation Cloudflare Queues : Queues
  • Documentation Sanity : Client API
  • Workers associĂ©s :
    • form-handler
  • Fichiers concernĂ©s :
    • services/submission-processor/src/index.ts
    • services/submission-processor/wrangler.toml
    • services/submission-processor/package.json
    • @inject_secret_to_cloudflare.sh
Date Version Description Auteur
2026-09-15 1.0 Création initiale Devin
Edit page

Last updated: