webspresso
v0.0.5
Published
Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling
Downloads
541
Maintainers
Readme
Webspresso
A minimal, file-based SSR framework for Node.js with Nunjucks templating.
Features
- File-Based Routing: Create pages by adding
.njkfiles to apages/directory - Dynamic Routes: Use
[param]for dynamic params and[...rest]for catch-all routes - API Endpoints: Add
.jsfiles topages/api/with method suffixes (e.g.,health.get.js) - Built-in i18n: JSON-based translations with automatic locale detection
- Lifecycle Hooks: Global and route-level hooks for request processing
- Template Helpers: Laravel-inspired helper functions available in templates
- Plugin System: Extensible architecture with version control and inter-plugin communication
- Built-in Plugins: Development dashboard, sitemap generator, analytics integration (Google, Yandex, Bing)
Installation
npm install -g webspresso
# or
npm install webspressoQuick Start
Using CLI (Recommended)
# Create a new project (Tailwind CSS included by default)
webspresso new my-app
# Navigate to project
cd my-app
# Install dependencies
npm install
# Build Tailwind CSS
npm run build:css
# Start development server (watches both CSS and server)
webspresso dev
# or
npm run devNote: New projects include Tailwind CSS by default. Use
--no-tailwindflag to skip it.
CLI Commands
webspresso new <project-name>
Create a new Webspresso project with Tailwind CSS (default).
webspresso new my-app
# Auto install dependencies and build CSS
webspresso new my-app --install
# Without Tailwind
webspresso new my-app --no-tailwindOptions:
-i, --install- Auto runnpm installandnpm run build:css--no-tailwind- Skip Tailwind CSS setup
The project includes:
- Tailwind CSS with build process
- Optimized layout template with navigation and footer
- Responsive starter page
- i18n setup (en/tr)
- Development and production scripts
webspresso page
Add a new page to your project (interactive prompt).
webspresso pageThe CLI will ask you:
- Route path (e.g.,
/aboutor/blog/post) - Whether to add a route config file
- Whether to add locale files
webspresso api
Add a new API endpoint (interactive prompt).
webspresso apiThe CLI will ask you:
- API route path (e.g.,
/api/usersor/api/users/[id]) - HTTP method (GET, POST, PUT, PATCH, DELETE)
webspresso dev
Start development server with hot reload.
webspresso dev
# or with custom port
webspresso dev --port 3001webspresso start
Start production server.
webspresso start
# or with custom port
webspresso start --port 3000webspresso add tailwind
Add Tailwind CSS to your project with build process.
webspresso add tailwindThis command will:
- Install Tailwind CSS, PostCSS, and Autoprefixer as dev dependencies
- Create
tailwind.config.jsandpostcss.config.js - Create
src/input.csswith Tailwind directives - Add build scripts to
package.json - Update your layout to use the built CSS instead of CDN
- Create
public/css/style.cssfor the compiled output
After running this command:
npm install
npm run build:css # Build CSS once
npm run watch:css # Watch and rebuild CSS on changes
npm run dev # Starts both CSS watch and dev serverProject Structure
Create your app with this structure:
my-app/
├── pages/
│ ├── locales/ # Global i18n translations
│ │ ├── en.json
│ │ └── tr.json
│ ├── _hooks.js # Global lifecycle hooks
│ ├── index.njk # Home page (GET /)
│ ├── about/
│ │ ├── index.njk # About page (GET /about)
│ │ └── locales/ # Route-specific translations
│ ├── tools/
│ │ ├── index.njk # Tools list (GET /tools)
│ │ ├── index.js # Route config with load()
│ │ ├── [slug].njk # Dynamic tool page (GET /tools/:slug)
│ │ └── [slug].js # Route config for dynamic page
│ └── api/
│ ├── health.get.js # GET /api/health
│ └── echo.post.js # POST /api/echo
├── views/
│ └── layout.njk # Base layout template
├── public/ # Static files
└── server.jsAPI
createApp(options)
Creates and configures the Express app.
Options:
pagesDir(required): Path to pages directoryviewsDir(optional): Path to views/layouts directorypublicDir(optional): Path to public/static directorylogging(optional): Enable request logging (default: true in development)helmet(optional): Helmet security configurationtrueorundefined: Use default secure configurationfalse: Disable HelmetObject: Custom Helmet configuration (merged with defaults)
middlewares(optional): Named middleware registry for routes
Example with middlewares:
const { createApp } = require('webspresso');
const { app } = createApp({
pagesDir: './pages',
viewsDir: './views',
middlewares: {
auth: (req, res, next) => {
if (!req.session?.user) {
return res.redirect('/login');
}
next();
},
admin: (req, res, next) => {
if (req.session?.user?.role !== 'admin') {
return res.status(403).send('Forbidden');
}
next();
},
rateLimit: require('express-rate-limit')({ windowMs: 60000, max: 100 })
}
});Then use in route configs by name:
// pages/admin/index.js
module.exports = {
middleware: ['auth', 'admin'], // Use named middlewares
load(req, ctx) { ... }
};
// pages/api/data.get.js
module.exports = {
middleware: ['auth', 'rateLimit'],
handler: (req, res) => res.json({ data: 'protected' })
};Custom Error Pages:
const { createApp } = require('webspresso');
const { app } = createApp({
pagesDir: './pages',
viewsDir: './views',
errorPages: {
// Option 1: Custom handler function
notFound: (req, res) => {
res.render('errors/404.njk', { url: req.url });
},
// Option 2: Template path (rendered with Nunjucks)
serverError: 'errors/500.njk'
}
});Error templates receive these variables:
404.njk:{ url, method }500.njk:{ error, status, isDev }
Asset Management:
Configure asset handling with versioning and manifest support:
const { createApp } = require('webspresso');
const path = require('path');
const { app } = createApp({
pagesDir: './pages',
viewsDir: './views',
publicDir: './public',
assets: {
// Option 1: Simple versioning (cache busting)
version: '1.2.3', // or process.env.APP_VERSION
// Option 2: Manifest file (Vite, Webpack, etc.)
manifestPath: path.join(__dirname, 'public/.vite/manifest.json'),
// URL prefix for assets
prefix: '/static'
}
});Use asset helpers in templates:
{# Using fsy helpers (auto-resolved) #}
<link rel="stylesheet" href="{{ fsy.asset('/css/style.css') }}">
{# Or generate full HTML tags #}
{{ fsy.css('/css/style.css') | safe }}
{{ fsy.js('/js/app.js', { defer: true, type: 'module' }) | safe }}
{{ fsy.img('/images/logo.png', 'Site Logo', { class: 'logo', loading: 'lazy' }) | safe }}Asset helpers available in fsy:
asset(path)- Returns versioned/manifest-resolved URLcss(href, attrs)- Generates<link>tagjs(src, attrs)- Generates<script>tagimg(src, alt, attrs)- Generates<img>tag
Manifest Support:
Works with Vite and Webpack manifest formats:
// Vite manifest format (.vite/manifest.json)
{
"css/style.css": { "file": "assets/style-abc123.css" },
"js/app.js": { "file": "assets/app-xyz789.js" }
}
// Webpack manifest format
{
"/css/style.css": "/dist/style.abc123.css",
"/js/app.js": "/dist/app.xyz789.js"
}Returns: { app, nunjucksEnv, pluginManager }
Plugin System
Webspresso has a built-in plugin system with version control and dependency management.
Using Plugins
const { createApp } = require('webspresso');
const { sitemapPlugin, analyticsPlugin, dashboardPlugin } = require('webspresso/plugins');
const { app } = createApp({
pagesDir: './pages',
viewsDir: './views',
plugins: [
dashboardPlugin(), // Dev dashboard at /_webspresso
sitemapPlugin({
hostname: 'https://example.com',
exclude: ['/admin/*', '/api/*'],
i18n: true,
locales: ['en', 'tr']
}),
analyticsPlugin({
google: {
measurementId: 'G-XXXXXXXXXX',
verificationCode: 'xxxxx'
},
yandex: {
counterId: '12345678',
verificationCode: 'xxxxx'
},
bing: {
uetId: '12345678',
verificationCode: 'xxxxx'
}
})
]
});Built-in Plugins
Dashboard Plugin:
- Development dashboard at
/_webspresso - Monitor all routes (SSR pages and API endpoints)
- View loaded plugins and configuration
- Filter and search routes
- Only active in development mode (disabled in production)
const { dashboardPlugin } = require('webspresso/plugins');
const { app } = createApp({
pagesDir: './pages',
plugins: [
dashboardPlugin() // Available at /_webspresso in dev mode
]
});Options:
path- Custom dashboard path (default:/_webspresso)enabled- Force enable/disable (default: auto based on NODE_ENV)
Sitemap Plugin:
- Generates
/sitemap.xmlfrom routes automatically - Excludes dynamic routes and API endpoints
- Supports i18n with hreflang tags
- Generates
/robots.txt
Analytics Plugin:
- Google Analytics (GA4) and Google Ads
- Google Tag Manager
- Yandex.Metrika
- Microsoft/Bing UET
- Facebook Pixel
- Verification meta tags for all services
Template helpers from analytics plugin:
<head>
{{ fsy.verificationTags() | safe }}
{{ fsy.analyticsHead() | safe }}
</head>
<body>
{{ fsy.analyticsBodyOpen() | safe }}
...
</body>Individual helpers: gtag(), gtm(), gtmNoscript(), yandexMetrika(), bingUET(), facebookPixel(), allAnalytics()
Creating Custom Plugins
const myPlugin = {
name: 'my-plugin',
version: '1.0.0',
// Optional: depend on other plugins
dependencies: {
'analytics': '^1.0.0'
},
// Optional: expose API for other plugins
api: {
getData() { return this.data; }
},
// Called during registration
register(ctx) {
// Access Express app
ctx.app.use((req, res, next) => next());
// Add template helpers
ctx.addHelper('myHelper', () => 'Hello!');
// Add Nunjucks filters
ctx.addFilter('myFilter', (val) => val.toUpperCase());
// Use other plugins
const analytics = ctx.usePlugin('analytics');
},
// Called after all routes are mounted
onRoutesReady(ctx) {
// Access route metadata
console.log('Routes:', ctx.routes);
// Add custom routes
ctx.addRoute('get', '/my-route', (req, res) => {
res.json({ hello: 'world' });
});
},
// Called before server starts
onReady(ctx) {
console.log('Server ready!');
}
};
// Use as factory function for configuration
function myPluginFactory(options = {}) {
return {
name: 'my-plugin',
version: '1.0.0',
_options: options,
register(ctx) {
// ctx.options contains the passed options
}
};
}File-Based Routing
SSR Pages
Create .njk files in the pages/ directory:
| File Path | Route |
|-----------|-------|
| pages/index.njk | / |
| pages/about/index.njk | /about |
| pages/tools/[slug].njk | /tools/:slug |
| pages/docs/[...rest].njk | /docs/* |
API Routes
Create .js files in pages/api/ with optional method suffixes:
| File Path | Route |
|-----------|-------|
| pages/api/health.get.js | GET /api/health |
| pages/api/echo.post.js | POST /api/echo |
| pages/api/users/[id].get.js | GET /api/users/:id |
Route Config
Add a .js file alongside your .njk file to configure the route:
// pages/tools/index.js
module.exports = {
// Middleware for this route
middleware: [(req, res, next) => next()],
// Load data for SSR
async load(req, ctx) {
return { tools: await fetchTools() };
},
// Override meta tags
meta(req, ctx) {
return {
title: 'Tools',
description: 'Developer tools'
};
},
// Route-level hooks
hooks: {
beforeLoad: async (ctx) => {},
afterRender: async (ctx) => {}
}
};i18n
Global Translations
Add JSON files to pages/locales/:
// pages/locales/en.json
{
"nav": {
"home": "Home",
"about": "About"
}
}Route-Specific Translations
Add a locales/ folder inside any route directory to override global translations.
Using Translations
In templates:
<h1>{{ t('nav.home') }}</h1>Template Helpers
The fsy object is available in all templates:
{# URL helpers #}
{{ fsy.url('/tools', { page: 1 }) }}
{{ fsy.fullUrl('/tools') }}
{{ fsy.route('/tools/:slug', { slug: 'test' }) }}
{# Request helpers #}
{{ fsy.q('page', 1) }}
{{ fsy.param('slug') }}
{{ fsy.hdr('User-Agent') }}
{# Utility helpers #}
{{ fsy.slugify('Hello World') }}
{{ fsy.truncate(text, 100) }}
{{ fsy.prettyBytes(1024) }}
{{ fsy.prettyMs(5000) }}
{# Environment #}
{% if fsy.isDev() %}Dev mode{% endif %}
{# SEO #}
{{ fsy.canonical() }}
{{ fsy.jsonld(schema) | safe }}Lifecycle Hooks
Global Hooks
Create pages/_hooks.js:
module.exports = {
onRequest(ctx) {},
beforeLoad(ctx) {},
afterLoad(ctx) {},
beforeRender(ctx) {},
afterRender(ctx) {},
onError(ctx, err) {}
};Hook Execution Order
- Global
onRequest - Route
onRequest - Route
beforeMiddleware - Route middleware
- Route
afterMiddleware - Route
beforeLoad - Route
load() - Route
afterLoad - Route
beforeRender - Nunjucks render
- Route
afterRender
Environment Variables
| Variable | Default | Description |
|----------|---------|-------------|
| NODE_ENV | development | Environment |
| DEFAULT_LOCALE | en | Default locale |
| SUPPORTED_LOCALES | en | Comma-separated locales |
| BASE_URL | http://localhost:3000 | Base URL for canonical URLs |
Development
# Install dependencies
npm install
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageLicense
MIT
