SvelteKit을 사용하여 URL 단축기를 만드는 방법.

다음과 같은 경우에 읽으십시오:
  • 이유가 필요없다

  • 자원
  • 소스 코드: Github
  • 라이브 예: shrink.theether.live
  • 영감:

  • 소개
    이것은 프로젝트 가이드입니다. 스스로 단계별로 따르도록 제안합니다. 이 기사에서는 Svelte, SvelteKit, Redis 및 TailwindCSS를 사용하여 URL 단축기를 만드는 방법을 배웁니다.

    초기화
    우리는 sveltekit 스타터 프로젝트부터 시작할 것입니다. 그런 다음 TailwindCSS를 설정합니다(my 를 읽는 방법을 모르는 경우).
  • 위 설정 외 필수 패키지(npm i package-name)

  •    1. redis
       2. dotenv
    

  • 환경 변수에 대한 .env 파일을 만듭니다. 이 프로젝트에서 우리는 Redis를 사용할 것이고 저는 DB용 PaaS 공급자인 Railway을 사용하고 있습니다.

  • // .env
    
    REDIS_URL=redis://localhost:6379 // Use your own it's for example.
    

  • src 디렉토리에 hooks.ts라는 이름으로 새 파일을 만듭니다.

  • // hooks.ts
    import dotenv from 'dotenv';
    
    dotenv.config();
    

    이것은 svelteKit에서 환경 변수를 로드합니다.

    Redis에 서버 연결

    Make sure you have installed it in your project if you haven't then run this command in your terminal

    npm i redis
    

  • lib 디렉토리에 src 폴더를 만들고 lib 폴더에 redisConnection.ts 파일을 만듭니다. 이 파일에서 Redis 연결을 처리하고 set 또는 get 값과 Redis 간의 기본 기능을 추가할 것입니다.

  • // redisConnection.ts
    
    import { createClient } from 'redis';
    import log from './log';
    
    const client = createClient({ url: process.env.REDIS_URL as string });
    
    let connectPromise: Promise<void> | undefined;
    let errorOnce = true;
    async function autoConnect(): Promise<void> {
        if (!connectPromise) {
            errorOnce = true;
            connectPromise = new Promise((resolve, reject) => {
                client.once('error', (err) => reject(new Error(`Redis: ${err.message}`)));
                client.connect().then(resolve, reject);
            });
        }
        await connectPromise;
    }
    client.on('error', (err) => {
        if (errorOnce) {
            log.error('Redis:', err);
            errorOnce = false;
        }
    });
    client.on('connect', () => {
        log('Redis up');
    });
    client.on('disconnect', () => {
        connectPromise = undefined;
        log('Redis down');
    });
    async function get<T>(key: string): Promise<T | undefined>;
    async function get<T>(key: string, fallback: T): Promise<T>;
    async function get<T>(key: string, fallback?: T): Promise<T | undefined> {
        await autoConnect();
        const value = await client.get(key);
        if (value === null) {
            return fallback;
        }
        return JSON.parse(value);
    }
    async function set(
        key: string,
        value: unknown,
        options?: { ttl: number } // TTL in seconds
    ): Promise<void> {
        const data = JSON.stringify(value);
        const config = options ? { EX: options.ttl } : {};
        await autoConnect();
        await client.set(key, data, config);
        await client.publish(key, data);
    }
    const storage = {
        get,
        set
    };
    export default storage;
    


    Here I'm using logger too, so skip logger for now. If you wanna learn how i did it check out Multiplayer Dice Game by bfanger on github. He is using socket.io, redis and many other things, you gonna learn alot from this.


  • 설명

  • -- redis에서 node에 대한 클라이언트를 만듭니다. createClient에서 redis를 사용합니다. REDIS_URL에 추가한 .env가 필요합니다.

    const client = createClient({ url: process.env.REDIS_URL as string });
    


    -- 우리는 redis에서 Redis 또는 set 값을 사용할 때 get에 연결하는 autoConnect 기능을 추가할 것입니다.

    let connectPromise: Promise<void> | undefined;
    let errorOnce = true;
    async function autoConnect(): Promise<void> {
        if (!connectPromise) {
            errorOnce = true;
            connectPromise = new Promise((resolve, reject) => {
                client.once('error', (err) => reject(new Error(`Redis: ${err.message}`)));
                client.connect().then(resolve, reject);
            });
        }
        await connectPromise;
    }
    


    -- 이제 우리는 redis와의 연결을 확인하고 이에 따라 응답하는 3개의 개시자를 추가했습니다. error , connecteddisconnected .

    client.on('error', (err) => {
        if (errorOnce) {
            log.error('Redis:', err);
            errorOnce = false;
        }
    });
    client.on('connect', () => {
        log('Redis up');
    });
    client.on('disconnect', () => {
        connectPromise = undefined;
        log('Redis down');
    });
    


    -- 이제 redis에서 데이터를 가져오는 기능 및 필요 기능을 추가할 것입니다.

    async function get<T>(key: string): Promise<T | undefined>;
    async function get<T>(key: string, fallback: T): Promise<T>;
    async function get<T>(key: string, fallback?: T): Promise<T | undefined> {
        await autoConnect();
        const value = await client.get(key);
        if (value === null) {
            return fallback;
        }
        return JSON.parse(value);
    }
    


    Here we first defined the types of function. Function takes one parameter key which is required to find value in redis. First we going to connect with redis using our auto connect function then going to get the value using our provided key and then we going to return parsed JSON.



    -- 이제 redis에 값을 추가하는 데 도움이 되는 set 함수를 추가하겠습니다. 설정 함수는 keyvalue 두 개의 매개변수를 사용합니다. redis에서 항목을 가져오는 데 도움이 되는 고유한 키가 필요합니다.

    async function set(
        key: string,
        value: unknown,
        options?: { ttl: number } // TTL in seconds
    ): Promise<void> {
        const data = JSON.stringify(value);
        const config = options ? { EX: options.ttl } : {};
        await autoConnect();
        await client.set(key, data, config);
        await client.publish(key, data);
    }
    


    -- 마지막으로 프로젝트의 어느 곳에서나 사용할 수 있도록 내보낼 것입니다.

    const storage = {
        get,
        set
    };
    export default storage;
    


    이것이 우리 프로젝트에서 작업하기 위해 redis에 추가해야 하는 전부입니다.

    프런트엔드 및 섀도우 엔드포인트 추가

    여기서 우리는 입력 상자와 버튼을 만들기 위해 html과 tailwindcss를 추가할 것입니다.

    // index.svelte
    
    <script lang="ts">
        import { page } from '$app/stores';
        import Clipboard from '$lib/Clipboard.svelte';
        let url: string;
        let isURLGenerated: boolean = false;
        let isInvalidURL: boolean = false;
    
        function isValidHttpUrl(string) {
            let url;
    
            try {
                url = new URL(string);
            } catch (_) {
                return false;
            }
    
            return url.protocol === 'http:' || url.protocol === 'https:';
        }
    
        async function getURL() {
            if (isValidHttpUrl(url)) {
                const r = (Math.random() + 1).toString(36).substring(7);
                const redirectURL = `${$page.url}${r}`;
                isInvalidURL = false;
                const data = { key: r, url: url };
                await fetch('/', {
                    method: 'POST',
                    headers: {
                        accept: 'application/json'
                    },
                    body: JSON.stringify(data)
                });
                url = redirectURL;
                isURLGenerated = true;
            } else isInvalidURL = true;
        }
    </script>
    
    <svelte:head>
        <title>Shrink | Home</title>
    </svelte:head>
    <div class="bg-white w-screen h-screen flex flex-col justify-center text-center">
        <h1 class="text-6xl p-4 text-fuchsia-500 font-bold">Shrink Me, Web.</h1>
        <div class="flex justify-center w-full p-10">
            <div class="mb-3 xl:w-2/4">
                {#if isInvalidURL}
                    <div
                        id="alert-border-2"
                        class="flex p-4 mb-4 bg-red-100 border-t-4 border-red-500 dark:bg-red-200"
                        role="alert"
                    >
                        <svg
                            class="flex-shrink-0 w-5 h-5 text-red-700"
                            fill="currentColor"
                            viewBox="0 0 20 20"
                            xmlns="http://www.w3.org/2000/svg"
                            ><path
                                fill-rule="evenodd"
                                d="M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z"
                                clip-rule="evenodd"
                            /></svg
                        >
                        <div class="ml-3 text-sm font-medium text-red-700">You have typed wrong URL.</div>
                    </div>
                {/if}
                <div class="input-group relative flex items-stretch w-full mb-4">
                    <input
                        type="text"
                        class="form-control relative flex-auto block w-full border-b-2 px-3 py-1.5 text-base font-normal text-gray-700 bg-white bg-clip-padding transition ease-in-out m-0 focus:outline-none duration-300 focus:text-gray-700 focus:bg-white {isURLGenerated
                            ? 'border-emerald-600 focus:border-emerald-600'
                            : 'border-fuchsia-600 focus:border-fuchsia-600'}"
                        placeholder="Paste or type your URL"
                        aria-label="url"
                        aria-describedby="url"
                        bind:value={url}
                    />
                    <Clipboard
                        text={url}
                        let:copy
                        on:copy={() => {
                            console.log('Has Copied');
                        }}
                    >
                        {#if isURLGenerated}
                            <button
                                on:click={copy}
                                class="inline-block px-6 py-2 border-2 border-emerald-600 bg-emerald-600 text-white font-medium text-xs leading-tight uppercase hover:bg-white hover:text-emerald-600 transition duration-300 ease-in-out"
                                type="button"
                                id="button-copy">Copy</button
                            >
                            <button
                                on:click={() => {
                                    url = '';
                                    isURLGenerated = false;
                                }}
                                class="inline-block px-6 py-2 border-2 border-rose-600 bg-rose-600 text-white font-medium text-xs leading-tight uppercase hover:bg-white hover:text-rose-600 transition duration-300 ease-in-out"
                                type="button"
                                id="button-reset">Reset</button
                            >
                        {:else}
                            <button
                                on:click={getURL}
                                class="inline-block px-6 py-2 border-2 border-fuchsia-600 bg-fuchsia-600 text-white font-medium text-xs leading-tight uppercase hover:bg-white hover:text-fuchsia-600 transition duration-300 ease-in-out"
                                type="button"
                                id="button-addon3">Shrink</button
                            >
                        {/if}
                    </Clipboard>
                </div>
                <div class="flex justify-center items-center">
                    <div class="spinner-grow inline-block w-8 h-8 bg-fuchsia-600 rounded-full  opacity-0" />
                </div>
            </div>
        </div>
    </div>
    
    



  • 설명
    여기서는 Shadow Endpoint에 대한 기본 기능 및 요청 작성에 대해 설명하겠습니다. 이전 게시물의 html 및 tailwind 부분을 이해하십시오.

  • -- 먼저 purscript 태그에 초점을 맞출 것입니다.

    <script lang="ts">
        import { page } from '$app/stores';
        import Clipboard from '$lib/Clipboard.svelte';
        let url: string;
        let isURLGenerated: boolean = false;
        let isInvalidURL: boolean = false;
    
        function isValidHttpUrl(string) {
            let url;
    
            try {
                url = new URL(string);
            } catch (_) {
                return false;
            }
    
            return url.protocol === 'http:' || url.protocol === 'https:';
        }
    
        async function getURL() {
            if (isValidHttpUrl(url)) {
                const r = (Math.random() + 1).toString(36).substring(7);
                const redirectURL = `${$page.url}${r}`;
                isInvalidURL = false;
                const data = { key: r, url: url };
                await fetch('/', {
                    method: 'POST',
                    headers: {
                        accept: 'application/json'
                    },
                    body: JSON.stringify(data)
                });
                url = redirectURL;
                isURLGenerated = true;
            } else isInvalidURL = true;
        }
    </script>
    


    Here, I have to define are parameters. Those are URL : which is provided by the user, isURLGenerated : checking if url generated or not (we need it to change are buttons from generate to copy), isInvalidURL : is defined for html to activate alert if url is not valid.

    isValidHttpUrl : This function help to verify our URL which is provided by user is a valid URL or not.

    getURL : This function generates are short url. First, I have added check for isValidHttpUrl if this is valid we gonna proceed otherwise we gonna return isInvalidURL = true. If URL is valid then going to generate a random string and after that we going to make a request to our shadow endpoint/api to save our key generated string and url provided by the user.



  • 섀도우 엔드포인트 또는 API

  • // index.ts
    
    import storage from '$lib/redisConnection';
    import type { RequestHandler } from '@sveltejs/kit';
    
    export const POST: RequestHandler = async ({ request }) => {
        const data = await request.json();
        await storage.set(data.key, data.url);
        return {
            body: {
                status: 200,
                error: null
            }
        };
    };
    


    -- 여기에 POST에서 요청하는 동안 제공하는 데이터를 가져오는 요청 매개변수를 사용하는 index.svelte 메서드 처리를 추가했습니다. 여기에서 set 함수를 사용하여 해당 데이터를 redis에 저장합니다.

    이것이 유효한 짧은 URL을 생성하고 redis에 저장하는 데 필요한 전부입니다. 이제 how we redirect user to URL when visit using short URL generated by you .

    원래 URL 가져오기 및 URL로 리디렉션
    이 섹션에서는 URL에서 매개 변수를 가져오고 redis에서 데이터를 가져온 다음 원래 URL로 리디렉션하는 방법을 배웁니다.

    -- routes 디렉토리[slug].ts에 새 파일을 추가하고 다음 코드 줄을 추가합니다.

    // [slug].ts
    
    import storage from '$lib/redisConnection';
    import type { RequestHandler } from '@sveltejs/kit';
    
    export const GET: RequestHandler = async ({ params }) => {
        if (params.slug.length > 3) {
            return {
                headers: {
                    Location: await storage.get(params.slug)
                },
                status: 302
            };
        } else
            return {
                headers: {
                    Location: '/'
                },
                status: 302,
                error: new Error('Short URL doesn't exist')
            };
    };
    


    Here we have added a GET method which will be called when we gonna visit any short url example. We are using {params} which is an inbuilt dictionary which contains our slug of the page.

    I have added condition to check length of the slug(generated string in index.svelte) and using that we gonna query our redis using get function and going to get the value which is our saved original URL. I have added Location in headers which helps us to redirect to that URL and added a status or redirect. If condition fails its going to redirected to home of our URL Shortner.



    원래 URL을 가져오고 리디렉션하는 방법입니다.

    그것이 기사의 끝입니다. 이 게시물을 훨씬 더 잘 이해하는 데 도움이 되는 언급된 모든 리소스와 링크를 확인해야 합니다.

    이것은 내가 당신을 위해 쓰는 것입니다. 묻고 싶은 것이나 제안하고 싶은 것이 있다면 댓글에 적어주세요.

    좋은 웹페이지 즐겨찾기