Une infrastructure qui ressemble à l'import d'un module et à l'appel d'une fonction. Alchemy est une bibliothèque d'Infrastructure-as-Code nativement écrite en TypeScript — sans YAML, sans HCL, sans DSL séparé. Les ressources sont de simples fonctions asynchrones que vous attendez avec await. Dans ce tutoriel, vous construirez et déploierez une API Cloudflare Workers complète, adossée à une base D1, un espace de noms KV, un bucket R2 et des secrets typés — le tout depuis un unique fichier alchemy.run.ts.
Ce que vous allez apprendre
À la fin de ce tutoriel, vous saurez :
- Comprendre ce qu'est Alchemy et en quoi il diffère de Terraform, Pulumi, SST et Wrangler
- Initialiser un projet Alchemy et vous authentifier auprès de Cloudflare
- Déployer un Cloudflare Worker écrit en pur TypeScript
- Provisionner et lier une base D1, un espace de noms KV et un bucket R2
- Gérer les secrets en toute sécurité avec
alchemy.secret - Lancer une boucle de développement locale qui émule vos liaisons
- Inspecter et comprendre les fichiers d'état et le rôle de
finalize() - Tout démanteler proprement avec une seule commande
Prérequis
Avant de commencer, assurez-vous d'avoir :
- Node.js 20+ ou Bun 1.1+ installé (
node --version) - Un compte Cloudflare gratuit (aucun forfait payant requis pour ce tutoriel)
- Des bases en TypeScript et en async/await
- Un éditeur de code (VS Code recommandé)
Aucune expérience préalable de Terraform, Pulumi ou Wrangler n'est nécessaire. Les oublier vous aidera même plutôt.
Qu'est-ce qu'Alchemy ?
Alchemy est une bibliothèque d'Infrastructure-as-Code (IaC) intégrable, sans dépendances et nativement écrite en TypeScript. Là où la plupart des outils IaC vous demandent d'apprendre un langage de configuration — le HCL de Terraform, le YAML de CloudFormation, les abstractions SDK de Pulumi — Alchemy ne vous demande rien de nouveau. Une ressource est une simple fonction asynchrone :
const bucket = await R2Bucket("uploads");Cette seule ligne crée un bucket R2 s'il n'existe pas, le met à jour si sa configuration a changé et enregistre son état localement. Pas de cérémonie plan/apply, pas de plugins de fournisseur à installer, pas de plan de contrôle caché. Le modèle mental est tout simplement : appelez une fonction, récupérez une ressource.
Comparaison
| Outil | Langage | Modèle mental | État |
|---|---|---|---|
| Terraform | HCL | Graphe déclaratif | .tfstate distant/local |
| Pulumi | SDK TS/Go/Python | Graphe déclaratif via SDK | Service Pulumi |
| SST | TS au-dessus de Pulumi | Constructs | Géré/local |
| Wrangler | wrangler.toml | CLI + fichier de config | Géré par Cloudflare |
| Alchemy | TypeScript pur | Fonctions asynchrones | Fichiers locaux versionnables |
Les différences clés qui font ressortir Alchemy en 2026 :
- Les ressources sont des fonctions asynchrones. Aucune nouvelle couche d'abstraction à apprendre.
- TypeScript pur en ESM. Il s'exécute partout où tourne le JavaScript moderne, avec un support de premier ordre pour Bun.
- L'état vit dans votre dépôt. Les fichiers d'état sont locaux, lisibles par un humain, inspectables, modifiables et versionnables.
- Multi-fournisseurs. Cloudflare et AWS sont de première classe, et vous pouvez générer rapidement un fournisseur pour n'importe quelle API REST.
Ce tutoriel se concentre sur Cloudflare car c'est le chemin le plus rapide du néant à une API déployée et avec état.
Étape 1 : initialiser le projet
Le moyen le plus rapide de démarrer est la commande create, qui met en place un projet TypeScript, installe Alchemy et câble les scripts :
# Avec npm
npx alchemy@latest create alchemy-api --template typescript
# Avec Bun (recommandé — Alchemy adore Bun)
bunx alchemy@latest create alchemy-api --template typescriptPlacez-vous dans le dossier :
cd alchemy-apiLe template vous donne une structure de ce type :
alchemy-api/
├── alchemy.run.ts # Votre infrastructure — le cœur du projet
├── src/
│ └── worker.ts # Le code applicatif de votre Worker
├── package.json
├── tsconfig.json
└── .env # Secrets locaux (ignorés par git)Les deux fichiers qui comptent sont alchemy.run.ts (quoi déployer) et src/worker.ts (le code qui s'exécute). Cette séparation — infrastructure et application côte à côte dans le même langage — est toute la philosophie.
Étape 2 : s'authentifier auprès de Cloudflare
Alchemy a besoin d'une autorisation pour gérer les ressources de votre compte Cloudflare. Lancez la connexion interactive une seule fois :
npx alchemy loginCela exécute un flux OAuth dans votre navigateur et stocke un jeton localement. Dans un environnement CI, vous pouvez sauter la connexion interactive et fournir une variable d'environnement CLOUDFLARE_API_TOKEN à la place — Alchemy la détectera automatiquement.
Si vous devez un jour reconfigurer les paramètres du projet, lancez :
npx alchemy configureÉtape 3 : déployer votre premier Worker
Ouvrez alchemy.run.ts. Dans sa forme la plus simple, le fichier initialise une application, déclare des ressources, puis finalise. Remplacez le contenu par :
import alchemy from "alchemy";
import { Worker } from "alchemy/cloudflare";
// Initialise l'application. Le nom isole toutes les ressources et l'état.
const app = await alchemy("alchemy-api");
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
});
console.log(`Worker deployed at: ${worker.url}`);
// finalize valide l'état et supprime les ressources orphelines.
await app.finalize();Écrivez maintenant le vrai Worker dans src/worker.ts :
export default {
async fetch(request: Request): Promise<Response> {
return Response.json({
message: "Hello from Alchemy!",
timestamp: new Date().toISOString(),
});
},
};Déployez-le :
npx alchemy deployAlchemy crée le Worker, téléverse votre code empaqueté et affiche une URL *.workers.dev en direct. Ouvrez-la — vous devriez voir votre réponse JSON. Vous venez d'expédier une API serverless sans aucun fichier de configuration.
Que vient-il de se passer ?
alchemy("alchemy-api")a ouvert une « portée » qui suit chaque ressource créée. Chaque appelawait Worker(...)réconcilie l'état souhaité avec la réalité.app.finalize()écrit ensuite le fichier d'état et supprime tout ce qui existe dans l'état mais n'est plus déclaré dans le code. C'est ainsi qu'Alchemy collecte les ressources supprimées.
Étape 4 : ajouter une base D1
Une vraie API a besoin de persistance. Cloudflare D1 est une base SQLite serverless, et Alchemy la provisionne comme n'importe quelle autre ressource. Mettez à jour alchemy.run.ts :
import alchemy from "alchemy";
import { Worker, D1Database } from "alchemy/cloudflare";
const app = await alchemy("alchemy-api");
// Provisionne une base D1.
const db = await D1Database("app-db", {
name: "alchemy-app-db",
});
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
// Les liaisons exposent les ressources au Worker par leur nom.
bindings: {
DB: db,
},
});
console.log(`Worker deployed at: ${worker.url}`);
await app.finalize();L'objet bindings est le pont entre l'infrastructure et le runtime. La clé DB devient accessible dans votre Worker via env.DB, entièrement typée comme un client D1. Pas de wrangler.toml, pas de déclarations de liaison manuelles.
Typer l'environnement de votre Worker
Alchemy déduit les types des liaisons depuis votre alchemy.run.ts. Exportez le worker pour que le type de son environnement parvienne à votre code applicatif. Ajoutez un export dans alchemy.run.ts :
export { worker };Puis dans src/worker.ts, importez le type d'environnement déduit :
import type { worker } from "../alchemy.run.ts";
type Env = typeof worker.Env;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
if (url.pathname === "/users") {
// env.DB est un client D1 entièrement typé — sans câblage manuel.
const { results } = await env.DB.prepare(
"SELECT id, name FROM users LIMIT 10",
).all();
return Response.json({ users: results });
}
return Response.json({ message: "Hello from Alchemy!" });
},
};Le nom de la liaison dans alchemy.run.ts et la propriété sur env correspondent forcément, car ils proviennent de la même source de vérité. Renommez une liaison et TypeScript signale chaque usage.
Étape 5 : ajouter les liaisons KV et R2
La plupart des applications ont besoin de plus qu'une base de données — un cache et un stockage d'objets sont courants. Alchemy traite les espaces de noms KV et les buckets R2 exactement comme D1 : on déclare, puis on lie.
import alchemy from "alchemy";
import {
Worker,
D1Database,
KVNamespace,
R2Bucket,
} from "alchemy/cloudflare";
const app = await alchemy("alchemy-api");
const db = await D1Database("app-db", { name: "alchemy-app-db" });
const cache = await KVNamespace("app-cache");
const uploads = await R2Bucket("app-uploads");
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: {
DB: db,
CACHE: cache,
UPLOADS: uploads,
},
});
console.log(`Worker deployed at: ${worker.url}`);
export { worker };
await app.finalize();Les trois ressources sont désormais accessibles dans le Worker via env.CACHE et env.UPLOADS, là encore entièrement typées :
import type { worker } from "../alchemy.run.ts";
type Env = typeof worker.Env;
export default {
async fetch(request: Request, env: Env): Promise<Response> {
const url = new URL(request.url);
// KV : exemple de cache en lecture anticipée
if (url.pathname === "/config") {
const cached = await env.CACHE.get("config");
if (cached) return new Response(cached);
const fresh = JSON.stringify({ theme: "dark", version: 2 });
// Cache pendant une heure
await env.CACHE.put("config", fresh, { expirationTtl: 3600 });
return new Response(fresh);
}
// R2 : stocker un fichier téléversé
if (url.pathname === "/upload" && request.method === "POST") {
const key = `uploads/${Date.now()}`;
await env.UPLOADS.put(key, request.body);
return Response.json({ stored: key });
}
return Response.json({ message: "Hello from Alchemy!" });
},
};Redéployez avec npx alchemy deploy. Alchemy compare vos ressources déclarées à l'état enregistré : la base existe déjà, elle est donc laissée intacte, tandis que le nouvel espace de noms KV et le nouveau bucket R2 sont créés et liés. Cette réconciliation incrémentale est ce qui rend l'IaC sûre à exécuter de façon répétée.
Étape 6 : gérer les secrets en toute sécurité
Les clés d'API et les jetons ne doivent jamais rester en clair dans votre code ou vos fichiers d'état. Alchemy fournit alchemy.secret, qui chiffre les valeurs avant leur écriture dans l'état et les injecte dans le Worker à l'exécution.
const worker = await Worker("api", {
entrypoint: "./src/worker.ts",
bindings: {
DB: db,
CACHE: cache,
UPLOADS: uploads,
// Enveloppez toute valeur sensible pour la chiffrer au repos dans l'état.
API_KEY: alchemy.secret(process.env.STRIPE_KEY),
},
});Vous pouvez aussi puiser directement dans les variables d'environnement avec l'assistant .env, qui lit la variable nommée et échoue bruyamment si elle manque :
bindings: {
// Lit process.env.STRIPE_KEY, le chiffre, échoue si absent.
API_KEY: alchemy.secret.env.STRIPE_KEY,
},Dans le Worker, le secret est une simple chaîne sur env :
const auth = request.headers.get("authorization");
if (auth !== `Bearer ${env.API_KEY}`) {
return new Response("Unauthorized", { status: 401 });
}Stockez la valeur réelle dans votre fichier .env local (que le template ignore via git) et dans votre gestionnaire de secrets CI pour les déploiements. C'est la forme chiffrée qui aboutit dans l'état, donc versionner l'état ne divulgue jamais d'identifiants.
Astuce : Alchemy chiffre les secrets dans l'état avec une phrase secrète. Définissez
ALCHEMY_PASSWORDdans votre environnement, en local comme en CI, afin que le même état puisse être déchiffré d'une machine à l'autre.
Étape 7 : la boucle de développement locale
Déployer sur Cloudflare à chaque changement est lent. Alchemy embarque un mode développement qui émule vos liaisons en local avec rechargement à chaud :
npx alchemy devLa plupart des liaisons Cloudflare — D1, KV et R2 — sont émulées automatiquement, si bien que env.DB, env.CACHE et env.UPLOADS fonctionnent sans toucher à vos ressources de production. Les secrets sont résolus depuis votre .env local. Modifiez src/worker.ts, enregistrez, et le Worker se recharge instantanément face à l'émulation locale.
Vous obtenez la boucle de rétroaction serrée d'un serveur local tout en conservant une source de vérité unique : le même alchemy.run.ts décrit votre environnement de développement local et la production. Aucune dérive entre un wrangler.toml de dev et autre chose pour la prod.
Étape 8 : comprendre les fichiers d'état
Après votre premier déploiement, regardez dans le dossier .alchemy/. Vous y trouverez des fichiers d'état JSON décrivant chaque ressource gérée par Alchemy — identifiants, configuration et secrets (chiffrés). C'est délibérément non caché derrière un service distant.
Comme l'état est local et lisible, vous pouvez :
- Le versionner pour que votre équipe partage une vision cohérente de l'infrastructure déployée
- L'inspecter pour comprendre exactement ce qui existe et pourquoi
- Le comparer dans les pull requests pour voir l'impact d'un changement sur des ressources réelles
C'est une philosophie différente de Terraform Cloud ou du backend géré de Pulumi. Alchemy vous confie votre propre état. Pour les équipes, vous pouvez configurer un magasin d'état partagé (par exemple adossé à une ressource Cloudflare) pour que les déploiements concurrents restent cohérents — mais pour les projets solo et les petites équipes, l'état local versionné suffit souvent.
Étape 9 : tout démanteler
L'un des meilleurs tests d'un outil IaC est la propreté avec laquelle il retire ce qu'il a créé. Alchemy suit chaque ressource dans l'état, donc une seule commande les détruit toutes dans l'ordre des dépendances :
npx alchemy destroyCela supprime le Worker, la base D1, l'espace de noms KV et le bucket R2 — tout ce qui se trouve dans la portée de votre application. Aucune ressource orpheline accumulant silencieusement des coûts sur votre facture Cloudflare. Comme la destruction est pilotée par l'état, elle est exacte : seul ce qu'Alchemy a créé est retiré.
Tester votre implémentation
Vérifiez le flux complet de bout en bout :
- Déploiement : lancez
npx alchemy deployet confirmez que l'URL affichée répond. - Base de données :
curl https://your-worker.workers.dev/usersrenvoie un tableau JSON (vide tant que vous n'avez pas inséré de données). - Cache :
curl https://your-worker.workers.dev/configdeux fois — le second appel est servi depuis KV. - Stockage :
curl -X POST --data-binary @file.png https://your-worker.workers.dev/uploadrenvoie une clé de stockage. - Secret : une requête sans l'en-tête
Authorizationcorrect vers une route protégée renvoie401. - Mode dev : lancez
npx alchemy dev, modifiez le Worker, et confirmez le rechargement à chaud face aux liaisons émulées. - Démantèlement :
npx alchemy destroyretire tout ; le relancer ne fait rien.
Dépannage
Not authenticated au déploiement. Relancez npx alchemy login, ou définissez CLOUDFLARE_API_TOKEN en CI. Vérifiez que le jeton possède les permissions Workers, D1, KV et R2.
La liaison est undefined à l'exécution. La clé dans l'objet bindings doit correspondre à la propriété lue sur env. Assurez-vous d'avoir exporté worker depuis alchemy.run.ts et importé typeof worker.Env pour que TypeScript détecte les écarts.
Impossible de déchiffrer le secret entre machines. Définissez le même ALCHEMY_PASSWORD partout où l'état est utilisé. Une phrase secrète différente ne peut pas déchiffrer des secrets écrits avec une autre.
Conflits d'état en déployant depuis deux machines. L'état local est propre à chaque copie de travail. Pour des déploiements partagés, configurez un magasin d'état distant plutôt que de versionner des modifications concurrentes des mêmes fichiers JSON.
destroy laisse une ressource. Cela signifie généralement que la ressource a été créée en dehors d'Alchemy (par exemple manuellement dans le tableau de bord). Alchemy ne gère que ce qui figure dans son état.
Prochaines étapes
- Ajoutez des Durable Objects et des Queues — toutes deux des ressources Cloudflare de première classe dans
alchemy/cloudflare, liées exactement comme D1 et KV. - Intégrez Alchemy à une chaîne CI/CD avec un workflow GitHub Actions qui lance
alchemy deployà la fusion — voir notre guide CI/CD GitHub Actions. - Comparez à l'approche SST dans notre tutoriel de déploiement SST Ion sur AWS.
- Déployez une application complète sur Workers avec notre tutoriel API Cloudflare Workers + Hono + D1.
- Générez un fournisseur personnalisé pour une API REST interne et gérez-la aux côtés de vos ressources Cloudflare.
Conclusion
Alchemy efface l'écart entre code applicatif et infrastructure en refusant d'introduire un nouveau langage pour cette dernière. Les ressources sont des fonctions asynchrones, les liaisons des objets typés, l'état du JSON lisible que vous possédez, et le même fichier décrit le développement local et la production. Dans ce tutoriel, vous avez initialisé un projet, déployé un Cloudflare Worker, attaché une base D1, un espace de noms KV et un bucket R2, sécurisé le tout avec des secrets chiffrés, exécuté une boucle de dev locale et tout démantelé d'une seule commande.
Pour les équipes qui vivent déjà en TypeScript — surtout sur Cloudflare — Alchemy supprime toute une catégorie de changements de contexte. Pas de YAML à mémoriser, pas de HCL à apprendre, pas de backend géré dont dépendre. Juste du code qui construit, déploie et nettoie l'infrastructure dont votre application a besoin.