asjs-express
v1.8.0
Published
Lightweight Express view engine with EJS-like templates, layouts, async page rendering, form enhancement, and a built-in client router.
Downloads
1,360
Maintainers
Readme
asjs-express
ASJS is a lightweight view engine for Express, built for projects that want to keep server-rendered pages simple, maintainable, and pleasant to work on while still benefiting from layouts, async data preparation, partials, and modern navigation behavior.
Bu depodaki örnek yüzey WebAS adıyla anlatılıyor. Giriş tonu özellikle daha doğal tutuldu; sanki aynı projelerde uzun süredir birlikte çalışan küçük bir ekip kendi düzenini, çalışma biçimini ve arayüz yaklaşımını sakin bir dille anlatıyormuş gibi okunması istendi.
English
Why this project exists
ASJS was built for a very common problem in Express projects: pages start simple, then real work arrives. A dashboard needs data before render. A contact form needs validation and a meaningful response. A shared layout needs to stay stable while the inside changes. At that point, many projects either become repetitive or drift into a heavy front-end stack before they actually need one.
ASJS keeps that middle ground clean. It gives you an EJS-like template syntax, a small but practical view model, optional client-side navigation, and the ability to finish your data work before the first HTML reaches the browser.
The WebAS example in this repository is written in that spirit. It presents a small, believable interface structure shaped by two developers:
The intention is simple: this should read like something a careful team would actually maintain, not like a throwaway demo assembled for a screenshot.
What ASJS gives you
- EJS-like syntax with
<% %>,<%= %>, and<%- %> - Layout support without extra ceremony
- Partial rendering with prop validation
- Template caching
- Built-in client router with transitions, prefetch, and loading bar support
- Async form enhancement for marked forms
- Plugin and hook support for Express projects that need to grow over time
- npx project scaffolding so a new ASJS app can be created with starter files in one command
- Package-served assets, so you do not have to manually copy router files into your public folder
Install
npm install asjs-expressCreate a new app with npx
ASJS now includes a small project generator. If you want a React or Next.js-style bootstrap flow, you can create a fresh project folder with one command.
npx asjs-express my-appThat command creates a ready-to-run ASJS project with app.js, views/layouts/main.asjs, views/home.asjs, package.json, .gitignore, and a small README.
You can also choose a slightly richer starter that already includes two routes and the newer helper API:
npx asjs-express create my-app --template starterUseful flags:
--template minimal--template starter--skip-install--force
After generation:
cd my-app
npm run devQuick start
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
debug: true,
cache: true,
forms: true,
transitions: 'lift',
prefetch: true,
loadingBar: true
})
app.get('/', asjs.page('home', {
title: 'Hello ASJS'
}))
app.use(asjs.errors())
app.listen(3000)Minimal Express template
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, { rootDir: __dirname })
app.get('/', asjs.page('home', { title: 'Hello ASJS' }))
app.listen(3000)Super simple one-page starter
If you want the lowest-friction starting point, use a single-page structure like this:
my-app/
app.js
views/
layouts/
main.asjs
home.asjsconst express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Home' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})
app.get('/', asjs.page('home', {
title: 'My first ASJS page',
headline: 'ASJS works with almost no setup.',
description: 'Header, router, loading bar, and SPA-ready page transitions are already connected.'
}))
app.use(asjs.errors())
app.listen(3000)<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html><section>
<h1><%= headline %></h1>
<p><%= description %></p>
</section>This is enough for a working ASJS page.
The repository also ships the same idea as a real folder under example-minimal/.
You can run it with npm run example:minimal.
Layout usage
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>asjs.clientTags() injects the built-in ASJS stylesheet and router script for you.
theme: true optionally loads the packaged light, corporate WebAS theme.
Easiest Express SPA setup
If you want the easiest integration, build it in 3 files: app.js, views/layouts/main.asjs, and one page such as views/home.asjs.
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Home' },
{ href: '/about', label: 'About' },
{ href: '/contact', label: 'Contact' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})
const buildPage = asjs.createPageModel({
pageDescription: 'My first ASJS page',
renderSummary: []
})
app.get('/', asjs.createPageRoute('home', {
buildPage,
renderState: {
delay: 120,
label: 'Home page ready',
narrative: 'ASJS prepared this page model before the HTML response was sent.'
}
}, () => ({
title: 'Home',
heroTitle: 'Hello ASJS',
heroText: 'This page is server-rendered and SPA navigation is already active.'
})))
app.listen(3000)<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>Default SPA loading and page transition animations are already included. If you do not add a custom class, ASJS keeps the built-in loading bar and transition look. If you want to style on top of them later, pass a class name instead of replacing the system.
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
transitions: {
name: 'fade',
className: 'my-page-motion'
},
loadingBar: {
enabled: true,
className: 'my-loading-bar'
}
})Built-in SPA header
ASJS now ships with a built-in header helper for the default SPA flow.
You do not have to define brand, siteCta, or isActiveNavItem just to make a working header. If you want a standard header, navItems is the only thing you will usually customize.
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Home', activeMode: 'exact' },
{ href: '/products', label: 'Products', activeMode: 'exact' },
{ href: '/contact', label: 'Contact', activeMode: 'exact' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>The built-in header is SPA-aware by default.
It renders data-asjs-link, uses the current request path automatically, and applies active states internally.
If you do not define a brand, ASJS generates a safe default brand object.
If you do not define siteCta, the header still works and simply renders without the CTA button.
If you want to customize them later, you can still pass them through setupAsjs().
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Overview' },
{ href: '/contact', label: 'Contact' }
],
brand: {
href: '/',
mark: 'WA',
name: 'WebAS',
tagline: 'Shared interface structure for Express projects'
},
siteCta: {
href: '/contact',
label: 'Start a project',
transition: 'slide'
}
})If you still prefer a custom partial, ASJS now also injects brand, siteCta, currentPath, and isActiveNavItem into the view locals automatically.
Work before the page loads
Yes. If you pass an async callback to asjs.page(), ASJS waits for that work to finish and only then calls res.render(viewName, locals).
That means database reads, API requests, permission checks, formatting, and view-model building can all happen before the browser receives the first HTML.
Database example before render
const db = {
companies: {
async findBySlug(slug) {
return { id: 7, slug, name: 'Northwind Studio' }
}
},
dashboards: {
async getMetrics(companyId) {
return [
{ label: 'Open tasks', value: 12 },
{ label: 'Active pages', value: 8 },
{ label: 'Pending review', value: 3 }
]
}
}
}
app.get('/dashboard/:slug', asjs.page('dashboard', async (req) => {
const company = await db.companies.findBySlug(req.params.slug)
const metrics = await db.dashboards.getMetrics(company.id)
return {
title: `${company.name} Dashboard`,
company,
metrics,
renderSummary: [
{ label: 'Source', value: 'Database' },
{ label: 'Company', value: company.name },
{ label: 'Mode', value: 'Server render' }
]
}
}))Returned page model
The callback above returns a plain object like this:
{
title: 'Northwind Studio Dashboard',
company: { id: 7, slug: 'northwind', name: 'Northwind Studio' },
metrics: [
{ label: 'Open tasks', value: 12 },
{ label: 'Active pages', value: 8 },
{ label: 'Pending review', value: 3 }
],
renderSummary: [
{ label: 'Source', value: 'Database' },
{ label: 'Company', value: 'Northwind Studio' },
{ label: 'Mode', value: 'Server render' }
]
}ASJS then turns that object into view locals and renders the final HTML. The browser does not receive a loading shell first and wait for the page model later. The work is already done.
Server-rendered POST example
You can use the same pattern for POST routes when you want validation or save results to come back as a fully rendered page state.
app.use(express.urlencoded({ extended: false }))
app.post('/contact', asjs.page('contact', async (req) => {
const formValues = {
name: String(req.body.name || '').trim(),
email: String(req.body.email || '').trim(),
brief: String(req.body.brief || '').trim()
}
const formErrors = {}
if (!formValues.name) {
formErrors.name = 'Please enter a name.'
}
if (!formValues.email.includes('@')) {
formErrors.email = 'Please enter a valid email address.'
}
if (Object.keys(formErrors).length) {
return {
status: 422,
title: 'Contact',
formValues,
formErrors
}
}
const savedRecord = await saveProjectNote(formValues)
return {
title: 'Contact received',
submission: savedRecord
}
}))Validation return
{
status: 422,
title: 'Contact',
formValues: {
name: '',
email: 'wrong-address',
brief: 'Need a redesign'
},
formErrors: {
name: 'Please enter a name.',
email: 'Please enter a valid email address.'
}
}Success return
{
title: 'Contact received',
submission: {
reference: 'WEBAS-K3J9X2',
name: 'Lina Howard',
email: '[email protected]',
brief: 'We need a cleaner delivery and dashboard structure.'
}
}In both cases, the route returns data. ASJS handles the render step after that.
Inline JSON response without leaving the page
Sometimes you do not want a full page transition. You want to post to a dedicated route, get a short answer, and place that answer inside a specific panel.
<form<%- asjs.formAttrs({
action: '/contact/availability',
method: 'post',
mode: 'json',
target: '#availability-response',
swap: 'replace'
}) %>>
<input type="text" name="company" placeholder="Company name">
<input type="text" name="scope" placeholder="Dashboard, launch page, corporate site">
<button type="submit">Check route</button>
</form>
<div id="availability-response"></div>app.post('/contact/availability', async (req, res) => {
const preview = await availabilityService.check(req.body)
res.json({
html: `
<div class="asjs-inline-response is-success">
<strong>${preview.title}</strong>
<p>${preview.message}</p>
</div>
`,
route: preview.route,
replyWindow: preview.replyWindow
})
})Example JSON response body
{
"html": "<div class=\"asjs-inline-response is-success\"><strong>Northwind Studio can be routed into the operational track.</strong><p>The request can continue without leaving the current page shell.</p></div>",
"route": "Operational UI track",
"replyWindow": "1 business day"
}ASJS reads that response, finds #availability-response, and updates only that part of the page.
Plugins and hooks
For projects that need room to grow, setupAsjs() now supports plugins and lifecycle hooks.
const asjs = setupAsjs(app, {
rootDir: __dirname,
plugins: [
function studioPlugin(api) {
const healthRouter = api.express.Router()
healthRouter.get('/', (req, res) => {
res.json({ ok: true, service: 'studio-plugin' })
})
api.app.use('/health', healthRouter)
api.extendLocals({ supportEmail: '[email protected]' })
api.addHook('beforeRender', ({ pageData }) => ({
pageData: {
...pageData,
supportEmail: '[email protected]'
}
}))
}
]
})Available hook points:
beforePageafterPagebeforeRenderafterRender
Form enhancement
<form class="contact-form"<%- asjs.formAttrs({
action: '/contact',
method: 'post',
transition: 'slide'
}) %>>
<input type="text" name="name" placeholder="Project lead">
<button type="submit">Send</button>
</form>asjs.formAttrs() marks a form for the built-in ASJS client handler.
The response may come back as full HTML or as JSON for an inline target update.
Router lifecycle events
asjs:before-navigateasjs:content-replacedasjs:navigatedasjs:navigation-errorasjs:before-submitasjs:form-responseasjs:form-successasjs:form-error
These events are useful for analytics, loading states, cleanup logic, and custom form feedback.
Component helper
const asjs = setupAsjs(app, {
components: {
'partials/card': {
props: {
title: 'string',
text: 'string',
tone: {
type: 'string',
default: 'neutral'
}
},
strict: true
}
}
})<%- component('partials/card', { title: 'Card', text: 'Body text' }) %>Built-in asset delivery
ASJS serves its client files from inside the package by default.
- default mount path:
/_asjs - router script:
/_asjs/asjs-router.js?v=... - core stylesheet:
/_asjs/asjs-core.css?v=... - optional theme stylesheet:
/_asjs/asjs-theme.css?v=...
The v=... query string is generated automatically for cache busting.
Transition presets
fadeliftslidescaleblur-soft
Use them globally with transitions, per-page with transition, or per-link with data-asjs-transition.
Highlights
setupAsjs(app, options)integrates directly into an existing Express app.component()validates props before rendering a partial.cache: truekeeps compiled templates until file mtimes change.prefetchandloadingBarimprove navigation feel.forms: trueenables built-in async form enhancement.pluginsopens room for middleware, routes, and project-specific services.assetVersionlets you override or disable cache-busting.serveClientAssetsserves router assets from the package itself.
Package utilities
const { getAsjsPackagePaths } = require('asjs-express')
const paths = getAsjsPackagePaths()
console.log(paths.routerPath)Local demo
npm install
npm run example:expressOpen http://localhost:3001.
Repository example app
The repository ships a dedicated Express example under example-express/.
- entry:
example-express/app.js - favicon:
example-express/favicon.svg - views:
example-express/views - start command:
npm run example:express - starter pages:
/,/dashboard,/products,/contact - plugin health route:
/webas-platform/health
The example presents WebAS as a practical interface structure associated with Acarfx and Seyfullah Kısacık. It is intentionally written with a calm, professional tone because the project is meant to feel usable, not theatrical.
Türkçe
Bu proje neden var?
ASJS, Express projelerinde çok sık karşılaşılan ama çoğu zaman gereksiz yere karmaşık hâle gelen bir alan için yazıldı: sayfalar ilk gün sade olur, sonra gerçek ihtiyaçlar gelir. Dashboard verisi gerekir. Form doğrulaması gerekir. Aynı layout içinde farklı sayfaları sakin biçimde taşımak gerekir. Bir noktadan sonra ya aynı kod tekrar eder ya da ihtiyaç o kadar büyük değilken proje ağır bir ön yüz yapısına sürüklenir.
ASJS tam burada devreye girer. Sunucu tarafını sade tutar, şablon katmanını EJS benzeri bir yapıda bırakır, istersen sayfa geçişlerini güçlendirir, istersen hiçbir şeyi zorlamaz. En önemlisi de şu soruya net cevap verir: Evet, sayfa yüklenmeden önce veriyi hazırlayabilirsin.
Bu depodaki WebAS örneği de bu anlayışla yazıldı. Dili özellikle daha insanî ve daha gerçek tutuldu. Amaç “vitrinlik demo” hissi vermek değil, gerçekten çalışan küçük bir ekibin kullanabileceği bir yapı duygusu vermekti.
Bu örnek anlatımın arkasında iki geliştirici adı özellikle görünür tutuluyor:
WebAS yüzeyi, bu iki geliştiricinin birlikte şekillendirdiği düzenli, açık ve kurumsal bir arayüz dili gibi anlatılıyor. Çünkü iyi bir arayüz çoğu zaman bağırmaz; düzeniyle güven verir.
ASJS neler sağlar?
<% %>,<%= %>,<%- %>tabanlı EJS benzeri sözdizimi- Ek yük bindirmeyen layout desteği
- Prop doğrulamalı partial ve component kullanımı
- Template cache
- Geçiş, prefetch ve loading bar destekli istemci yönlendirmesi
- İşaretli formlar için dahili async form akışı
- Büyüyen Express projeleri için plugin ve hook desteği
- Tek komutla başlangıç dosyaları oluşturan npx proje oluşturucu
- Public klasöre dosya kopyalamadan paket içinden asset servisi
Kurulum
npm install asjs-expressnpx ile yeni proje oluşturma
ASJS artık küçük bir proje oluşturucu ile geliyor. React veya Next.js tarzı bir başlangıç akışı istiyorsan tek komutla yeni proje klasörü oluşturabilirsin.
npx asjs-express my-appBu komut çalışan bir ASJS projesi üretir. İçine app.js, views/layouts/main.asjs, views/home.asjs, package.json, .gitignore ve kısa bir README koyar.
Biraz daha dolu bir başlangıç istersen iki route ve yeni helper API ile gelen starter şablonunu seçebilirsin:
npx asjs-express create my-app --template starterKullanışlı bayraklar:
--template minimal--template starter--skip-install--force
Üretimden sonra:
cd my-app
npm run devHızlı başlangıç
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
debug: true,
cache: true,
forms: true,
transitions: 'lift',
prefetch: true,
loadingBar: true
})
app.get('/', asjs.page('home', {
title: 'Merhaba ASJS'
}))
app.use(asjs.errors())
app.listen(3000)Minimal Express şablonu
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, { rootDir: __dirname })
app.get('/', asjs.page('home', { title: 'Merhaba ASJS' }))
app.listen(3000)Aşırı basit tek sayfa başlangıcı
En az sürtünmeli başlangıç için şu kadar basit bir yapı yeterlidir:
my-app/
app.js
views/
layouts/
main.asjs
home.asjsconst express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Ana Sayfa' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})
app.get('/', asjs.page('home', {
title: 'İlk ASJS sayfam',
headline: 'ASJS neredeyse kurulum istemeden çalışır.',
description: 'Header, router, loading bar ve SPA hazır sayfa geçişleri zaten bağlı gelir.'
}))
app.use(asjs.errors())
app.listen(3000)<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html><section>
<h1><%= headline %></h1>
<p><%= description %></p>
</section>Bu kadarı çalışan bir ASJS sayfası için yeterlidir.
Aynı mantığın gerçek klasör örneği repo içinde example-minimal/ altında da var.
Çalıştırmak için npm run example:minimal kullanabilirsin.
Layout kullanımı
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>asjs.clientTags() çağrısı dahili ASJS stil ve router etiketlerini otomatik üretir.
theme: true ise açık renkli, kurumsal WebAS temasını yükler.
En kolay Express SPA kurulumu
En kolay entegrasyon için 3 dosya yeterlidir: app.js, views/layouts/main.asjs ve örnek olarak views/home.asjs.
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Ana Sayfa' },
{ href: '/about', label: 'Hakkında' },
{ href: '/contact', label: 'İletişim' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})
const buildPage = asjs.createPageModel({
pageDescription: 'İlk ASJS sayfam',
renderSummary: []
})
app.get('/', asjs.createPageRoute('home', {
buildPage,
renderState: {
delay: 120,
label: 'Ana sayfa hazır',
narrative: 'ASJS bu sayfa modelini HTML cevabı gitmeden önce hazırladı.'
}
}, () => ({
title: 'Ana Sayfa',
heroTitle: 'Merhaba ASJS',
heroText: 'Bu sayfa server-rendered çalışır ve SPA gezinme hemen aktiftir.'
})))
app.listen(3000)<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>Varsayılan SPA yükleme ve sayfa geçiş animasyonları zaten dahildir. Ek bir class vermezsen ASJS dahili loading bar ve transition görünümünü korur. Sonradan üstüne kendi stilini yazmak istersen sistemi komple değiştirmek yerine sadece class eklersin.
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
transitions: {
name: 'fade',
className: 'my-page-motion'
},
loadingBar: {
enabled: true,
className: 'my-loading-bar'
}
})Dahili SPA header sistemi
ASJS artık varsayılan SPA akışı için dahili bir header helper ile geliyor.
Çalışan bir header kurmak için brand, siteCta veya isActiveNavItem tanımlamak zorunda değilsin. Standart bir header istiyorsan çoğu durumda sadece navItems alanını düzenlemen yeterlidir.
const express = require('express')
const { setupAsjs } = require('asjs-express')
const app = express()
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Ana Sayfa', activeMode: 'exact' },
{ href: '/products', label: 'Ürünler', activeMode: 'exact' },
{ href: '/contact', label: 'İletişim', activeMode: 'exact' }
],
transitions: 'fade',
prefetch: true,
loadingBar: true
})<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title><%= title %></title>
<%- asjs.clientTags({ preload: true, theme: true }) %>
</head>
<body<%- asjs.bodyAttrs() %>>
<%- asjs.progressMarkup() %>
<%- asjs.header() %>
<main<%- asjs.viewAttrs() %>>
<%- body %>
</main>
</body>
</html>Bu dahili header varsayılan olarak SPA uyumludur.
Linkleri data-asjs-link ile üretir, mevcut request path bilgisini kendi alır ve aktif link durumunu içeride hesaplar.
brand tanımlamazsan ASJS güvenli bir varsayılan brand nesnesi üretir.
siteCta tanımlamazsan header bozulmaz; sadece sağ taraftaki CTA düğmesi çizilmez.
Daha sonra özelleştirmek istersen bunları yine setupAsjs() üzerinden verebilirsin.
const asjs = setupAsjs(app, {
rootDir: __dirname,
defaultLayout: 'layouts/main',
navItems: [
{ href: '/', label: 'Genel Bakış' },
{ href: '/contact', label: 'İletişim' }
],
brand: {
href: '/',
mark: 'WA',
name: 'WebAS',
tagline: 'Express projeleri için ortak arayüz yapısı'
},
siteCta: {
href: '/contact',
label: 'Proje başlat',
transition: 'slide'
}
})Kendi partial yapını yazmak istersen ASJS artık brand, siteCta, currentPath ve isActiveNavItem alanlarını da view locals içine otomatik koyar.
Sayfa yüklenmeden önce işlem yapmak
Evet, mümkün. asjs.page() içine async bir callback verirsen ASJS bu işlemin bitmesini bekler, ardından res.render(viewName, locals) çağrısını yapar.
Yani veritabanı sorgusu, API isteği, yetki kontrolü, veri biçimlendirme ve view model oluşturma gibi işler ilk HTML tarayıcıya gitmeden önce tamamlanabilir.
Render öncesi veritabanı örneği
const db = {
companies: {
async findBySlug(slug) {
return { id: 7, slug, name: 'Northwind Studio' }
}
},
dashboards: {
async getMetrics(companyId) {
return [
{ label: 'Açık görev', value: 12 },
{ label: 'Aktif sayfa', value: 8 },
{ label: 'Onay bekleyen', value: 3 }
]
}
}
}
app.get('/dashboard/:slug', asjs.page('dashboard', async (req) => {
const company = await db.companies.findBySlug(req.params.slug)
const metrics = await db.dashboards.getMetrics(company.id)
return {
title: `${company.name} Dashboard`,
company,
metrics,
renderSummary: [
{ label: 'Kaynak', value: 'Veritabanı' },
{ label: 'Şirket', value: company.name },
{ label: 'Mod', value: 'Sunucu render' }
]
}
}))Dönen sayfa modeli
Yukarıdaki callback şu tipte bir nesne döndürür:
{
title: 'Northwind Studio Dashboard',
company: { id: 7, slug: 'northwind', name: 'Northwind Studio' },
metrics: [
{ label: 'Açık görev', value: 12 },
{ label: 'Aktif sayfa', value: 8 },
{ label: 'Onay bekleyen', value: 3 }
],
renderSummary: [
{ label: 'Kaynak', value: 'Veritabanı' },
{ label: 'Şirket', value: 'Northwind Studio' },
{ label: 'Mod', value: 'Sunucu render' }
]
}ASJS bu nesneyi view locals hâline getirir ve nihai HTML’i üretir. Tarayıcı önce boş bir iskelet alıp sonra veriyi beklemez. Veri işi zaten tamamlanmıştır.
Server-rendered POST dönüşü
POST rotalarında da aynı yaklaşımı kullanabilirsin. Özellikle form doğrulaması, kayıt sonucu veya başarı ekranı aynı view içinde geri dönecekse bu çok temiz çalışır.
app.use(express.urlencoded({ extended: false }))
app.post('/contact', asjs.page('contact', async (req) => {
const formValues = {
name: String(req.body.name || '').trim(),
email: String(req.body.email || '').trim(),
brief: String(req.body.brief || '').trim()
}
const formErrors = {}
if (!formValues.name) {
formErrors.name = 'Lütfen bir isim girin.'
}
if (!formValues.email.includes('@')) {
formErrors.email = 'Lütfen geçerli bir e-posta adresi girin.'
}
if (Object.keys(formErrors).length) {
return {
status: 422,
title: 'İletişim',
formValues,
formErrors
}
}
const savedRecord = await saveProjectNote(formValues)
return {
title: 'İletişim alındı',
submission: savedRecord
}
}))Hatalı dönüş örneği
{
status: 422,
title: 'İletişim',
formValues: {
name: '',
email: 'yanlis-adres',
brief: 'Tasarım yenilemesi istiyoruz'
},
formErrors: {
name: 'Lütfen bir isim girin.',
email: 'Lütfen geçerli bir e-posta adresi girin.'
}
}Başarılı dönüş örneği
{
title: 'İletişim alındı',
submission: {
reference: 'WEBAS-K3J9X2',
name: 'Lina Howard',
email: '[email protected]',
brief: 'Daha temiz bir teslim ve dashboard yapısı istiyoruz.'
}
}Her iki durumda da rota veri döndürür. Render adımını ASJS sonradan kendisi yürütür.
Sayfadan ayrılmadan JSON döndürmek
Bazen tam sayfa geçişi istemezsin. Belirli bir rotaya POST atıp kısa bir cevap almak ve yalnızca tek bir paneli güncellemek istersin.
<form<%- asjs.formAttrs({
action: '/contact/availability',
method: 'post',
mode: 'json',
target: '#availability-response',
swap: 'replace'
}) %>>
<input type="text" name="company" placeholder="Şirket adı">
<input type="text" name="scope" placeholder="Dashboard, lansman sayfası, kurumsal site">
<button type="submit">Rotayı kontrol et</button>
</form>
<div id="availability-response"></div>app.post('/contact/availability', async (req, res) => {
const preview = await availabilityService.check(req.body)
res.json({
html: `
<div class="asjs-inline-response is-success">
<strong>${preview.title}</strong>
<p>${preview.message}</p>
</div>
`,
route: preview.route,
replyWindow: preview.replyWindow
})
})Örnek JSON cevap gövdesi
{
"html": "<div class=\"asjs-inline-response is-success\"><strong>Northwind Studio operasyon hattına yönlendirilebilir.</strong><p>İstek mevcut sayfa kabuğu bozulmadan devam edebilir.</p></div>",
"route": "Operasyonel arayüz hattı",
"replyWindow": "1 iş günü"
}ASJS bu cevabı okur, #availability-response alanını bulur ve yalnızca o kısmı günceller.
Plugin ve hook yapısı
Proje büyüdüğünde setupAsjs() artık plugin ve yaşam döngüsü hook’ları için de alan açar.
const asjs = setupAsjs(app, {
rootDir: __dirname,
plugins: [
function studioPlugin(api) {
const healthRouter = api.express.Router()
healthRouter.get('/', (req, res) => {
res.json({ ok: true, service: 'studio-plugin' })
})
api.app.use('/health', healthRouter)
api.extendLocals({ supportEmail: '[email protected]' })
api.addHook('beforeRender', ({ pageData }) => ({
pageData: {
...pageData,
supportEmail: '[email protected]'
}
}))
}
]
})Kullanılabilen hook noktaları:
beforePageafterPagebeforeRenderafterRender
Form geliştirme
<form class="contact-form"<%- asjs.formAttrs({
action: '/contact',
method: 'post',
transition: 'slide'
}) %>>
<input type="text" name="name" placeholder="Proje sorumlusu">
<button type="submit">Gönder</button>
</form>asjs.formAttrs() bir formu dahili ASJS istemci akışı için işaretler.
Gelen cevap tam HTML de olabilir, belirli bir hedef alanı güncelleyen JSON da olabilir.
Router yaşam döngüsü event’leri
asjs:before-navigateasjs:content-replacedasjs:navigatedasjs:navigation-errorasjs:before-submitasjs:form-responseasjs:form-successasjs:form-error
Bu event’ler loading durumları, analytics, arayüz temizliği ve özel form geri bildirimi için işe yarar.
Component helper
const asjs = setupAsjs(app, {
components: {
'partials/card': {
props: {
title: 'string',
text: 'string',
tone: {
type: 'string',
default: 'neutral'
}
},
strict: true
}
}
})<%- component('partials/card', { title: 'Kart', text: 'İçerik' }) %>Dahili asset servisi
ASJS istemci dosyalarını varsayılan olarak paketin içinden servis eder.
- varsayılan mount path:
/_asjs - router script:
/_asjs/asjs-router.js?v=... - ana stil dosyası:
/_asjs/asjs-core.css?v=... - opsiyonel tema dosyası:
/_asjs/asjs-theme.css?v=...
v=... query parametresi otomatik cache-busting için üretilir.
Geçiş preset’leri
fadeliftslidescaleblur-soft
Bunları global olarak transitions, sayfa bazında transition, link bazında ise data-asjs-transition ile kullanabilirsin.
Öne çıkanlar
setupAsjs(app, options)mevcut Express uygulamasına doğrudan bağlanır.component()partial render etmeden önce props doğrular.cache: truederlenmiş template’leri dosya mtime değişene kadar saklar.prefetchveloadingBargeçiş hissini güçlendirir.forms: trueişaretli formlar için dahili async form akışını açar.pluginsmiddleware, route ve servis katmanı için alan bırakır.assetVersionile dahili asset versiyonlamasını ezebilir veya kapatabilirsin.serveClientAssetsrouter asset’lerini doğrudan paket içinden servis eder.
Paket yardımcıları
const { getAsjsPackagePaths } = require('asjs-express')
const paths = getAsjsPackagePaths()
console.log(paths.routerPath)Lokal demo
npm install
npm run example:expressTarayıcıda http://localhost:3001 adresini aç.
Repodaki örnek uygulama
Repo içinde ayrı bir Express örneği example-express/ altında durur.
- giriş:
example-express/app.js - favicon:
example-express/favicon.svg - view klasörü:
example-express/views - çalıştırma komutu:
npm run example:express - başlangıç rotaları:
/,/dashboard,/products,/contact - plugin sağlık rotası:
/webas-platform/health
Bu örnek, WebAS’i Acarfx ve Seyfullah Kısacık ile ilişkilendirilen düzenli ve kurumsal bir arayüz dili gibi sunar. Bu ton bilinçli seçildi. Çünkü bazı projelerde asıl değer, daha çok şey göstermek değil, daha çok şeyi sakin biçimde taşıyabilmektir.
