Last active
January 13, 2022 14:07
-
-
Save ngshiheng/358f1ff3c0ed184a19ab5f8561c529d0 to your computer and use it in GitHub Desktop.
I Built My Own TinyURL. Here’s How I Did it https://jerrynsh.com/i-built-my-own-tiny-url/
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// handlers/createShortUrl.js | |
import { generateUniqueUrlKey } from '../utils/urlKey' | |
export const createShortUrl = async (request, event) => { | |
try { | |
const urlKey = await generateUniqueUrlKey() | |
const { host } = new URL(request.url) | |
const shortUrl = `https://${host}/${urlKey}` | |
const { originalUrl } = await request.json() | |
const response = new Response( | |
JSON.stringify({ | |
urlKey, | |
shortUrl, | |
originalUrl, | |
}), | |
{ headers: { 'Content-Type': 'application/json' } } | |
) | |
event.waitUntil(URL_DB.put(urlKey, originalUrl)) | |
return response | |
} catch (error) { | |
console.error(error, error.stack) | |
return new Response('Unexpected Error', { status: 500 }) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// handlers/createShortUrl.js | |
import { URL_CACHE } from '../utils/constants' | |
import { generateUniqueUrlKey } from '../utils/urlKey' | |
export const createShortUrl = async (request, event) => { | |
try { | |
const urlKey = await generateUniqueUrlKey() | |
const { host } = new URL(request.url) | |
const shortUrl = `https://${host}/${urlKey}` | |
const { originalUrl } = await request.json() | |
const response = new Response( | |
JSON.stringify({ | |
urlKey, | |
shortUrl, | |
originalUrl, | |
}), | |
{ headers: { 'Content-Type': 'application/json' } } | |
) | |
const cache = await caches.open(URL_CACHE) // Access our API cache instance | |
event.waitUntil(URL_DB.put(urlKey, originalUrl)) | |
event.waitUntil(cache.put(originalUrl, response.clone())) // Update our cache here | |
return response | |
} catch (error) { | |
console.error(error, error.stack) | |
return new Response('Unexpected Error', { status: 500 }) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// index.js | |
import { Router } from 'itty-router' | |
import { createShortUrl } from './src/handlers/createShortUrl' | |
import { redirectShortUrl } from './src/handlers/redirectShortUrl' | |
import { LANDING_PAGE_HTML } from './src/utils/constants' | |
const router = Router() | |
// GET landing page html | |
router.get('/', () => { | |
return new Response(LANDING_PAGE_HTML, { | |
headers: { | |
'content-type': 'text/html;charset=UTF-8', | |
}, | |
}) | |
}) | |
// GET redirects short URL to its original URL. | |
router.get('/:text', redirectShortUrl) | |
// POST creates a short URL that is associated with its an original URL. | |
router.post('/api/url', createShortUrl) | |
// 404 for everything else. | |
router.all('*', () => new Response('Not Found', { status: 404 })) | |
// All incoming requests are passed to the router where your routes are called and the response is sent. | |
addEventListener('fetch', (e) => { | |
e.respondWith(router.handle(e.request)) | |
}) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// handlers/redirectShortUrl.js | |
export const redirectShortUrl = async ({ params }) => { | |
const urlKey = decodeURIComponent(params.text) | |
const originalUrl = await URL_DB.get(urlKey) | |
if (originalUrl) { | |
return Response.redirect(originalUrl, 301) | |
} | |
return new Response('Invalid Short URL', { status: 404 }) | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import { URL_CACHE } from '../utils/constants' | |
export const shortUrlCacheMiddleware = async (request) => { | |
const { originalUrl } = await request.clone().json() | |
if (!originalUrl) { | |
return new Response('Invalid Request Body', { | |
status: 400, | |
}) | |
} | |
const cache = await caches.open(URL_CACHE) | |
const response = await cache.match(originalUrl) | |
if (response) { | |
console.log('Serving response from cache.') | |
return response | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// utils/urlKey.js | |
import { customAlphabet } from 'nanoid' | |
const ALPHABET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz' | |
/* | |
Generate a unique `urlKey` using `nanoid` package. | |
Keep retrying until a unique urlKey which does not exist in the URL_DB. | |
*/ | |
export const generateUniqueUrlKey = async () => { | |
const nanoId = customAlphabet(ALPHABET, 8) | |
let urlKey = nanoId() | |
while ((await URL_DB.get(urlKey)) !== null) { | |
urlKey = nanoId() | |
} | |
return urlKey | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment