+1

Viết Shorten URL bằng TALL stack

Tổng quan

Trước khi bắt đầu, mình sẽ giới thiệu cho các bạn những tính năng của web app này. Đây là sẽ là màn hình chính của app. Ở đây sẽ có tên app, một cái input để người dùng có thể thêm url vào. Bên dưới có hai nút, một nút Shorten để generate ra url ngắn hơn của url mà người dùng đã paste vào và một nút Copy để copy url mới được generate.

image.png

Ví dụ như mình thêm url dài vào và bấm Shorten thì mình sẽ được một cái url ngắn hơn như bên dưới. Nếu bấm nút Copy thì url mới này sẽ được copy vào clipboard và mình có thể đem paste đi chỗ khác.

image.png

Stack mình sẽ dùng ở đây là TALL stack gồm có Tailwindcss, AlpineJS, Laravel và Livewire. Về db thì mình dùng sqlite3.

Khởi tạo

Laravel

Đầu tiên, mình sẽ khởi tạo dự án bằng Laravel.

laravel new shorten

Sau đó, chúng ta sẽ chạy npm install để cài những package cho js. Cuối cùng, là thay đổi tên của dự án trong .env ở phần APP_NAME, đổi thành Shorten.

Livewire

Để cài đặt livewire, chúng ta chạy lệnh sau.

composer require livewire/livewire

Tiếp theo là tạo layout cho livewire.

php artisan livewire:layout

Các bạn sẽ có một file như thế này trong resources/views/components/layouts/app.blade.php.

<!DOCTYPE html>
<html lang="{{ str_replace('_', '-', app()->getLocale()) }}">
   <head>
       <meta charset="utf-8">
       <meta name="viewport" content="width=device-width, initial-scale=1.0">

       <title>{{ $title ?? 'Page Title' }}</title>
   </head>
   <body>
       {{ $slot }}
   </body>
</html>

TailwindCSS

Với tailwindCSS, mình sẽ dùng thêm DaisyUI để đỡ khai báo nhiều class. Đầu tiên mình sẽ cài tailwindcss.

npm install -D tailwindcss postcss autoprefixer
npx tailwindcss init -p

Thêm cái này vào phần content của file tailwind.config.js

     "./resources/**/*.blade.php",
    "./resources/**/*.js",
    "./resources/**/*.vue",

Thêm phần này vào app.css

@tailwind base;
@tailwind components;
@tailwind utilities;

Cuối cùng, thêm đoạn này vào trong phần <head></head> của file layout lúc nãy chúng ta tạo cho livewire.

@vite('resources/css/app.css')

Tiếp theo, chạy câu lệnh này để cài đặt DaisyUI

npm i -D daisyui@latest

Cuối cùng, thêm vào plugins vào file tailwind.config.js

module.exports = {
  //...
  plugins: [
    require('daisyui'),
  ],
}

Ok như vậy là chúng ta đã cài xong tailwindcss và daisyUI.

AlpineJS

AlpineJS thì có rồi, khỏi cài

Logic

Tiếp theo, chúng ta cần phải xác định về luồng của app. Thì luồng app này cũng khá đơn giản. Khi mà user nhập một cái url vào ví dụ như https://www.example.com, thì app sẽ generate ra một cái unique code, ví dụ như là 12344. Sau đó, app sẽ gắn cái code này với cái url trong database. Cuối cùng, app sẽ dùng code này kết hợp với domain để trả ra kết quả cho người dùng. Thì chúng ta sẽ có http://localhost:8000/12344 -> https://www.example.com. Tức là khi bấm vào http://localhost:8000/12344 thì nó sẽ redirect chúng ta qua https://www.example.com. Ok, vậy là xong logic. Tiếp tục với bước tiếp theo là chúng ta sẽ lên structure cho database.

Database

Chúng ta sẽ có 1 table tên là urls, trong table này sẽ có những field sau:

- id unsignedBigInteger
- code varchar(255)
- url text
- created_at datetime
- updated_at datetime

Sau khi xong phần định nghĩa structure của db thì chúng ta sẽ bắt đầu với phần migration.

Migration

Các bạn chạy lệnh

php artisan make:migration create_urls_table

Laravel sẽ generate ra một file để chạy migration trong thư mục database/migrations. Các bạn thêm code như vầy để có được structure db như mình định nghĩa ở trên.

return new class() extends Migration {
    /**
     * Run the migrations.
     */
    public function up(): void
    {
        Schema::create('urls', function (Blueprint $table) {
            $table->id();
            $table->string('code');
            $table->text('url');
            $table->timestamps();
        });
    }

    /**
     * Reverse the migrations.
     */
    public function down(): void
    {
        Schema::dropIfExists('urls');
    }
};

