Tworzenie pełnego frontendu w Vue z backendem Django
Tworzenie środowiska developerskiego Vue+Django
Vue oprócz opcji dołączania do projektu jako zewnętrzna biblioteka, oferuje też opcję stworzenia pełnego środowiska developerskiego, w którym następnie możemy zbudować naszą aplikację i dołączyć ją do aplikacji backendowej jako funkcjonalną całość.
Ponieważ frontend jest w dużej mierze niezależny od backendu w aplikacjach internetowych, nie ma Jednej Słusznej Metody integracji Vue i Django, dlatego zaproponujemy własny sposób pracy nad jedną i drugą stroną aplikacji.
Środowisko i serwer deweloperski Vue
Zaczniemy od stworzenia w folderze z naszą aplikacją Django podfolderu w którym zawrzemy frontend naszej aplikacji. W tym celu skorzystamy z zalecanej przez Vue metody tworzenia aplikcaji:
$ npm create vue@3
> npx
> create-vue
┌ Vue.js - The Progressive JavaScript Framework
│
◇ Project name (target directory):
│ frontend
│
◆ Select features to include in your project: (↑/↓ to navigate, space to select, a to toggle all, enter to confirm)
│ ◼ TypeScript
│ ◻ JSX Support
│ ◻ Router (SPA development)
│ ◻ Pinia (state management)
│ ◻ Vitest (unit testing)
│ ◻ End-to-End Testing
│ ◼ ESLint (error prevention)
│ ◼ Prettier (code formatting)
◇ Install Oxlint for faster linting? (experimental)
│ No
Scaffolding project in /home/student/mysite/frontend...
│
└ Done. Now run:
cd frontend
npm install
npm run format
npm run dev
| Optional: Initialize Git in your project directory with:
git init && git add -A && git commit -m "initial commit"
└
W przypadku maszyny wirtualnej przygotowanej pod te zajęcia wszystkie niezbędne pakiety powinny być dostępne w lokalnym cache npm
, więc można przyspieszyć tworzenie projektu dodając do komendy parametr --offline
$ npm create --offline vue@3
Następnym krokiem będzie instalacja niezbędnych pakietów zgodnie z instrukcją po stworzeniu podfolderu.
Jeżeli pracujemy na maszynie przygotowanej pod te zajęcia, można przyspieszyć pracę poprzez dodanie flagi --offline
$ cd frontend
$ npm --offline install
Przekierowanie żądań z serwera Vue do Django
W celu rozwoju naszej aplikacji frontendowej możemy uruchomić server developerski używając komendy npm run dev
jednak to da nam dostęp tylko do aplikacji frontendowej. W celu uruchomienia naszej backendowej aplikacji Django i przekierowania do niej ruchu musimy dodatkowo skonfigurować serwer frontendowy.
Po pierwsze chcemy przekierować część zapytań z serwera developerskiego naszego frontendu do serwera backendowego. Możemy to zrobić edytując plik konfiguracyjny frontend/vite.config.ts
.
1import { fileURLToPath, URL } from 'node:url'
2
3import { defineConfig } from 'vite'
4import vue from '@vitejs/plugin-vue'
5import vueDevTools from 'vite-plugin-vue-devtools'
6
7let proxyConfig = {
8 target: 'http://localhost:8000',
9 changeOrigin: false,
10 configure: (proxy, _options) => {
11 proxy.on('error', (err, _req, _res) => {
12 console.log('proxy error', err);
13 });
14 proxy.on('proxyReq', (proxyReq, req, _res) => {
15 console.log('Sending Request to the Target:', req.method, req.url);
16 });
17 proxy.on('proxyRes', (proxyRes, req, _res) => {
18 console.log('Received Response from the Target:', proxyRes.statusCode, req.url);
19 });
20 },
21}
22
23// https://vite.dev/config/
24export default defineConfig({
25 plugins: [
26 vue(),
27 vueDevTools(),
28 ],
29 resolve: {
30 alias: {
31 '@': fileURLToPath(new URL('./src', import.meta.url))
32 },
33 },
34 server: {
35 proxy: {
36 '/polls': proxyConfig,
37 '/static': proxyConfig,
38 }
39 },
40})
W liniach 36 i 37 definiujemy przekierowanie ścieżek /polls
i /static
do serwera na którym będzie działała nasza aplikacja Django. Obie ścieżki będą nam potrzebne aby aplikacja poprawnie działała i się renderowała jak należy. W konfiguracji w linii 9 wskazujemy adres i port serwera z aplikacją Django. W liniach 11-19 dodatkowo konfigurujemy logowanie przekierowywania żądań, co będzie nam potrzebne za chwilę, kiedy będziemy uruchamiać oba serwery jedną komendą.
Na chwilę obecną możemy uruchomić oba serwery w dwóch osobnych terminalach i wejście na adres wskazywany przez serwer developerski Vue na ścieżkę /polls
powinno przekierować nas do znanej nam aplikacji. Po weściu na ścieżkę /
powinna nam się pokazać aplikacja startowa Vue.
Uruchamianie obu serwerów jedną komendą
W celu uruchomienia dwóch serwerów jedną komendą będziemy potrzebowali zainstalować dodatkowy pakiet npm, mianowicie concurrently
. Możemy to zrobić następującą komendą w katalogu frontend
:
$ cd frontend
$ npm install --save-dev concurrently
Następnie zmodyfikujemy plik package.json
i dodamy do niego następujące komendy npm
:
1{
2 "name": "frontend",
3 "version": "0.0.0",
4 "private": true,
5 "type": "module",
6 "scripts": {
7 "dev": "concurrently --kill-others --handle-input --raw --hide 1 \"npm:dev:frontend\" \"npm:dev:backend\"",
8 "dev:frontend": "vite",
9 "dev:backend": "cd .. && ./manage.py runserver",
10 "build": "run-p type-check \"build-only {@}\" --",
11 "preview": "vite preview",
12 "build-only": "vite build",
13 "type-check": "vue-tsc --build",
14 "lint": "eslint . --fix",
15 "format": "prettier --write src/"
16 },
17 "dependencies": {
18 "vue": "^3.5.13"
19 },
20 "devDependencies": {
21 "@tsconfig/node22": "^22.0.0",
22 "@types/node": "^22.13.9",
23 "@vitejs/plugin-vue": "^5.2.1",
24 "@vue/eslint-config-prettier": "^10.2.0",
25 "@vue/eslint-config-typescript": "^14.5.0",
26 "@vue/tsconfig": "^0.7.0",
27 "concurrently": "^9.1.2",
28 "eslint": "^9.21.0",
29 "eslint-plugin-vue": "~10.0.0",
30 "jiti": "^2.4.2",
31 "npm-run-all2": "^7.0.2",
32 "prettier": "3.5.3",
33 "typescript": "~5.8.0",
34 "vite": "^6.2.1",
35 "vite-plugin-vue-devtools": "^7.7.2",
36 "vue-tsc": "^2.2.8"
37 }
38}
W linii 7 definiujemy komendę, która uruchomi jednocześnie oba serwery. Jeżeli jeden z nich zakończy pracę, to drugi zostanie automatycznie zamknięty (--kill-others
), ukryty zostanie output z serwera Django (--hide 1
) a wszelki input od użytkownika będzie przekierowany do pierwszego z procesów (--handle-input --raw
).
Teraz przy pomocy następujących komend powinniśmy móc uruchomić oba serwery:
$ cd frontend
$ npm run dev
Czyszczenie aplikacji startowej Vue
Celem dzisiejszej lekcji będzi odtworzenie strony głównej naszej aplikacji od ankiet w Vue. W tym celu zaczniemy od wyrzucenia z aplikacji startowej wszystkich utworzonych komponentów i kodu demonstracyjnego.
Usuniemy wszystkie pliki z folderu frontend/src/components/
i usuniemy niemalże caly kod z pliku frontend/src/components/App.Vue
1<script setup>
2</script>
3
4<template>
5</template>
6
7<style scoped>
8</style>
W katalogu frontend/src/assets
pozostawimy tylko pusty plik main.css
Odtworzenie statycznych części HTML i CSS w Vue
Aby odtworzyć stronę z listą dostępnych ankiet zaczniemy od modyfikacji pliku frontend/index.html
i upewnimy się, żeby wyglądał możliwie podobnie do pliku polls/templates/polls/base.html
, w szczególności chcemy dodać do głównego elementu <div>
klasę CSS container
aby nasze style poprawnie aplikowały się do reszty aplikacji.
Na tym etapie nie dodajemy żadnych dyrektyw renderowania Vue czy innych elementów szablonów. Nasza aplikacja po załadowaniu wypełni treścią pusty obecnie element <div>
1<!doctype html>
2<html lang="en">
3 <head>
4 <meta charset="UTF-8" />
5 <link rel="icon" href="/favicon.ico" />
6 <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7 <title>Awesome polls page</title>
8 </head>
9 <body>
10 <div class="container" id="app"></div>
11 <script type="module" src="/src/main.ts"></script>
12 </body>
13</html>
Następnie przekopiujemy zawartość pliku polls/static/polls/style.css
do frontend/src/assets/main.css
Ostatnim elementem będzie odtworzenie zawartości szablonu polls/templates/polls/list.html
w pliku frontend/src/App.vue
1<script setup>
2</script>
3
4<template>
5 <h2>Recent polls:</h2>
6 <ul>
7 <li><a href="#">Tytuł ankiety</a></li>
8 </ul>
9 <a href="/polls/new" class="new-poll-button">Add new poll</a>
10</template>
11
12<style scoped>
13</style>
Dynamiczne generowanie treści w komponencie Vue
Przedostatnim elementem będzie wygenerowanie linków do ankiet na podstawie przykładowych danych obecnych w JavaScriptowym bloku <script>
. Podczas poprzedniej lekcji korzystaliśmy z dyrektyw v-for
i v-bind
(w postaci skrótu :
) oraz wypełniania elementów z wykorzystaniem bloków {{ }}
. Wykorzystując tą wiedzę, możemy np. zaimplementować nasz komponent Vue w ten sposób:
1<script setup>
2import { ref } from "vue";
3
4const pollList = ref([
5 {
6 "id": 1,
7 "question": "Cats or dogs?",
8 },
9 {
10 "id": 2,
11 "question": "What's the best programming language?",
12 },
13]);
14</script>
15
16<template>
17 <h2>Recent polls:</h2>
18 <ul>
19 <li v-for="poll in pollList"><a :href="`/polls/${poll.id}`">{{ poll.question }}</a></li>
20 </ul>
21 <a href="/polls/new" class="new-poll-button">Add new poll</a>
22</template>
23
24<style scoped>
25</style>
Przygotowanie API dla frontendu
Żeby nasz frontend miał co pobrać, nasz serwer musi być w stanie zwrócić odpowiednie dane. Teoretycznie nic nie stoi na przeszkodzie, aby wysłać żądanie do jednej z już obsługiwanych ścieżek i wydobyć potrzebne nam dane zaszyte w zwróconym dokumencie HTML. Dużo łatwiej z perspektywy rozwoju części frontendowej będzie przygotować po stronie serwera dane w formie prostszej, np. w formacie JSON tak jak widzimy w powyższym listingu frontend/src/components/App.Vue
w liniach 4-13.
Django pozwala nam na zwrócenie z widoku danych w formacie JSON w prosty sposób używając klasy JSONResponse
. Użyjemy go w naszym pliku implementującym widoki:
1# ...
2from django.http import HttpResponseRedirect, JsonResponse
3
4# ...
5
6def api_polls(request):
7 poll_queryset = Poll.objects.order_by("-pub_date")[:5]
8 poll_list = [{"question": poll.question_text, "id": poll.pk} for poll in poll_queryset]
9 return JsonResponse({"pollList": poll_list})
10
11# ...
Jak widać implementacja nie jest bardzo skomplikowana, wymaga tylko przygotowania odpowiedniego zapytania do bazy danych oraz wyciągnięcia odpowiednich danych do prostej zmiennej słownikowej. Z czasem pewnie rozbudujemy nasze API, ale chwilowo tyle powinno wystarczyć. Dodajmy jeszcze nasz widok do listy obsługiwanych ścieżek.
1# ...
2urlpatterns = [
3# ...
4 path('api/polls', views.api_polls, name="api_polls")
5# ...
6]
Nawiązanie komunikacji między frontendem a backendem
W kodzie JavaScript mamy możliwośc wysyłania żądań do innych podstron na naszym serwerze, albo do dowolnego innego serwera z którego chcielibyśmy pobrać jakieś dane. Historycznie jedynym sposobem wysyłania takich żądań było API XMLHTTPRequest, z którego nadal można korzystać, natomiast łatwiejszą alternatywą powinno być API Fetch korzystające z nowszych elementów języka JavaScript.
Wykorzystując API Fetch nasza naiwna implementacja pobierania listy dostępnych ankiet może wyglądać następująco:
1<script setup>
2import { ref, onMounted } from "vue";
3
4const pollList = ref([]);
5
6onMounted(() => {
7 fetch("/polls/api/polls")
8 .then((response) => response.json())
9 .then((data) => {
10 pollList.value = data.pollList;
11 })
12});
13</script>
14
15<!-- ... -->
Po zmodyfikowaniu w ten sposób naszej aplikacji Vue, po wejściu na śieżkę główną naszej strony powinna pokazać nam się strona bliźniaczo podobna do tej ze ścieżki /polls
.
Podsumowanie
Odtworzyliśmy w ten sposób stronę startową naszej aplikacji. W kolejnch lekcjach odtworzymy i rozbudujemy podstronę służącą do głosowania w ankietach, tworzenia nowych ankiet a także rozprawimy się z logowaniem użytkownika po stronie frontendowej.