Raccourcisseur d'URL avec Nitro sur Cloudflare Pages
Dans cet article, nous allons créer notre propre raccourcisseur d'URL en utilisant Nitro et le déployer sur Cloudflare Pages.
Le code source est disponible sur url-shortener.
Nitro est la prochaine génération d'outils serveur. Il nous permet de créer des serveurs web avec tout ce dont nous avons besoin et de les déployer là où nous le souhaitons.
Cloudflare Pages est une plate-forme pour créer et héberger des sites web à la périphérie. Il est possible d'utiliser des services comme KV pour créer des applications complètes et avec état.
Notre projet est un simple raccourcisseur d'URL qui nous permettra de créer une courte URL à partir d'une longue. Nous utiliserons le Cloudflare KV pour stocker les URL et le serveur Nitro pour gérer les requêtes.
Nous allons utiliser :
- unstorage pour abstraire la couche KV et simplifier le développement, car nous n'avons pas besoin d'utiliser l'interface en ligne de commande Cloudflare Wrangler.
- ohash pour créer un hachage à partir de l'URL et éviter les collisions.
- nanojsx pour créer les pages HTML en utilisant TSX.
- pico.css pour styliser l'application.
Initialiser le Projet
Tout d'abord, nous devons créer un nouveau projet Nitro :
npx giget@latest nitro url-shortener
Ensuite, nous pouvons entrer dans le projet et installer les dépendances requises :
cd url-shortener
npm install
Nous pouvons démarrer le serveur de développement pour voir la page par défaut de Nitro :
npm run dev
Ouvrez votre navigateur et accédez à http://localhost:3000 pour vérifier que tout fonctionne.
Créer le Raccourcisseur d'URL
Avant toute chose, nous devons installer les packages nécessaires :
npm install ohash nano-jsx
Créer une URL Courte
Tout d'abord, nous devons créer une route appelée index.get.tsx
dans le dossier server/routes
. Cette route sera la page d'accueil de notre raccourcisseur d'URL où l'utilisateur pourra créer une courte URL à partir d'une longue.
import { Helmet, h, renderSSR } from 'nano-jsx' // le `h` est très important 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 affichera un formulaire où l'utilisateur peut entrer une URL à raccourcir. Lorsque le formulaire est soumis, il enverra une requête POST à la route /create
.
Nous utilisons un gestionnaire d'événements paresseux pour créer la vue une seule fois, lorsqu'une requête atteint le serveur. Ensuite, la réponse est mise en cache en mémoire et réutilisée pour les requêtes suivantes. Cela est utile pour éviter de créer la vue à chaque requête, car elle est identique pour tous.
La fonction withTemplate
est un utilitaire que nous devons créer.
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>`
}
C'est un simple modèle de chaîne où nous injectons le contenu de l'en-tête et du corps créé par nano-jsx.
Stocker l'URL
Avant de passer à cette partie, nous devons installer zod
pour valider le corps de la requête.
npm install zod
Maintenant, nous devons créer la route /create
pour gérer la requête POST et stocker l'URL dans le KV. La route s'appelle create.post.tsx
et se trouve dans le dossier server/routes
.
import { z } from 'zod'
import { hash } from 'ohash'
import { Helmet, h, renderSSR } from 'nano-jsx' // le `h` est très important 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 cette route, nous utilisons la fonction readValidatedBody
pour valider le corps de la requête. Cela nous assure que le champ url
est une URL valide. Sinon, une erreur sera levée.
Ensuite, nous obtenons l'URL de la requête à l'aide de la fonction getRequestURL
de h3
.
Nous créons un hachage à partir de l'URL du corps en utilisant la fonction hash
de ohash
. Cela est utile pour garantir qu'une URL aura toujours le même hachage et pour éviter les collisions.
Nous stockons l'URL dans le KV en utilisant la fonction useStorage
de unstorage
. Nous utilisons l'espace de noms data
pour stocker les URL qui est préconfiguré pour nous.
À la fin de ce gestionnaire d'événements, nous retournons une route avec l'URL raccourcie que l'utilisateur peut copier et utiliser.
En développement, tout est en ordre, 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 l'espace de noms data
, tout comme en développement, pour utiliser le driver cloudflare-kv-binding
et le binding url-shortener
sous la clé $production
. Cela signifie que cette configuration ne sera utilisée que dans l'environnement de production pour accéder à Cloudflare KV.
Rediriger vers l'URL
Enfin, nous devons créer une route pour gérer les URL raccourcies et rediriger l'utilisateur vers l'URL originale. Cette route s'appelle [id].get.ts
et se trouve dans le dossier 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)
})
Rien de vraiment spécial ici. Nous obtenons l'id
des paramètres du routeur et nous utilisons la fonction useStorage
pour obtenir l'URL du KV. Si l'URL n'existe pas, nous levons une erreur 404. Sinon, nous redirigeons l'utilisateur vers l'URL originale.
Déployer le Raccourcisseur d'URL
C'est tout ! Nous avons créé un simple raccourcisseur d'URL avec Nitro. Maintenant, nous devons le déployer sur Cloudflare Pages. Pas de panique, c'est facile grâce à des fournisseurs de déploiement sans configuration.
Fournisseurs sans configurationWarning
Nous devons avoir un compte Cloudflare et un dépôt GitHub pour déployer l'application pour continuer. Tout d'abord, connectez-vous au tableau de bord Cloudflare et sélectionnez un compte. Ensuite, cliquez sur Accueil du compte, sélectionnez Workers & Pages. Créez une nouvelle application en utilisant le bouton en haut à droite, puis l'onglet Pages, et suivez le processus en utilisant une connexion Git.
Sélectionnez le dépôt, ajoutez la commande de construction npm run build
, le répertoire de sortie dist
, ajoutez une variable d'environnement NODE_ENV
avec la valeur production
et enregistrez et déployez.
Maintenant, nous n'avons plus qu'à attendre la fin du déploiement. Une fois terminé, nous pouvons accéder au raccourcisseur d'URL en utilisant l'URL fournie par Cloudflare Pages.
Mais, cela ne fonctionnera pas vraiment puisque nous n'avons pas lié un espace de noms KV. Nous devons le faire manuellement. Allez dans le projet, paramètres, fonctions et faites défiler jusqu'à ce que vous voyiez les liaisons d'espace de noms KV. Ensuite, ajoutez une liaison avec le nom url-shortener
et l'espace de noms que vous souhaitez utiliser.
Note
Il est possible de créer un espace de noms KV dans la section Workers & Pages. Maintenant, nous devons redéployer notre application pour prendre en compte la nouvelle liaison d'espace de noms KV. Une fois cela fait, nous pouvons utiliser le raccourcisseur d'URL. ✨
Note
Si vous ne prévoyez pas d'utiliser le projet, n'oubliez pas de le supprimer du tableau de bord Cloudflare Pages pour éviter des coûts inutiles.
Aller Plus Loin
Nous pouvons ajouter une simple protection CSRF en utilisant la syntaxe de l'objet gestionnaire. Nous mettons à jour le fichier create.post.tsx
pour ajouter un gestionnaire avant la requête afin de vérifier l'en-tête origin
.
import { z } from 'zod'
import { hash } from 'ohash'
import { Helmet, h, renderSSR } from 'nano-jsx'
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 définissons un gestionnaire avant la requête pour vérifier l'en-tête origin
. S'il y a un désaccord, nous levons une erreur. Sinon, nous continuons vers le gestionnaire principal.
Note
Veuillez lire Cross-Site Request Forgery Prevention Cheat Sheet d'OWASP pour comprendre la protection CSRF. Vérifier l'en-tête origin
est un moyen simple de se protéger contre la CSRF mais considéré comme une défense en profondeur. Il est recommandé d'utiliser une protection plus avancée comme un token.
Enfin
Construire avec Nitro et Cloudflare Pages est assez facile et offre une excellente expérience développeur grâce à la possibilité d'utiliser TSX et à un stockage de développement évitant l'utilisation de l'interface en ligne de commande Cloudflare Wrangler.
Amusez-vous à construire avec Nitro et Cloudflare Pages ! 🚀
Note
Le raccourcisseur d'URL est inspiré par le raccourcisseur d'URL de Yusuke Wada.