Tạo livewire component

Giờ chúng ta sẽ chạy câu lệnh

php artisan make:livewire ShortenUrl

Sau khi chạy câu lệnh này, chúng ta sẽ có 2 file. Một file là php class tên là ShortenUrl.php nằm trong thư mục app/Livewire. Một file là file blade template shorten-url.blade.php nằm trong thư mục resources/views/livewire/shorten-url.blade.php. Tiếp theo, chúng ta sẽ vào file web.php trong thư mục routes để trỏ route / đến component này.

Route::get('/', ShortenUrl::class);

Giờ khi truy cập vào đường dẫn / thì app sẽ load ra component ShortenUrl.

Implement UI

Giờ mình sẽ implement UI follow theo thiết kế. Code mình sẽ như thế này.

<div class="flex justify-center flex-wrap flex-col h-screen">
    <div class="w-full">
        <h1 class="text-center text-6xl font-bold text-indigo-700">Shorten Urls Easily</h1>
    </div>
    <div class="w-full text-center my-10">
        <input type="url" placeholder="Add Url here" class="input input-bordered w-full max-w-md" wire:model="url" />
        <p class="text-error">
            @error('url')
                {{ $message }}
            @enderror
        </p>
    </div>
    <div class="flex justify-center text-center mx-auto mb-3">
        @if ($result)
            <div role="alert" class="alert alert-success">
                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6 shrink-0 stroke-current" fill="none"
                    viewBox="0 0 24 24">
                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2"
                        d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />
                </svg>
                <a target="_blank" class="link link-primary text-white" href="{{ $result }}"
                    id="result">{{ $result }}</span>
            </div>
        @endif
    </div>
    <div class="w-full text-center">
        <button wire:click="submit" class="btn btn-active btn-accent text-white">Shorten</button>
        <button
            @click.prevent="
      navigator.clipboard.writeText(document.getElementById('result').innerText)
        .then(() => {
            alert('Copied');
        })
        .catch(err => {
          alert('Failed to copy: ', err);
        })
    "
            class="btn btn-active btn-accent text-white">Copy</button>
    </div>
</div>

Và class ShortenUrl.php của mình sẽ như thế này:

class ShortenUrl extends Component
{
    public string $url;

    public string $result;

    public function rules()
    {
        return [
            'url' => [
                'required',
                'url'
            ],
        ];
    }

    public function submit()
    {
        $this->validate();
        $code = Str::random(7);
        $url = Url::firstOrCreate([
            'url' => $this->url,
        ], [
            'code' => $code
        ]);
        $this->result = $url->shortUrl;
    }

    public function render()
    {
        return view('livewire.shorten-url');
    }
}

Ok như vậy là mình xong phần UI và handle logic của livewire component. Có những phần mình cần giải thích như sau:

<input type="url" placeholder="Add Url here" class="input input-bordered w-full max-w-md" wire:model="url" />

Ở đây chúng ta dùng wire:model="url" để gán giá trị của input này vào property url trong class ShortenUrl.

@error('url')
      {{ $message }}
@enderror

Khi có lỗi khi validate property url thì chúng ta sẽ hiện lỗi ra ở đây.

        <button wire:click="submit" class="btn btn-active btn-accent text-white">Shorten</button>

Ở đây, chúng ta có wire:click="submit". Cái này để handle khi mà click vào nút này, thì sẽ chạy function submit trong class ShortenUrl. Trong function submit này thì chúng ta sẽ validate xem url có hợp lệ không. Nếu hợp lệ thì tạo url short mới và gán vào kết quả.

 <button
            @click.prevent="
      navigator.clipboard.writeText(document.getElementById('result').innerText)
        .then(() => {
            alert('Copied');
        })
        .catch(err => {
          alert('Failed to copy: ', err);
        })
    "
     class="btn btn-active btn-accent text-white">Copy</button>

Đây là phần chúng ta xử lý trong AlpineJS. Khi bấm vào nút Copy này, thì app sẽ kiếm đến nội dung của thẻ id result và copy nội dung vào Clipboard và show ra cho người dùng biết là nội dung đã được copy. Nếu mà fail thì log ra là fail. Với tính năng này, thì khi vào trang khác hoặc là vào một app khác, khi bấm Ctrl + V thì url mới sẽ được paste vào trang hoặc app mà người dùng đang dùng. Như vậy là chúng ta đã làm xong một app shorten url đơn giản bằng TALL stack.

Chạy app

Để chạy app, chúng ta sẽ chạy 2 lệnh cùng với nhau

php artisan serve

Để chạy Laravel app và chạy lệnh:

npm run dev

Để chạy mấy cái file trong resources.


All Rights Reserved

Viblo
Let's register a Viblo Account to get more interesting posts.