Nitro: universal JavaScript server

Kas yra Nitro ir kodėl apie jį verta žinoti

Jeigu dirbate su JavaScript ekosistema, tikriausiai pastebėjote, kaip greitai viskas keičiasi. Kas mėnesį atsiranda naujų framework’ų, bibliotekų ir įrankių. Tačiau kartais pasirodo kažkas, kas iš tiesų keičia žaidimo taisykles. Nitro yra būtent toks projektas – universalus JavaScript serveris, kuris bando išspręsti problemą, su kuria susiduria daugelis kūrėjų: kaip sukurti serverio pusės aplikaciją, kuri veiktų bet kur?

Nitro gimė iš praktinių poreikių. Kai Nuxt komanda kūrė trečiąją savo framework’o versiją, jiems reikėjo serverio variklio, kuris būtų pakankamai lankstus, kad veiktų įvairiose aplinkose – nuo tradicinių Node.js serverių iki šiuolaikinių edge computing platformų. Vietoj to, kad pritaikytų esamą sprendimą, jie sukūrė Nitro – atskirą projektą, kuris dabar naudojamas ne tik Nuxt, bet ir kitų framework’ų.

Pagrindinis Nitro privalumas yra jo universalumas. Tai ne tik dar vienas Express ar Fastify alternatyva. Tai serverio runtime’as, kuris leidžia jums parašyti kodą vieną kartą ir paleisti jį praktiškai bet kurioje platformoje – AWS Lambda, Cloudflare Workers, Vercel, Netlify, tradiciniame Node.js serveryje ar net Deno. Skamba per gerai, kad būtų tiesa? Pažiūrėkime giliau.

Architektūra ir pagrindai

Nitro pastatytas ant kelių pagrindinių koncepcijų, kurios daro jį tokį lankstų. Pirmiausia, tai file-based routing sistema. Jei esate dirbę su Nuxt ar Next.js, ši koncepcija jums bus pažįstama. Vietoj to, kad rankiniu būdu apibrėžtumėte visus maršrutus, tiesiog sukuriate failus atitinkamoje direktorijoje, ir Nitro automatiškai sukuria reikiamus endpoint’us.

Pavyzdžiui, failas routes/api/users.ts automatiškai tampa /api/users endpoint’u. Tai gali pasirodyti kaip smulkmena, bet kai projektas auga, tokia struktūra labai palengvina navigaciją ir kodą daro daug skaitomesnį. Nereikia ieškoti kažkokio centrinio routing failo ir bandyti suprasti, kur kas apibrėžta.

Antra svarbi koncepcija – auto-imports. Nitro automatiškai importuoja utility funkcijas, helper’ius ir kitus dažnai naudojamus dalykus. Tai reiškia, kad galite tiesiog pradėti naudoti funkcijas kaip defineEventHandler, readBody, getQuery ir pan., nereikalaujant explicit import’ų kiekviename faile. Iš pradžių tai gali atrodyti keista, ypač jei esate įpratę prie griežtos TypeScript disciplinos, bet praktikoje tai labai pagreitina developmentą.

Trečias dalykas – storage layer. Nitro turi integruotą abstrakciją darbui su įvairiais storage sprendimais. Ar tai būtų failų sistema, Redis, Cloudflare KV, ar bet koks kitas storage – jūs naudojate tą patį API. Tai ypač naudinga, kai kuriate aplikaciją, kuri gali būti deployed’inta skirtingose aplinkose.

Praktinis pavyzdys: API endpoint’o kūrimas

Geriausias būdas suprasti Nitro – pamatyti jį veikiantį. Sukurkime paprastą API endpoint’ą, kuris grąžina vartotojų sąrašą. Pirma, reikia inicializuoti projektą:

npx giget@latest nitro my-nitro-app
cd my-nitro-app
npm install

Dabar sukuriame failą routes/api/users.get.ts. Pastebėkite .get priesagą – tai nurodo, kad šis endpoint’as atsakys tik į GET užklausas. Galite naudoti ir kitus HTTP metodus: .post, .put, .delete ir t.t.

export default defineEventHandler(async (event) => {
const users = await useStorage().getItem('users') || []
return users
})

Štai ir viskas! Šis kodas veiks ir lokalioje aplinkoje, ir production’e, nepriklausomai nuo to, kokį storage backend’ą naudojate. Jei norite pridėti naują vartotoją:

