08 β Data Fetching & Server (Nitro)¶
β¬ οΈ Oldingi: 07 β Nuxt asoslari Β· π README Β· Keyingi: 09 β Nuxt UI β‘οΈ
Bu modul Nuxt'ning eng kuchli va eng "fullstack" qismi. Backend odam uchun aynan shu yer qiziq: Nuxt faqat frontend emas β uning ichida Nitro degan to'liq server engine bor. Ya'ni bitta loyihada ham Vue app, ham API yozasan. Laravel termini bilan: bu deyarli mini-Laravel frontend bilan birga keladi.
Modul 2 qismdan iborat:
1. Client/Universal data fetching β useFetch, useAsyncData, $fetch
2. Server qismi (Nitro) β server/api/, middleware, useState
1. Muammo: nega oddiy fetch yetarli emas?¶
SSR'da kod ikki marta ishlaydi: bir marta serverda (birinchi HTML render), bir marta clientda (hydration va keyingi navigatsiya). Endi savol: ma'lumotni qayerda olamiz?
Agar React'dagidek onMounted ichida fetch qilsang:
<script setup>
const users = ref([])
onMounted(async () => {
users.value = await fetch('/api/users').then(r => r.json())
})
</script>
Muammolar:
- onMounted faqat clientda ishlaydi β serverda HTML bo'sh keladi β SEO yo'q, "loading..." miltillaydi (FOUC).
- Bot/Google sahifani ko'rganda ma'lumot yo'q.
- Server qilgan ishni client yana takrorlaydi.
Nuxt buni useFetch / useAsyncData bilan hal qiladi: kod serverda ishlaydi, natija HTML bilan birga clientga "payload" sifatida uzatiladi, client uni qayta yuklamaydi. Bu β universal (isomorphic) data fetching.
Quyidagi diagramma bu oqimni bosqichma-bosqich ko'rsatadi: server data oladi va HTML render qiladi, brauzer uni darrov ko'rsatadi, keyin hydration paytida payloaddan o'qib qayta fetch qilmaydi.
Laravel analogiyasi. Controller
User::all()ni olib,view()ga uzatadi β server ma'lumot bilan tayyor HTML yuboradi.useFetchham xuddi shu, lekin keyin SPA sifatida client-side davom etadi. Ikki dunyoning yaxshisi.
2. useFetch β asosiy ish quroli¶
Eng ko'p ishlatadigan composable. Setup'ning yuqori qismida (top-level) chaqiriladi.
<script setup>
const { data, pending, error, refresh } = await useFetch('/api/users')
</script>
<template>
<p v-if="pending">Yuklanmoqda...</p>
<p v-else-if="error">Xato: {{ error.message }}</p>
<ul v-else>
<li v-for="u in data" :key="u.id">{{ u.name }}</li>
</ul>
</template>
Qaytaradi:
| Maydon | Nima |
|---|---|
data |
natija (Ref) |
pending |
true β yuklanmoqda (boolean Ref) |
error |
xato bo'lsa error obyekti, aks holda null |
refresh() |
qayta yuklash funksiyasi |
status |
'idle' \| 'pending' \| 'success' \| 'error' |
execute() |
lazy/immediate:false bo'lganda qo'lda ishga tushirish |
clear() |
data'ni tozalash |
Muhim opsiyalar¶
const { data } = await useFetch('/api/products', {
query: { page: 1, limit: 20 }, // ?page=1&limit=20
method: 'GET',
headers: { 'X-Custom': 'value' },
lazy: false, // false = navigatsiyani bloklab kutadi; true = bloklamaydi
server: true, // false = faqat clientda fetch qil (SSR'da o'tkazib yubor)
immediate: true, // false = avtomatik chaqirmaydi, execute() kutadi
default: () => [], // data hali yo'q paytda boshlang'ich qiymat
watch: [page], // shu ref o'zgarsa avtomatik refetch
key: 'products', // dedupe / cache kaliti
})
transform va pick β payload'ni kichraytirish¶
useFetch natijasi HTML bilan clientga uzatiladi (payload). Agar API katta obyekt qaytarsa, hammasi client'ga ketadi β sahifa og'irlashadi. Faqat kerakligini olib qol:
// pick β faqat shu maydonlar payloadga tushadi
const { data } = await useFetch('/api/user', {
pick: ['id', 'name', 'avatar'],
})
// transform β server javobini o'zgartirib, kichraytirib saqlash
const { data } = await useFetch('/api/products', {
transform: (res) => res.items.map(p => ({ id: p.id, title: p.title })),
})
Why. Bu performance uchun muhim: API'da 50 ta ustun bo'lsa-yu, sahifada 3 tasi kerak bo'lsa β qolgan 47 tasini client'ga uzatish behuda trafik.
pick/transformpayloadni qisqartiradi.
3. useAsyncData β nazorat kerak bo'lganda¶
useFetch aslida useAsyncData ustiga sugar:
// Bu ikkalasi deyarli bir xil
useFetch('/api/users')
useAsyncData('users', () => $fetch('/api/users'))
useAsyncData(key, handler) ni qachon ishlatasan:
- Handler ichida bir nechta so'rov birlashtirish kerak bo'lsa.
- HTTP bo'lmagan async logika (masalan, local DB, SDK chaqiruvi).
- O'zing $fetch chaqiruvini to'liq nazorat qilmoqchi bo'lsang.
const { data } = await useAsyncData('dashboard', async () => {
const [stats, recent] = await Promise.all([
$fetch('/api/stats'),
$fetch('/api/recent'),
])
return { stats, recent }
})
Mental model.
useFetch= "shu URL'ni ur".useAsyncData= "men o'zim yozaman, sen faqat SSR/cache/dedupe'ni boshqar". Birinchi argument key β u bo'yicha dedupe va cache ishlaydi.
4. $fetch β action'lar uchun¶
$fetch β haqiqiy HTTP client (ofetch kutubxonasi). JSON'ni avtomatik parse qiladi, base URL biladi.
Qoida (juda muhim):
| Holat | Nima ishlatasan |
|---|---|
| Sahifa yuklanganda data olish (top-level) | useFetch / useAsyncData |
| Foydalanuvchi harakati: tugma bosish, forma yuborish (POST/PUT/DELETE) | $fetch |
<script setup>
async function createPost() {
const post = await $fetch('/api/posts', {
method: 'POST',
body: { title: title.value, content: content.value },
})
// ro'yxatni yangilash
await refresh()
}
</script>
β Xato: setup'ning yuqorisida
const data = await $fetch('/api/users')yozish. Bu SSR'da ishlaydi-yu, lekin dedupe/payload bo'lmaydi β client hydration paytida yana fetch qiladi (ikki marta!). Top-level data uchun doimuseFetch/useAsyncData.$fetchβ faqat event handler ichida.
Bu qarorni quyidagi diagramma bilan mustahkamlab oling β qaysi vaziyatda qaysi quroldan foydalanish kerakligini ko'rsatadi.
5. β οΈ Nuxt 4 gotcha: data endi shallowRef¶
Nuxt 4'da useFetch/useAsyncData qaytaradigan data β shallowRef (ilgari oddiy ref edi). Ya'ni faqat .value ni qayta tayinlash reaktivlikni ishga soladi; ichidagi nested propertyni o'zgartirish ishlamaydi:
const { data } = await useFetch('/api/todos')
// β Ishlamaydi (shallowRef nested o'zgarishni kuzatmaydi)
data.value.push(newTodo)
// β
To'g'ri β qayta tayinla
data.value = [...data.value, newTodo]
Why. Bu ataylab qilingan performance optimizatsiyasi β katta data obyektini deep-reactive qilish qimmat. Agar deep reaktivlik kerak bo'lsa:
useFetch(url, { deep: true }).
6. Xatolarni boshqarish¶
<script setup>
const { data, error } = await useFetch('/api/user/123')
// Agar 404 kerak bo'lsa β error.vue sahifasiga o'tkazish
if (error.value) {
throw createError({
statusCode: 404,
statusMessage: 'Foydalanuvchi topilmadi',
fatal: true, // fatal:true β error.vue ko'rsatiladi
})
}
</script>
app/error.vue β global xato sahifasi (Laravel'dagi resources/views/errors/404.blade.php analogi):
<!-- app/error.vue -->
<script setup>
defineProps({ error: Object })
const handleError = () => clearError({ redirect: '/' })
</script>
<template>
<div>
<h1>{{ error.statusCode }}</h1>
<p>{{ error.statusMessage }}</p>
<button @click="handleError">Bosh sahifaga</button>
</div>
</template>
7. Cookie va SSR autentifikatsiya¶
SSR'da bitta nozik masala bor: server useFetch qilganda brauzer cookie'larini avtomatik uzatmaydi. Ya'ni login bo'lgan userning tokeni serverdagi so'rovga ilashmaydi. Qo'lda uzatish kerak:
<script setup>
// SSR paytida kelgan cookie'ni keyingi API so'roviga uzatamiz
const headers = useRequestHeaders(['cookie'])
const { data } = await useFetch('/api/me', { headers })
</script>
useCookie β SSR-safe reaktiv cookie (ham serverda, ham clientda ishlaydi):
const token = useCookie('auth_token', {
maxAge: 60 * 60 * 24 * 7, // 7 kun
httpOnly: false, // JS o'qishi kerak bo'lsa
sameSite: 'lax',
})
token.value = 'eyJ...' // o'rnatish
Laravel analogiyasi.
useCookieβcookie()helper + reaktiv.useRequestHeaders(['cookie'])β kiruvchiRequestheaderlarini keyingi internal so'rovga forward qilish (gateway/proxy pattern).
β‘ 2-QISM: Server (Nitro)¶
Mana eng qiziq joy. server/ papkasi β bu Nitro engine ostida ishlaydigan to'liq backend. Bu yerda API endpoint, middleware, hatto WebSocket yozasan. Nitro standalone deploy bo'ladi: Node, serverless, edge (Cloudflare Workers, Vercel Edge) β hamma joyda.
server/
βββ api/ β /api/* endpointlar
βββ routes/ β /* (api prefiksisiz route'lar, masalan /sitemap.xml)
βββ middleware/ β har bir so'rovda ishlaydigan server middleware
βββ plugins/ β Nitro plugin (startup hooks)
βββ utils/ β auto-import bo'ladigan server helperlar
Why bu backend odamga muhim. EduCore'da haqiqiy backend Laravel'da bo'lishi mumkin. Lekin Nuxt server qismi BFF (Backend-for-Frontend) sifatida ishlaydi: Laravel API'ni proxy qiladi, maxfiy kalitlarni yashiradi, auth cookie'larini boshqaradi, bir nechta so'rovni birlashtiradi. Frontend hech qachon to'g'ridan-to'g'ri tashqi API kalitini ko'rmaydi.
8. Server API routes β server/api/¶
File-based, xuddi sahifalar kabi:
// server/api/hello.ts β GET /api/hello
export default defineEventHandler((event) => {
return { message: 'Salom EduCore' } // avtomatik JSON bo'ladi
})
defineEventHandler = controller method. event = Request/Response o'rami.
Event utility'lari (eng kerakli)¶
export default defineEventHandler(async (event) => {
const query = getQuery(event) // ?page=1 β { page: '1' }
const id = getRouterParam(event, 'id') // /api/users/5 β '5'
const body = await readBody(event) // POST body (JSON)
const auth = getHeader(event, 'authorization')
const token = getCookie(event, 'auth_token')
setResponseStatus(event, 201)
setCookie(event, 'session', 'abc', { httpOnly: true })
return { ok: true }
})
Laravel β Nitro lug'ati¶
| Nitro | Laravel ekvivalenti |
|---|---|
server/api/users.ts |
routes/api.php + Controller (bitta faylda) |
defineEventHandler(fn) |
Controller __invoke() |
getQuery(event) |
$request->query() |
readBody(event) |
$request->all() / $request->json() |
getRouterParam(event, 'id') |
route {id} parametri |
getHeader(event, 'x') |
$request->header('x') |
setResponseStatus(event, 201) |
response()->json($d, 201) |
createError({ statusCode }) |
abort(404) |
Dynamic va method-based routing¶
// server/api/users/[id].ts β GET /api/users/:id
export default defineEventHandler((event) => {
const id = getRouterParam(event, 'id')
return { id }
})
Fayl nomiga method qo'shib, REST resource yasaysan:
server/api/posts/
βββ index.get.ts β GET /api/posts (ro'yxat)
βββ index.post.ts β POST /api/posts (yaratish)
βββ [id].get.ts β GET /api/posts/:id (bitta)
βββ [id].put.ts β PUT /api/posts/:id (yangilash)
βββ [id].delete.ts β DELETE /api/posts/:id (o'chirish)
Bu Laravel'ning
Route::apiResource('posts', ...)ning fayl-tizimdagi ko'rinishi. Har bir verb β alohida fayl, alohida handler.
BFF misoli β maxfiy kalit bilan tashqi API'ni proxy qilish¶
// server/api/courses.get.ts
export default defineEventHandler(async (event) => {
const config = useRuntimeConfig() // serverda private kalitlar mavjud
const data = await $fetch('https://api.educore.uz/courses', {
headers: { Authorization: `Bearer ${config.apiSecret}` },
})
return data
})
Endi clientdagi useFetch('/api/courses') shu Nitro endpointga uradi, apiSecret esa hech qachon brauzerga chiqmaydi. Bu β to'g'ri arxitektura.
Quyidagi diagramma klient-server ma'lumot oqimini ko'rsatadi: client faqat o'z /api/courses endpointini biladi, Nitro esa maxfiy kalit bilan tashqi API'ni proxy qiladi.
9. Server middleware β server/middleware/¶
Har bir server so'rovida, route handlerdan oldin ishlaydi. Hech narsa return qilmaydi β faqat event.context ga ma'lumot biriktiradi yoki xato tashlaydi.
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const token = getCookie(event, 'auth_token')
if (token) {
event.context.user = verifyToken(token) // keyingi handlerlar o'qiy oladi
}
})
Keyin istalgan API handlerda:
// server/api/me.get.ts
export default defineEventHandler((event) => {
if (!event.context.user) {
throw createError({ statusCode: 401, statusMessage: 'Avtorizatsiya yo\'q' })
}
return event.context.user
})
Laravel analogiyasi. Server middleware β global middleware (
app/Http/Kernel.phpda ro'yxatdan o'tgan).event.contextβ request'ga bog'langan ma'lumot ($request->user()ni middleware o'rnatgani kabi). Har bir so'rovda ishlaydi.
10. Route middleware β app/middleware/ (server middleware EMAS!)¶
Diqqat: bu boshqa narsa. Route middleware β navigatsiya guard'i (05-moduldagi Vue Router guard'ining Nuxt versiyasi). Ham serverda, ham clientda, sahifaga kirishdan oldin ishlaydi.
// app/middleware/auth.ts
export default defineNuxtRouteMiddleware((to, from) => {
const user = useAuthStore() // yoki useCookie/useState
if (!user.isLoggedIn) {
return navigateTo('/login')
}
})
Sahifada ulash:
Turlari:
- Named β app/middleware/auth.ts, definePageMeta orqali ulanadi.
- Global β app/middleware/analytics.global.ts, har bir navigatsiyada avtomatik.
- Inline β definePageMeta({ middleware: [(to) => {...}] }).
Ikkalasini ADASHTIRMA β eng muhim jadval¶
Route middleware (app/middleware/) |
Server middleware (server/middleware/) |
|
|---|---|---|
| Qachon | Sahifaga navigatsiyadan oldin | Har bir HTTP so'rovda |
| Qayerda | Client + server (navigatsiya) | Faqat server |
| Funksiya | defineNuxtRouteMiddleware |
defineEventHandler |
| Vazifa | Redirect, access control (UI) | Auth context, logging, CORS, headers |
| Laravel analogi | route'ga ulangan middleware (->middleware('auth')) |
global HTTP middleware (Kernel) |
Why ikkitaligi. Sahifa access controli (login bo'lmasa
/loginga ot) β bu navigatsiya darajasi β route middleware. API so'rovni himoyalash, har request'da token tekshirish β bu server darajasi β server middleware. Ko'pincha ikkalasi birga kerak.
11. useState β SSR-safe shared state¶
Bu yer backend odam uchun xavfsizlik nuqtai nazaridan juda muhim.
β Hech qachon shunday qilma:
// composables/counter.js β XATARLI
const count = ref(0) // module-scope ref
export const useCounter = () => count
Nega xatarli? Serverda bitta process minglab foydalanuvchiga xizmat qiladi. Module-scope ref esa hamma so'rovlar uchun bitta β ya'ni A foydalanuvchining ma'lumoti B foydalanuvchiga leak bo'ladi. Bu jiddiy xavfsizlik bug'i (cross-request state pollution).
β
To'g'ri yo'l β useState:
// SSR-safe, har bir so'rov uchun alohida, clientga serialize bo'ladi
const counter = useState('counter', () => 0)
const user = useState('user', () => null)
<script setup>
const count = useState('count', () => 0)
</script>
<template>
<button @click="count++">{{ count }}</button>
</template>
Laravel analogiyasi. Module-scope
ref=staticpropertyda request ma'lumotini saqlash (har request'da bir xil obyekt β leak).useState= request lifecycle'ga bog'langan, har so'rovda yangi (Laravel'da har request yangi container/instance bo'lgani kabi). Singleton vs request-scoped farqi.
useState vs Pinia¶
useState |
Pinia (06-modul) |
|---|---|
| Oddiy shared SSR state | Murakkab app state |
| Faqat qiymat | State + getters + actions + plugins |
| Tezkor, kichik holatlar | Auth, cart, tenant kabi to'liq domenlar |
Kichik narsaga useState, katta domenga Pinia. EduCore'da: theme toggle β useState; auth/tenant/cart β Pinia store.
12. Nitro qo'shimchalari (qisqacha, hero darajasi uchun)¶
routeRules β nuxt.config.ts da deklarativ rendering/cache/proxy qoidalari:
export default defineNuxtConfig({
routeRules: {
'/': { prerender: true }, // build'da static
'/admin/**': { ssr: false }, // SPA (faqat client)
'/blog/**': { isr: 3600 }, // ISR β har soatda regenerate
'/api/legacy/**': { proxy: 'https://old.educore.uz/**' }, // proxy
},
})
Cached event handler β server javobini keshlash:
// server/api/stats.get.ts
export default defineCachedEventHandler(async () => {
return await computeHeavyStats()
}, { maxAge: 60 }) // 60 soniya kesh
useStorage β Nitro'ning built-in KV/storage qatlami (Redis, FS, memory):
const storage = useStorage('redis')
await storage.setItem('key', value)
const v = await storage.getItem('key')
Bularning hammasi alohida chuqur mavzu (rendering modes, Nitro deep) β keyingi to'plamda. Hozircha mavjudligini bil.
13. EduCore arxitekturasi β hammasi birga¶
Multi-tenant SaaS uchun tipik Nuxt server oqimi:
// server/middleware/tenant.ts β subdomain'dan tenant aniqlash
export default defineEventHandler((event) => {
const host = getHeader(event, 'host') || ''
const subdomain = host.split('.')[0] // markaz.educore.uz β "markaz"
event.context.tenant = subdomain
})
// server/api/students.get.ts β tenant bo'yicha scoping
export default defineEventHandler(async (event) => {
const tenant = event.context.tenant
const config = useRuntimeConfig()
return await $fetch(`${config.apiBase}/students`, {
headers: {
'X-Tenant': tenant,
Authorization: `Bearer ${config.apiSecret}`,
},
})
})
// app/middleware/auth.global.ts β UI darajasida himoya
export default defineNuxtRouteMiddleware((to) => {
const auth = useAuthStore()
const publicPages = ['/login', '/register']
if (!auth.isLoggedIn && !publicPages.includes(to.path)) {
return navigateTo('/login')
}
})
To'liq oqim:
Brauzer β markaz.educore.uz
β
server/middleware/tenant.ts (tenant'ni context'ga qo'yadi)
β
server/middleware/auth.ts (token tekshiradi)
β
useFetch('/api/students')
β
server/api/students.get.ts (Laravel API'ni tenant header bilan proxy)
β
Laravel backend (tenant_id scoping, DDD)
Frontend hech qachon apiSecret'ni ko'rmaydi, tenant aniqlash serverda β bu xavfsiz va to'g'ri arxitektura.
Xulosa β mental model¶
DATA OLISH:
Sahifa yuklanishi (top-level) β useFetch / useAsyncData
Foydalanuvchi harakati (POST) β $fetch
SERVER:
server/api/ β endpointlar (defineEventHandler)
server/middleware/ β har so'rovda (auth context, logging)
app/middleware/ β navigatsiya guard (defineNuxtRouteMiddleware)
STATE:
Oddiy SSR state β useState (module-scope ref ASLO!)
Murakkab domen β Pinia
Eng ko'p qilinadigan 3 xato:
1. Top-level'da $fetch ishlatib, ikki marta fetch qilish (useFetch kerak).
2. Server'da module-scope ref β cross-request leak (useState kerak).
3. Route middleware bilan server middleware'ni adashtirish.
π― Masalalar (24 ta)¶
npx nuxi init data-labbilan loyiha och. Server qismi uchunserver/api/ichida soxta (mock) data qaytaruvchi endpointlar yozib, frontend bilan ulab mashq qil.
A daraja β data fetching¶
- β
/userssahifa yarat,useFetch('https://jsonplaceholder.typicode.com/users')bilan ro'yxat chiqar.pendingvaerrorholatlarini ham ko'rsat. - β
Yuqoridagi so'rovga
pick: ['id', 'name', 'email']qo'sh. DevTools β payload hajmi qanday o'zgardi, kuzat. - β
transformbilan javobni faqat{ id, name }massiviga aylantir. - β
β
/postssahifa:?pagequery bilan paginatsiya.pageref'iniwatchopsiyasiga ber β sahifa o'zgarsa avtomatik refetch bo'lsin. - β
β
"Yangilash" tugmasi qo'y,
refresh()ni chaqir. Tugma bosilgandapendingko'rsatilsinmi? Tekshir. - β
β
lazy: truevalazy: falsefarqini his qil: ikki sahifa yasab, navigatsiyada qaysi biri "kutadi", qaysi biri darrov ochiladi β kuzat. - β Nima uchun setup top-level'da
const d = await $fetch('/api/x')yomon? O'z so'zing bilan yoz. β - β
β
useAsyncData('dash', ...)bilan ikki endpointniPromise.allorqali bitta composable'da birlashtir.
B daraja β actions va xatolar¶
- β
β
Forma yarat (title, body), "Saqlash" tugmasi
$fetch('/api/posts', { method:'POST', body })qilsin. Javobni konsolga chiqar. β - β
β
POST muvaffaqiyatli bo'lgach, ro'yxatni
refresh()bilan yangila. - β
β
[id].vuesahifadauseFetch('/api/posts/' + id). ID mavjud bo'lmasacreateError({ statusCode: 404 })tashla. - β
β
app/error.vueyarat, 404 va 500 ni chiroyli ko'rsat, "Bosh sahifaga" tugmasiclearErrorchaqirsin. - β Nuxt 4'da
data.value.push(x)nega UI'ni yangilamaydi? Qanday to'g'rilaysan? β - β
β
β
Optimistic update: tugma bosilganda avval UI'ni yangila (
data.value = [...]), keyin$fetchPOST qil. Xato bo'lsa orqaga qaytar (rollback). (06-moduldagi Pinia versiyasi bilan solishtir.)
C daraja β server (Nitro)¶
- β
server/api/ping.tsyarat,{ pong: true, time: Date.now() }qaytarsin. Brauzerda/api/pingni och. β - β
β
server/api/users/[id].get.tsβgetRouterParambilan ID'ni olib{ id, name: 'User ' + id }qaytar. - β
β
server/api/echo.post.tsβreadBodybilan kelgan JSON'ni o'qib, qaytarib yubor (echo).$fetchbilan POST qilib sina. - β
β
Method-based resource:
server/api/todos/ichidaindex.get.ts,index.post.ts,[id].delete.tsyoz. In-memory massivda saqla (server qayta ishga tushguncha yashaydi). - β
β
β
BFF:
server/api/weather.get.tsβuseRuntimeConfigdan API kalit olib, tashqi ob-havo API'sini proxy qil. Kalit clientga chiqmasligiga ishonch hosil qil (DevTools β Network).
D daraja β middleware va state¶
- β
β
server/middleware/logger.tsβ har so'rovdaconsole.log(event.method, event.path)chiqarsin. Bir nechta sahifa ochib, terminalda log'ni kuzat. β - β
β
server/middleware/auth.tsβ cookie'dan token o'qibevent.context.userga qo'ysin.server/api/me.get.tsda contextdan o'qib qaytar, yo'q bo'lsa 401. - β
β
app/middleware/guest.tsβ agaruseAuthStore().isLoggedInbo'lsa/dashboardga yo'naltir (login sahifasiga ulang). - β
useState('x', () => 0)vaapp/middleware/dagi route middleware β bittasi qaysi muammoni hal qiladi, ikkinchisi qaysisini? Farqini yoz. - β
β
β
EduCore mini-BFF.
server/middleware/tenant.ts(host'dan subdomain βevent.context.tenant) +server/api/students.get.ts(tenant'ni header qilib mock API qaytaradi) +app/middleware/auth.global.ts(login bo'lmasa/login). Uchalasini ulab, oqimni ishlatib ko'r. β
β Tanlangan yechimlar¶
7 β Nega top-level $fetch yomon
Top-level $fetch SSR'da bir marta serverda ishlaydi va HTML hosil qiladi. Lekin uning natijasi payload sifatida saqlanmaydi va key bilan dedupe qilinmaydi. Shu sababli client hydration paytida Vue yana o'sha kodni ishga tushiradi β ikkinchi marta fetch bo'ladi. Natija: ikki barobar so'rov, ortiqcha yuk, ba'zan hydration mismatch.
useFetch/useAsyncData esa serverdagi natijani __NUXT__ payloadga joylaydi; client uni o'qiydi, qayta fetch qilmaydi. Shuning uchun: top-level data β har doim useFetch/useAsyncData; $fetch β faqat event handler (action) ichida.
9 β POST forma
<script setup>
const title = ref('')
const body = ref('')
const result = ref(null)
const saving = ref(false)
async function save() {
saving.value = true
try {
result.value = await $fetch('https://jsonplaceholder.typicode.com/posts', {
method: 'POST',
body: { title: title.value, body: body.value, userId: 1 },
})
} catch (e) {
console.error('Xato:', e)
} finally {
saving.value = false
}
}
</script>
<template>
<input v-model="title" placeholder="Sarlavha" />
<textarea v-model="body" placeholder="Matn" />
<button :disabled="saving" @click="save">
{{ saving ? 'Saqlanmoqda...' : 'Saqlash' }}
</button>
<pre v-if="result">{{ result }}</pre>
</template>
$fetch event handler (save) ichida β to'g'ri joy. body to'g'ridan-to'g'ri obyekt (ofetch o'zi JSON qiladi).
13 β shallowRef gotcha
Nuxt 4'da data β shallowRef. shallowRef faqat .value butunlay almashtirilsa reaktivlikni ishga soladi; ichidagi massiv/obyektni mutate qilish (push, nested o'zgartirish) kuzatilmaydi.
const { data } = await useFetch('/api/todos')
// β UI yangilanmaydi
data.value.push(newTodo)
// β
Yangi reference ber
data.value = [...data.value, newTodo]
useFetch('/api/todos', { deep: true }). Lekin katta data'da deep qimmat β odatda reassign yondashuvi yaxshiroq.
15 β birinchi server endpoint
// server/api/ping.ts
export default defineEventHandler(() => {
return { pong: true, time: Date.now() }
})
/api/ping ga kirsang JSON ko'rasan. return qilingan obyekt avtomatik Content-Type: application/json bilan beriladi β qo'lda JSON.stringify shart emas. Bu Laravel'da controller'dan array qaytarsang avtomatik JSON bo'lganiga o'xshaydi.
20 β server logger middleware
// server/middleware/logger.ts
export default defineEventHandler((event) => {
console.log(`[${new Date().toISOString()}] ${event.method} ${event.path}`)
// hech narsa return qilinmaydi β middleware faqat "o'tib ketadi"
})
event.context ga yozish uchun. Har bir so'rovda β shu jumladan /api/* va sahifalar uchun ham β ishlaydi. Bu Laravel global middleware'ning aynan o'zi.
24 β EduCore mini-BFF
// server/middleware/tenant.ts
export default defineEventHandler((event) => {
const host = getHeader(event, 'host') || 'demo.localhost'
event.context.tenant = host.split('.')[0] // markaz.educore.uz β "markaz"
})
// server/api/students.get.ts
export default defineEventHandler((event) => {
const tenant = event.context.tenant
// haqiqiy loyihada bu yerda Laravel API'ga $fetch qilinadi (tenant header bilan)
return {
tenant,
students: [
{ id: 1, name: 'Ali', tenant },
{ id: 2, name: 'Vali', tenant },
],
}
})
// app/middleware/auth.global.ts
export default defineNuxtRouteMiddleware((to) => {
const isLoggedIn = useCookie('auth_token').value
const publicPages = ['/login', '/register']
if (!isLoggedIn && !publicPages.includes(to.path)) {
return navigateTo('/login')
}
})
<!-- app/pages/students.vue -->
<script setup>
const { data } = await useFetch('/api/students')
</script>
<template>
<h1>{{ data.tenant }} markazi o'quvchilari</h1>
<ul><li v-for="s in data.students" :key="s.id">{{ s.name }}</li></ul>
</template>
Oqim: navigatsiya β auth.global.ts (login tekshiradi) β sahifa useFetch('/api/students') β server'da tenant.ts middleware tenant'ni aniqlaydi β students.get.ts tenant bo'yicha data qaytaradi. Bu real multi-tenant BFF'ning soddalashtirilgan skeletini ko'rsatadi: tenant aniqlash va maxfiy kalitlar serverda, frontend toza qoladi.
π Birinchi to'plam yakuni¶
Tabriklayman β Vue 3 yadrosini (01β06) va Nuxt'ning eng muhim qismini (07β08) tugatding. Endi sen: - Vue'da reaktiv, komponentli, router'li, state-managed SPA yoza olasan. - Nuxt'da SSR sahifalar, server API, middleware va to'g'ri data fetching arxitekturasini qura olasan. - EduCore kabi multi-tenant SaaS'ning frontend + BFF skeletini tushunasan.
Keyingi to'plam (so'rasang yozaman, shu uslubda): rendering modes (SSR/SSG/ISR/hybrid chuqur) Β· SEO/meta to'liq Β· Nitro deep (storage, caching, tasks) Β· modules ecosystem Β· testing (Vitest + Vue Test Utils) Β· deployment (VPS/Nginx, Docker, edge) Β· TypeScript chuqur Β· performance optimizatsiya.
π README Β· β¬ οΈ Oldingi: 07 β Nuxt asoslari Β· Keyingi: 09 β Nuxt UI β‘οΈ