Nous allons réaliser un raccourcisseur d’URL basique en utilisant les services proposés par Cloudflare. Cela va nous permettre de voir Cloudflare Workers et KV.
Cloudflare Workers propose un environnement d’exécution serverless qui fonctionne sur le réseau de Cloudflare basé sur plus de 200 villes au niveau mondial. Sous le capot, Cloudflare Workers utilise le moteur V8 de Google. C’est le même engine que celui de Node.js.
Cloudflare Workers KV est un espace de stockage clé-valeur sans serveur qui fonctionne en conjonction avec Cloudflare Workers. Les bases de données clé-valeur sont très performantes en lecture. Les données sont écrites dans un petit nombre de data centers centralisés et sont ensuite mises en cache après l’accès. Il possède également la possibilité d’automatiquement supprimer la donnée basée sur une clé d’expiration ce qui va nous être bien utile.
Pour réaliser ce petit projet, vous avez besoin :
À noter que les free tiers de Cloudflare sont très généreux et que vous pourrez faire l’entièreté de cet article sans carte bleue.
Nous allons réaliser un worker avec deux routes d’API :
Maintenant que l’on sait où nous allons, nous pouvons nous lancer !
Wrangler CLI est un utilitaire en ligne de commande pour construire des workers sur Cloudflare Workers. Il va nous permettre d’initialiser un projet, de le lancer en local et de le déployer sur les infrastructures de Cloudflare.
Dans un terminal, installer Wrangler CLI via la commande suivante :
$ npm install -g wrangler
ou avec yarn :
$ yarn global add wrangler
Une fois fait, vous pouvez vérifier la bonne installation en exécutant la commande suivante :
$ wrangler --version
Afin de faire le lien entre Wrangler CLI et votre compte Cloudflare, vous allez devoir passer par un mécanisme d’authentification.
Pour cela, saisissez la commande suivante :
$ wrangler login
Une fenêtre de votre navigateur favori va s’ouvrir vous demandant d’autoriser l’accès à votre compte à Wrangler CLI. Une fois que vous avez accepté, vous pouvez refermer la page et retourner sur votre terminal.
Vous pouvez vérifier que l’identification a bien eu lieu via la commande :
$ wrangler whoami
Vous devriez avoir en retour quelque chose de similaire à ceci :
⛅️ wrangler 2.0.28--------------------Getting User settings...┌───────────────────────────────────┬──────────────────────────────────┐│ Account Name │ Account ID │├───────────────────────────────────┼──────────────────────────────────┤└───────────────────────────────────┴──────────────────────────────────┘🔓 Token Permissions: If scopes are missing, you may need to logout and re-login.Scope (Access)- account (read)- user (read)- workers (write)- workers_kv (write)- workers_routes (write)- workers_scripts (write)- workers_tail (read)- pages (write)- zone (read)- offline_accessRetrieving cached values for userId from node_modules/.cache/wrangler
Dans un nouveau dossier, saisissez la commande suivante :
$ wrangler init shorten
Le CLI va vous poser une série de questions, répondez toujours par oui.
À la question “Would you like to create a Worker at src/index.ts?”, répondez “Fetch handler”.
⛅️ wrangler 2.0.28-------------------------------------------------------Using npm as package manager.✨ Created wrangler.tomlWould you like to use git to manage this Worker? (y/n)✨ Initialized git repositoryNo package.json found. Would you like to create one? (y/n)✨ Created package.jsonWould you like to use TypeScript? (y/n)✨ Created tsconfig.jsonWould you like to create a Worker at src/index.ts?None❯ Fetch handlerScheduled handler
Vous allez obtenir un projet nommé shorten
avec les éléments suivants :
.git
signifiant que vous êtes prêt à envoyer votre code vers un repo distantpackage.json
tsconfig.json
avec toute la configuration TypeScriptsrc/index.ts
contenant un classique “Hello World!“.Vous pouvez essayer de lancer ce projet via la commande suivante :
$ wrangler dev
Cela va lancer notre Worker sur http://localhost:8787. Si vous appuyez sur “b”, une fenêtre de navigateur va s’ouvrir et vous verrez alors le résultat de notre Worker.
Si vous ouvrez le fichier src/index.ts
, vous devriez trouver ceci :
/*** Welcome to Cloudflare Workers! This is your first worker.** - Run `wrangler dev src/index.ts` in your terminal to start a development server* - Open a browser tab at http://localhost:8787/ to see your worker in action* - Run `wrangler publish src/index.ts --name my-worker` to publish your worker** Learn more at https://developers.cloudflare.com/workers/*/export interface Env {// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/// MY_KV_NAMESPACE: KVNamespace;//// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/// MY_DURABLE_OBJECT: DurableObjectNamespace;//// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/// MY_BUCKET: R2Bucket;}export default {async fetch(request: Request,env: Env,ctx: ExecutionContext): Promise<Response> {return new Response("Hello World!");},};
Le code ci-dessus défini tout d’abord notre environnement Env
avec beaucoup de commentaires sur l’interface. Cela permet de faire le lien entre les différents systèmes que propose Cloudflare et notamment les datastores. Nous y reviendrons un peu plus loin.
Ensuite, nous voyons que notre index.ts
exporte une fonction fetch
. Nous retrouvons des similitudes avec les Web Workers. D’ailleurs le nom Cloudflare Workers n’a certainement pas été choisi au hasard.
Dans l’exemple, la fonction fetch
renvoie une réponse contenant “Hello World!“. C’est cette réponse que nous récupérons dans le navigateur.
Maintenant que nous avons un début de projet, nous allons pouvoir commencer à construire notre solution. Pour cela, nous allons avoir besoin d’un datastore pour stocker nos codes/url.
Dans un terminal, positionnez-vous dans le dossier du projet créé précédemment et saisissez la commande suivante :
$ wrangler kv:namespace create KV
Nous allons refaire la même opération en ajoutant --preview
, pour avoir un datastore pour nos développements :
$ wrangler kv:namespace create KV --preview
À chacune des créations, un identifiant va vous être fourni. Veuillez les garder de côté. Nous allons en avoir besoin par la suite.
Ouvrez maintenant le fichier wrangler.toml
qui se situe à la racine du projet. Vous devriez avoir quelque chose de similaire à ceci :
name = "shorten"main = "src/index.ts"compatibility_date = "2022-09-06"
Nous allons ajouter nos datastore à ce fichier :
name = "shorten"main = "src/index.ts"compatibility_date = "2022-09-06"kv_namespaces = [{ binding = "KV", id = "<prod_id>", preview_id = "<preview_id>" }]
Pensez à remplacer <prod_id> et <preview_id> par les id que vous aviez mis de côté à l’étape précédente.
Retournez maintenant dans src/index.ts
et finissez le binding de votre datastore dans l’interface Env :
export interface Env {// Example binding to KV. Learn more at https://developers.cloudflare.com/workers/runtime-apis/kv/KV: KVNamespace;//// Example binding to Durable Object. Learn more at https://developers.cloudflare.com/workers/runtime-apis/durable-objects/// MY_DURABLE_OBJECT: DurableObjectNamespace;//// Example binding to R2. Learn more at https://developers.cloudflare.com/workers/runtime-apis/r2/// MY_BUCKET: R2Bucket;}
Le nom de la propriété dans
Env
doit être identique à ce que vous avez mis dans le fichierwrangler.toml
dans la propriétébinding
. Ici, nous avons misKV
.
Pour rappel, nous allons devoir gérer deux routes dans notre projet :
Pour cela, nous allons utiliser le package itty-router
. C’est un petit routeur sans aucune dépendance qui gère les paramètres d’URL et le parsing de query construit spécialement pour faire des Workers sur Cloudflare. Nous allons en profiter pour ajouter nanoid
pour générer nos codes.
Dans un terminal, positionnez-vous dans le projet et saisissez la commande suivante :
$ npm install itty-router itty-router-extras nanoid
Maintenant que nous avons nos dépendances, créez un fichier src/router.ts
avec le contenu ci-dessous :
import {Router} from "itty-router";import {missing} from "itty-router-extras";const router = Router();router.all('*', () => missing('Not found.'));export default router;
Nous commençons par créer notre router via const router = Router();
. Puis, nous pluggons par défaut toutes les routes à une erreur 404. Enfin, nous exportons ce router.
Dans le fichier src/index.ts
, modifier le contenu comme ci-dessous pour plugger la réponse à notre router :
export default {async fetch(request: Request,env: Env,ctx: ExecutionContext): Promise<Response> {return router.handle(request, env);},};
À ce niveau, notre worker ne fait plus grand-chose à part renvoyer des erreurs 404…
Nous allons maintenant nous occuper de faire le nécessaire pour enregistrer les URLs envoyées par les utilisateurs en leur associant un code dans le datastore.
Dans un dossier src/handlers
, créez un fichier shorten.ts
avec le code ci-dessous :
import {Request} from "itty-router";import {error} from "itty-router-extras";import {nanoid} from "nanoid";import {Env} from "../index";const shortenHandler = async (request: Request, env: Env) => {const requestBody = await request.json?.();if (!requestBody?.url) {return error(400, 'Missing `url` field.');}const slug = nanoid();await env.KV.put(slug, requestBody.url, {expirationTtl: 3600});return new Response(JSON.stringify({"url": `${new URL(request.url).origin}/${slug}`}), {headers: {'Access-Control-Allow-Origin': '*','Content-type': 'application/json'},status: 200});}export default shortenHandler;
Nous récupérons l’URL à enregistrer via requestBody.url
. Si celle-ci est manquante, nous renvoyons une erreur 400.
Nous générons ensuite un code via nanoid grâce à const slug = nanoid();
puis nous l’enregistrons sur Cloudflare KV via await env.KV.put(slug, requestBody.url, {expirationTtl: 3600});
.
Le mot clé
await
permet d’attendre d’avoir la confirmation d’écriture. Dans le cas contraire, nous pourrions être sorti du worker sans avoir correctement écrit dans le datastore.
Nous mettons un
expirationTtl
d’une heure pour que les entrées soient automatiquement supprimées par le datastore sans que nous ayons à gérer la logique de notre côté.
Pour finir, nous créons la réponse à renvoyer au client avec l’URL raccourcie.
Ouvrez maintenant le fichier src/router.ts
et ajoutez notre nouvelle route comme ci-dessous :
import {Router} from "itty-router";import {missing} from "itty-router-extras";import shortenHandler from "./handlers/shorten";router.post('/shorten', shortenHandler).all('*', () => missing('Not found.'));export default router;
Vous pouvez tester en lançant le projet via wrangler dev
puis en envoyant un curl comme ceci :
curl -X "POST" "http://127.0.0.1:8787/shorten" \-H 'Content-Type: application/json; charset=utf-8' \-d $'{"url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ"}'
Vous devriez avoir en réponse l’URL raccourcie. Vous pouvez également aller sur Cloudflare KV pour voir l’entrée dans le datastore preview
.
Il ne reste plus qu’à créer une route pour pouvoir rediriger l’utilisateur en fonction du code reçue.
Dans le dossier src/handlers
, créez un fichier redirect.ts
avec le code ci-dessous :
import {Request} from "itty-router";import {error, missing} from "itty-router-extras";import {Env} from "../index";const redirectHandler = async (request: Request, env: Env) => {const {params} = requestif (!params?.slug) {return error(400, 'Missing `slug` param.');}let link = await env.KV.get(params.slug);if (!link) {return missing(`Slug '${params.slug}' not found.`);}return new Response(null, {headers: {Location: link},status: 301});}export default redirectHandler;
Nous commençons par récupérer le code dans les paramètres via params.slug
puis nous interrogeons le datastore via await env.KV.get(params.slug);
. Si nous trouvons une URL nous effectuons la redirection sinon nous renvoyons une erreur 404.
Comme précédemment, vous devez ajouter cette route au fichier src/router.ts
:
import {Router} from "itty-router";import {missing} from "itty-router-extras";import shortenHandler from "./handlers/shorten";import redirectHandler from "./handlers/redirect";const router = Router();router.post('/shorten', shortenHandler).get('/:slug', redirectHandler).all('*', () => missing('Not found.'));export default router;
Maintenant, vous pouvez démarrez le projet via wrangler dev
. Ouvrez une fenêtre du navigateur et saisissez l’URL http://127.0.0.1/<code>
pour être redirigé.
Nous pouvons facilement déployer notre worker sur Cloudflare via la commande wrangler publish
. Le CLI va tout prendre en charge et nous obtiendrons une URL d’accès à notre worker accessible pour tout le monde.
Le code source de cet article est disponible sur Github.
Vous pouvez maintenant réaliser une interface pour votre raccourcisseur d’URL. De mon côté, j’en ai réalisé une en React disponible sur ce repository Github.
À noter que vous allez être confronté à un problème de CORS. En effet, l’URL de votre interface sera différente de celle de votre worker.
Pour le résoudre, vous devez ajouter une route à votre router comme ceci :
router.options('/shorten', () => {return new Response(null, {headers: {"Access-Control-Allow-Origin": "*","Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept"}});}).post('/shorten', shortenHandler).get('/:slug', redirectHandler).all('*', () => missing('Not found.'));export default router;
Je suis convaincu que l’avenir est à l’abstraction de la couche serveur pour les équipes de développement que ce soit au niveau du back mais également des bases de données.
Si vous avez un projet serverless, n’hésitez pas à me contacter pour que nous y réfléchissions ensemble.