export default defineEventHandler(async (event) => {
const body = await readBody(event)
const users = await useStorage().getItem('users') || []

const newUser = {
id: Date.now(),
...body
}

users.push(newUser)
await useStorage().setItem('users', users)

return newUser
})

Atkreipkite dėmesį, kaip paprastai skaitome request body su readBody funkcija. Nereikia jokių middleware’ų ar papildomų bibliotekų – viskas veikia iš dėžės.

Middleware ir request handling

Kiekvienas rimtas serveris reikalauja middleware funkcionalumo. Nitro čia taip pat turi elegantišką sprendimą. Middleware’ai gyvena middleware/ direktorijoje ir automatiškai taikomi visiems request’ams.

Pavyzdžiui, sukurkime paprastą logging middleware:

export default defineEventHandler((event) => {
console.log(`${event.node.req.method} ${event.node.req.url}`)
})

Arba autentifikacijos middleware, kuris tikrina JWT token’ą:

export default defineEventHandler(async (event) => {
const path = event.node.req.url

if (path?.startsWith('/api/protected')) {
const token = getHeader(event, 'authorization')

if (!token) {
throw createError({
statusCode: 401,
message: 'Unauthorized'
})
}

// Čia būtų token validacija
event.context.user = { id: 1, name: 'User' }
}
})

Vienas iš gražiausių dalykų – event.context objektas. Jis leidžia perduoti duomenis tarp middleware’ų ir galutinio handler’io. Tai labai patogu, kai norite, pavyzdžiui, ištraukti user informaciją middleware’e ir ją panaudoti endpoint’e.

Deployment galimybės ir platformų palaikymas

Čia Nitro iš tiesų spindi. Kai baigiate kurti aplikaciją, tiesiog paleidžiate npm run build, ir Nitro sugeneruoja output’ą, optimizuotą jūsų pasirinktai platformai. Platformą galite nurodyti nitro.config.ts faile:

export default defineNitroConfig({
preset: 'cloudflare'
})

Palaikomų preset’ų sąrašas įspūdingas: AWS Lambda, Azure Functions, Cloudflare Workers, Deno Deploy, Netlify, Vercel, Node.js server, ir dar daugybė kitų. Kiekvienas preset’as generuoja kodą, optimizuotą konkrečiai platformai, bet jūsų aplikacijos kodas lieka tas pats.

Tai ypač naudinga, kai dirbate su klientais, kurie dar nėra apsisprendę dėl hosting’o, arba kai norite turėti galimybę lengvai migruoti tarp platformų. Nebereikia perrašinėti pusės aplikacijos, kad perkeltumėte ją iš AWS į Cloudflare ar atvirkščiai.

Praktinis patarimas: pradėkite su node-server preset’u development’e ir testing’e. Jis veikia kaip tradicinis Node.js serveris ir leidžia naudoti visus įprastus debugging įrankius. Production’e galite perjungti į bet kurį kitą preset’ą.

Caching ir performance optimizacijos

Nitro turi integruotą caching sistemą, kuri veikia skirtingose aplinkose. Galite cache’inti response’us naudodami cachedEventHandler:

export default cachedEventHandler(async (event) => {
// Šis kodas bus vykdomas tik kartą per valandą
const data = await fetchExpensiveData()
return data
}, {
maxAge: 60 * 60, // 1 valanda
getKey: (event) => {
// Galite customizuoti cache key
return event.node.req.url || ''
}
})

Tai ypač naudinga, kai turite endpoint’us, kurie daro brangias operacijas – duomenų bazės užklausas, išorinius API kvietimus ir pan. Cache’as automatiškai veikia ir memory, ir distributed cache sistemose, priklausomai nuo deployment platformos.

Dar viena įdomi funkcija – route rules. Jos leidžia deklaratyviai nurodyti, kaip turėtų būti apdorojami tam tikri maršrutai:

export default defineNitroConfig({
routeRules: {
'/api/static/**': { cache: { maxAge: 60 * 60 * 24 } },
'/api/dynamic/**': { cache: false },
'/old-page': { redirect: '/new-page' }
}
})

Šios taisyklės taikomos build time, todėl jos veikia labai efektyviai ir neturi runtime overhead’o.

Integracija su frontend framework’ais

