Fastify + Hotwire - nowy (lepszy?) pomysł na frontend

Aleksander Patschek

Czas czytania: 4 min
Liczba słów: 550
Data: 11-05-2021
Udostępnij:

Obstawiam, że 90% aktualnie tworzonych aplikacji opiera się na podział frontend, backend i przesył danych pomiędzy nimi w postaci JSON'a. Frontend prosi o dane i gdy je otrzymuje, to odpowiednio modyfikuje wygląd strony. A może można przesyłać coś innego niż JSON?

Co przesyłamy między przeglądarką a serwerem?

Frontend i backend muszą się jakoś komunikować. Jak to wyglądało na przestrzeni lat? Na początku backend i frontend były jednym organizmem. Backend tworzył pliki HTML i wysyłał do przeglądarki. Proste. Tylko od strony UI/UX było słabe, ponieważ każde zapytanie było równoznaczne z odświeżeniem strony.

Wtedy zaczęto rozdzielać frontend od backendu. Ciężar odświeżania danych jest teraz na frontendzie. Frontend tworzy UI, wysyła zapytania o dane i odświeża UI strony. Backend musi tylko zwrócić dane w postaci JSON'a. Nie interesuje go już, co zostanie z tymi danymi zrobione. Od strony UI/UX jest już spoko, ale są inne problemy - SEO, duplikowanie stanu, konieczność synchronizacji itp.

Teraz zaczynamy próbować podejścia hybrydowego. Mamy backend, który tworzy część widoków, a część zwraca jako kod JS'a by sam się sobą zajął. W Ruby pojawił się ostatnio inne podejście oparte o wysyłanie do frontendu fragmentów HTML'a, które trzeba zaktualizować. Oto Hotwire.

Czym jest Hotwire?

Hotwire (HTML over the wire) jest nowym pomysłem na przesyłanie danych pomiędzy frontendem a backendem. Został stworzony przez Basecamp i polega na wysyłaniu fragmentów HTML zamiast JSON'a. Dzięki temu, że strona jest budowana na backendzie, to ładuje się szybko i cały stan aplikacji jest trzymany w jednym miejscu. Co więcej, dzięki przesyłaniu fragmentów HTML'a nie musimy przeładować całego widoku, tylko pojedyncze elementy - tak jak w SPA.

Jak Hotwire wygląda w praktyce?

Do zabawy z Hotwire wykorzystałem Fastify. Aby to dodać wykorzystałem wtyczkę fastify-hotwire, która umożliwia prostą implementację tego rozwiązania.

npm i fastify-hotwire --save

Teraz zostaje kwestia dodania wtyczki do Fastify

fastify
    .register(import('fastify-hotwire'), {
        templates: join(__dirname, 'views'),
        filename: join(__dirname, 'worker.js')
    })
    .register(import('fastify-formbody'))

Drugą wtyczkę dodałem do poprawnej obsługi formularzy i odczytu danych z formularza. To, co musimy podać przy rejestracji wtyczki fastify-hotwire, to jest folder, gdzie będziemy trzymać widoki oraz plik odpowiedzialny za renderowanie plików. Mamy tutaj pełną dowolność, jeśli chodzi o wybór narzędzia do tworzenia widoków. Ja skorzystałem z ejs, który jest dosyć popularny, ale możesz skorzystać z dowolnej biblioteki.

import ejs from 'ejs'

export default async ({ file, data, fragment }) => {
   const body = await ejs.renderFile(file,data)

   return body
}

Cała aplikacja to bardzo podstawowa wersja TODO list - czyli jest możliwość dodawania zadań i ustawianie ich jako ukończonych. Jak to wygląda w praktyce?

Wyświetlanie głównego widoku

Na początek wyświetlenie wszystkich elementów. Aby to zrobić musimy obsłużyć zapytanie GET na adresie /.

fastify.get('/', async (req, reply) => {
    return reply.render('index.ejs', { tasks: tasks })
})

Dzięki zarejestrowaniu fastify-hotwire mamy dostępne w obiekcie reply dodatkowe metody. Podstawową z nich jest render, która renderuje całą stronę. Plik index.ejs prezentuje się następująco.

//index.ejs
<head>
    <script src="https://unpkg.com/@hotwired/[email protected]/dist/turbo.es5-umd.js"></script>
</head>

<form action="/task" method="POST" style="margin-top: 40px">
    <input name="content" type="text" />
    <button type="submit">Create New Task</button>
</form>

<%- include('tasks'); %>

To, co najważniejsze, to skrypt hotwire w nagłówku. Reszta plików prezentuje się następująco. Są odpowiedzialne za wyświetlenie listy zadań oraz wyświetlenie pojedynczego elementu.

//tasks.ejs

<table id='tasks'>
        <% tasks.forEach(function(task){ %>
                <%- include('task', {task: task}); %>
        <% }); %>
</table>
// task.ejs

<tr id="task_<%= task.id %>">
    <td style="width: 200px">
        <%= task.name %>
    </td>
    <td style="width: 200px">
        <%= task.status %>
    </td>
    <td style="width: 200px">
            <form action="/finish-task" method="POST" style="margin-bottom: 0;">
                <input value="<%= task.id %>" name='id' style="visibility: hidden; width: 0">
                <button type="submit">Finish</button>
            </form>
    </td>
</tr>

Dodawanie nowych zadań

Aby dodać nowe zadania, bez konieczności przeładowania strony, skorzystałem z Turbo Streams. Jest to dodatkowo wspierane przez użytą przeze mnie bibliotekę. Sprowadza się to do następującego kodu.

fastify.post('/task', async (req, reply) => {
    const newTask = { id: tasks.length, name: req.body.content, status: 'TODO' };
    tasks.push(newTask)

    return reply.turboStream.append(
        'task.ejs',
        'tasks',
        { task: newTask }
    )
})

Cały kod sprowadza się do wyrenderowania nowego zadania przy pomocy pliku task.ejs, oraz dodanie tego do elementu HTML, który ma id="tasks".

Zmiana statusu zadania

Tutaj jest podobnie. W pliku task.ejs jest mały formularz, który umożliwia zmianę statusu zadania. Kod na backendzie, który to umożliwia, wygląda następująco.

fastify.post('/finish-task', async (req, reply) => {
    const id = req.body.id;

    tasks[id] = { ...tasks[id], status: 'FINISHED' }

    return reply.turboStream.replace(
        'task.ejs',
        `task_${id}`,
        { task: tasks[id] }
    )
})

Tutaj wykorzystałem metodę replace. Wyszukuje ona element HTML o podanym id i zamienia zawartość.

Ogólnie koncept mi się podoba. Jest to kolejna próba połączenia frontendu i backendu do jednego projektu, ale z lepszym UX dla użytkownika. Myślę, że podobne koncepty będą się pojawiały coraz częściej. A ty co o tym sądzisz?

Jeśli podobał ci się ten artykuł to dołącz do newslettera. Dostaniesz dodatkowe treści do każdego postu oraz eksluzywne materiały, które pomogą ci pisac lepszy kod Chcę uzyskać dostęp do bonusów