---
contentId: aa8454be-94ba-4d46-8be0-8cf703c0bfed
title: Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages
description: Apprenez à créer un raccourcisseur d'URL en utilisant Nitro et à le déployer sur Cloudflare Pages, permettant un développement dans l'edge sans effort.
date: 2024-03-19
---

Dans cet article, nous allons développer un **raccourcisseur d'URL** en utilisant [**Nitro**](https://nitro.unjs.io) et le déployer sur [**Cloudflare Pages**](https://pages.cloudflare.com/).

Le code source est accessible sur <GitHubLink repo="barbapapazes/url-shortener" />.

**Nitro** représente une nouvelle génération de **kit d'outils serveur**. Il nous permet de construire des serveurs web avec toutes les fonctionnalités nécessaires et de les déployer à notre convenance.

**Cloudflare Pages** est une plateforme conçue pour construire et héberger des sites web sur le edge. Elle prend en charge des services comme [**KV**](https://developers.cloudflare.com/kv/) pour créer des applications complètes et à état.

Notre projet est un **raccourcisseur d'URL** simple qui facilite la conversion d'une longue URL en une version raccourcie. Nous allons utiliser le **Cloudflare KV** pour stocker les URL et le **serveur Nitro** pour gérer les requêtes.

Nous allons utiliser :

- [**unstorage**](https://unstorage.unjs.io) pour rationaliser le processus de développement en abstraire la couche **KV**, éliminant ainsi le besoin du **Cloudflare Wrangler CLI**.
- [**ohash**](https://ohash.unjs.io) pour générer un **hash** à partir de l'URL afin de prévenir les collisions.
- [**nanojsx**](https://nanojsx.io/) pour construire les **pages HTML** en utilisant **TSX**.
- [**pico.css**](https://picocss.com) pour styliser l'application.

## Initialisation du Projet

Tout d'abord, créez un nouveau **projet Nitro** :

```bash
npx giget@latest nitro url-shortener
```

Ensuite, naviguez jusqu'au projet et installez les **dépendances** requises :

```bash
cd url-shortener
npm install
```

Démarrez le **serveur de développement** pour voir la page par défaut de Nitro :

```bash
npm run dev
```

Ouvrez votre navigateur et visitez [http://localhost:3000](http://localhost:3000) pour vérifier la fonctionnalité.

## Construction du Raccourcisseur d'URL

Dans un premier temps, installez les paquets nécessaires :

```bash
npm install ohash nano-jsx
```

### Générer une URL Raccourcie

Créez une **route** nommée `index.get.tsx` dans le répertoire `server/routes`. Cela servira de **page d'accueil** de notre raccourcisseur d'URL où les utilisateurs peuvent générer une URL raccourcie à partir d'une longue.

```tsx [server/routes/index.get.tsx]
import { h, Helmet, renderSSR } from 'nano-jsx' // le `h` est crucial ici
import { withTemplate } from '../resources/template'

export default defineLazyEventHandler(() => {
  const App = () => {
    return (
      <div>
        <Helmet>
          <title>Raccourcisseur d'URL avec Nitro</title>
        </Helmet>
        <h2>Raccourcir une URL</h2>
        <form action="/create" method="POST">
          <input type="url" name="url" placeholder="URL à raccourcir" autocomplete="off" />
          <button type="submit">Créer</button>
        </form>
      </div>
    )
  }
  const app = renderSSR(<App />)
  const { body, head } = Helmet.SSR(app)

  const page = withTemplate({
    body,
    head,
  })

  return defineEventHandler(() => {
    return page
  })
})
```

Cette route présentera un formulaire permettant aux utilisateurs de saisir une URL à raccourcir. Lors de la soumission du formulaire, une **requête POST** est envoyée à la route `/create`.

Un **gestionnaire d'événements paresseux** est utilisé pour générer la vue une seule fois, lorsque la requête atteint le serveur. La réponse est ensuite **mise en cache en mémoire** et réutilisée pour les requêtes futures, réduisant ainsi la charge computationnelle.

La fonction `withTemplate` sert d'utilitaire que nous devons construire.

```ts [server/resources/template.tsx]
interface LayoutProps {
  body: string
  head: string[]
}

export function withTemplate(props: LayoutProps) {
  const { head, body } = props

  return /* html */`<html>
      <head>
       <link
          rel="stylesheet"
          href="https://cdn.jsdelivr.net/npm/@picocss/pico@2/css/pico.min.css"
        />
        ${head.join('\n')}
      </head>
      <body>
        <header class="container">
          <h1>
            <a href="/">Raccourcisseur d'URL avec Nitro</a>
          </h1>
        </header>
        <main class="container">
          ${body}
        </main>
      </body>
    </html>`
}
```

Ceci est un simple **template de chaîne** où nous incorporons le contenu de l'en-tête et du corps générés par **nano-jsx**.

### Stockage de l'URL

Avant de procéder, installez `zod` pour valider le corps de la requête.

```bash
npm install zod
```

Créez la route `/create` pour gérer la **requête POST** et stocker l'URL dans le **KV**. Cette route est étiquetée `create.post.tsx` et se trouve dans le répertoire `server/routes`.

```tsx [server/routes/create.post.tsx]
import { h, Helmet, renderSSR } from 'nano-jsx'
import { hash } from 'ohash'
import { z } from 'zod' // le `h` est essentiel ici
import { withTemplate } from '../resources/template'

export default defineEventHandler(async (event) => {
  const body = await readValidatedBody(event, z.object({
    url: z.string().url(),
  }).parse)

  const requestURL = getRequestURL(event)
  const id = hash(body.url)
  const shortenURL = new URL(`/${id}`, requestURL).href

  await useStorage('data').setItem(id, body.url)

  const App = () => {
    return (
      <div>
        <Helmet>
          <title>Créé</title>
        </Helmet>
        <h2>Créé et prêt</h2>
        <input
          type="text"
          value={shortenURL}
          autofocus
        />
      </div>
    )
  }

  const app = renderSSR(<App />)
  const { body: nanoBody, head } = Helmet.SSR(app)

  return withTemplate({
    body: nanoBody,
    head,
  })
})
```

Dans ce segment, nous utilisons la fonction `readValidatedBody` pour **valider le corps de la requête**. Cela assure que le champ `url` est une URL légitime, sinon une erreur est générée.

Nous récupérons l'**URL de la requête** en utilisant la fonction `getRequestURL` de `h3`.

Le **hash** est créé à partir de l'URL du corps en utilisant la fonction `hash` de **ohash**, garantissant une génération de hash cohérente pour éviter les collisions.

L'URL est stockée dans le **KV** en utilisant la fonction `useStorage` de **unstorage**, employant le namespace `data` pour stocker les URL, préconfiguré pour notre utilisation.

<CalloutReadMore text="Stockage KV" href="https://nitro.unjs.io/guide/storage#usage" />

À la fin de ce gestionnaire d'événements, une route est renvoyée avec l'URL raccourcie que les utilisateurs peuvent copier et utiliser.

Pour le développement, tout fonctionne correctement, mais nous devons mettre à jour cette configuration, `nitro.config.ts`, pour l'environnement de production.

```ts [nitro.config.ts]
export default defineNitroConfig({
  srcDir: 'server',
  $production: {
    storage: { data: { driver: 'cloudflare-kv-binding', binding: 'url-shortener' } },
  },
})
```

Dans cette configuration, nous définissons le namespace `data`, comme dans le développement, afin d'utiliser le driver **cloudflare-kv-binding** et le binding `url-shortener`, spécifié sous la clé `$production`. Cela indique que la configuration s'applique uniquement à l'**environnement de production** pour accéder au **Cloudflare KV**.

<CalloutReadMore text="Configuration spécifique à l'environnement" href="https://github.com/unjs/c12#environment-specific-configuration" />

### Redirection vers l'URL Originale

Enfin, nous devons établir une route pour gérer les URL raccourcies et rediriger les utilisateurs vers l'URL principale. Cette route est nommée `[id].get.ts` et est située dans le répertoire `server/routes`.

```ts [server/routes/[id].get.ts]
export default defineEventHandler(async (event) => {
  const id = getRouterParam(event, 'short')

  const value = await useStorage<string>('data').getItem(id)

  if (!value) {
    throw createError({
      statusCode: 404,
      statusMessage: 'Non trouvé',
    })
  }

  return sendRedirect(event, value)
})
```

Ici, nous obtenons simplement l'`id` à partir des paramètres du routeur et utilisons `useStorage` pour récupérer l'**URL** du **KV**. Si l'URL n'existe pas, une **erreur 404** est générée. Sinon, l'utilisateur est redirigé vers l'URL originale.

## Déployer le Raccourcisseur d'URL

Félicitations ! Nous avons réussi à créer un raccourcisseur d'URL de base en utilisant Nitro. La prochaine étape consiste à **le déployer** sur Cloudflare Pages. Rassurez-vous, c'est simple grâce aux **fournisseurs de déploiement sans configuration**.

<CalloutReadMore text="Fournisseurs Zero Config" href="https://nitro.unjs.io/deploy#zero-config-providers" />

> [!WARNING]
> Un compte Cloudflare et un dépôt GitHub sont nécessaires pour continuer avec le déploiement.
Tout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Naviguez vers Accueil du compte, choisissez Workers & Pages, et créez une nouvelle application en utilisant le bouton en haut à droite. Sélectionnez l'onglet Pages et suivez les instructions pour établir une connexion GitHub.

Sélectionnez le dépôt, ajoutez la commande de construction `npm run build`, spécifiez le répertoire de sortie `dist`, incluez une variable d'environnement `NODE_ENV` avec la valeur `production`, puis enregistrez et déployez.

Maintenant, attendons que le déploiement soit terminé. Une fois terminé, vous pouvez accéder au raccourcisseur d'URL en utilisant l'URL fournie par **Cloudflare Pages**.

**Cependant**, il se peut qu'il ne fonctionne pas adéquatement initialement, car un **namespace KV** n'a pas été lié. Cela nécessite un lien manuel. Allez dans les paramètres du projet, sélectionnez les fonctions et faites défiler jusqu'à ce que vous trouviez **liaisons de namespace KV**. Ajoutez une liaison avec le nom `url-shortener` et choisissez le namespace désiré.

> [!NOTE]
> Créer un **namespace KV** est réalisable dans la section **Workers & Pages**.
Redéployez votre application pour tenir compte de la nouvelle liaison de namespace KV. Une fois terminé, le raccourcisseur d'URL sera pleinement fonctionnel. :sparkles:

> [!NOTE]
> Si vous n'avez pas l'intention de maintenir le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages afin d'éviter des dépenses inutiles.

## Améliorations et Considérations Futures

Pour améliorer la sécurité, envisagez d'ajouter une simple **protection CSRF** en utilisant la syntaxe des objets gestionnaires. Modifiez le fichier `create.post.tsx` pour inclure un gestionnaire avant la requête pour la vérification de l'en-tête d'origine.

```tsx [server/routes/create.post.tsx]
import { h, Helmet, renderSSR } from 'nano-jsx'
import { hash } from 'ohash'
import { z } from 'zod'
import { withTemplate } from '../resources/template'

export default defineEventHandler({
  onBeforeResponse: async (event) => {
    const requestURL = getRequestURL(event).origin
    const origin = getRequestHeader(event, 'origin')

    if (!origin) {
      throw createError({
        statusCode: 400,
        statusMessage: 'Mauvaise requête',
      })
    }

    if (origin !== requestURL) {
      throw createError({
        statusCode: 403,
        statusMessage: 'Interdit',
      })
    }
  },
  handler: async (event) => {
    const body = await readValidatedBody(event, z.object({
      url: z.string().url(),
    }).parse)

    const requestURL = getRequestURL(event)
    const id = hash(body.url)
    const shortenURL = new URL(`/${id}`, requestURL).href

    await useStorage('data').setItem(id, body.url)

    const App = () => {
      return (
        <div>
          <Helmet>
            <title>Créé</title>
          </Helmet>
          <h2>Créé et prêt</h2>
          <input
            type="text"
            value={shortenURL}
            autofocus
          />
        </div>
      )
    }

    const app = renderSSR(<App />)
    const { body: nanoBody, head } = Helmet.SSR(app)

    return withTemplate({
      body: nanoBody,
      head,
    })
  },
})
```

Nous mettons en place un gestionnaire avant la requête pour vérifier l'**en-tête d'origine**. S'il y a un désaccord, une erreur est générée ; sinon, la requête passe au gestionnaire principal.

> [!NOTE]
> Pour des informations complètes sur la **protection CSRF**, consultez la [Fiche de Prévention contre les Attaques CSRF](https://cheatsheetseries.owasp.org/cheatsheets/Cross-Site_Request_Forgery_Prevention_Cheat_Sheet.html) par OWASP. Valider l'**en-tête d'origine** est une approche de défense basique pour **CSRF**. Cependant, il est conseillé de mettre en œuvre un mécanisme de protection plus robuste, tel qu'un token.

## Conclusion

Développer avec **Nitro** et **Cloudflare Pages** est simple et offre une expérience de développeur exceptionnelle grâce à la possibilité d'utiliser **TSX** et un stockage de développement, éliminant le besoin du **Cloudflare Wrangler CLI**.

Profitez de votre parcours de développement avec **Nitro** et **Cloudflare Pages** ! 🚀

> [!NOTE]
> Le raccourcisseur d'URL est inspiré par [le Raccourcisseur d'URL de Yusuke Wada](https://github.com/yusukebe/url-shortener).