Nors Nitro gimė Nuxt ekosistemoje, jis puikiai veikia ir su kitais framework’ais. Galite jį naudoti kaip backend’ą React, Vue, Svelte ar bet kuriai kitai frontend aplikacijai. Tiesiog sukuriate Nitro projektą ir naudojate jį kaip API serverį.

Tačiau integracija su Nuxt yra ypač glandi. Nuxt 3 naudoja Nitro po gaubtu, todėl jūsų server routes automatiškai tampa dalimi jūsų aplikacijos. Galite naudoti $fetch composable, kuris automatiškai žino apie jūsų API endpoint’us ir net suteikia TypeScript type safety.

Jei kuriate full-stack aplikaciją su Nuxt, jūsų server/ direktorija iš esmės yra Nitro projektas. Tai reiškia, kad visos Nitro funkcijos veikia automatiškai – file-based routing, auto-imports, storage layer, ir t.t.

Dar vienas įdomus use case – API proxy. Galite naudoti Nitro kaip proxy serverį tarp frontend’o ir išorinių API, pridedant autentifikaciją, rate limiting, ar kitas funkcijas:

export default defineEventHandler(async (event) => {
const query = getQuery(event)
const apiKey = useRuntimeConfig().apiKey

const data = await $fetch('https://external-api.com/data', {
query,
headers: {
'Authorization': `Bearer ${apiKey}`
}
})

return data
})

Ką reikia žinoti prieš pradedant

Nitro yra puikus įrankis, bet kaip ir bet kokia technologija, jis turi savo niuansų. Pirma, jei esate įpratę prie tradicinių Node.js framework’ų kaip Express, jums gali prireikti laiko prisitaikyti prie Nitro filosofijos. File-based routing ir auto-imports iš pradžių gali atrodyti keisti, bet patikėkite – po savaitės darbo jau negalėsite įsivaizduoti gyvenimo be jų.

Antra, dokumentacija, nors ir gera, kartais gali būti per daug high-level. Kai susidursite su specifine problema, gali tekti pasikasinėti source code arba ieškoti pavyzdžių GitHub’e. Bendruomenė auga, bet ji dar nėra tokia didelė kaip Express ar Fastify.

Trečia, TypeScript palaikymas yra puikus, bet kartais auto-imports gali supainioti jūsų IDE. Jei naudojate VS Code, įsitikinkite, kad turite naujausią Volar extension’ą (jei dirbate su Vue/Nuxt) arba TypeScript language server’į tinkamai sukonfigūruotą.

Dėl performance – Nitro yra tikrai greitas, bet atminkite, kad build time gali būti šiek tiek ilgesnis nei su paprastais Node.js projektais. Tai dėl to, kad Nitro analizuoja jūsų kodą ir generuoja optimizuotą output’ą. Development mode’e tai ne problema, nes naudojamas hot reload, bet CI/CD pipeline’uose gali prireikti papildomų minučių.

Kodėl Nitro yra ateities technologija

Žvelgiant į JavaScript serverio pusės ekosistemą, matome aiškią tendenciją link universalumo ir portability. Edge computing tampa vis populiaresnis, serverless architektūros tampa norma, o kūrėjai nori galimybės lengvai perjungti tarp platformų. Nitro puikiai atitinka šias tendencijas.

Tai, kas pradėjo kaip Nuxt vidinis projektas, dabar tampa standartu kitiems framework’ams. Analog.js (Angular meta-framework) naudoja Nitro. SolidStart eksperimentuoja su juo. Tai rodo, kad bendruomenė mato Nitro vertę ir potencialą.

Jei kuriate naują projektą ir svarstote, kokį serverio sprendimą pasirinkti, Nitro tikrai turėtų būti jūsų sąraše. Jis gali būti ne idealus kiekvienai situacijai – jei kuriate labai specifinę sistemą su sudėtingais reikalavimais, galbūt tradicinis framework’as bus geresnis pasirinkimas. Bet daugumai modernių web aplikacijų Nitro suteikia puikų balansą tarp paprastumo, funkcionalumo ir lankstumo.

Pradėkite su paprastu projektu, išbandykite pagrindines funkcijas, ir greičiausiai pastebėsite, kad Nitro ne tik palengvina developmentą, bet ir atidaro naujas galimybes jūsų aplikacijoms. Universalumas nėra tik buzzword’as – tai reali vertė, kuri gali sutaupyti daug laiko ir pastangų ilgalaikėje perspektyvoje.

Daugiau

Rome tools: viskas vienoje įrankių grandinėje