Créer un raccourcisseur d'URL avec Nitro et Cloudflare Pages
Dans cet article, nous allons développer un raccourcisseur d'URL en utilisant Nitro et le déployer sur Cloudflare Pages.
Le code source est accessible sur 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 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 pour rationaliser le processus de développement en abstraire la couche KV, éliminant ainsi le besoin du Cloudflare Wrangler CLI.
- ohash pour générer un hash à partir de l'URL afin de prévenir les collisions.
- nanojsx pour construire les pages HTML en utilisant TSX.
- pico.css pour styliser l'application.
Initialisation du Projet
Tout d'abord, créez un nouveau projet Nitro :
npx giget@latest nitro url-shortener
Ensuite, naviguez jusqu'au projet et installez les dépendances requises :
cd url-shortener
npm install
Démarrez le serveur de développement pour voir la page par défaut de Nitro :
npm run dev
Ouvrez votre navigateur et visitez http://localhost:3000 pour vérifier la fonctionnalité.
Construction du Raccourcisseur d'URL
Dans un premier temps, installez les paquets nécessaires :
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.
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.
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.
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
.
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.
À 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.
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.
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
.
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.
Fournisseurs Zero ConfigWarning
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. ✨
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.
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 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.