Minimal, production-ready SSR framework for Node.js with file-based routing, Nunjucks templating, built-in i18n, and CLI tooling
npm install webspressoA minimal, file-based SSR framework for Node.js with Nunjucks templating.
- File-Based Routing: Create pages by adding .njk files to a pages/ directory
- Dynamic Routes: Use [param] for dynamic params and [...rest] for catch-all routes
- API Endpoints: Add .js files to pages/api/ with method suffixes (e.g., health.get.js)
- Schema Validation: Zod-based request validation for body, params, and query
- 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, SEO checker, analytics integration (Google, Yandex, Bing)
``bash`
npm install -g webspressoor
npm install webspresso
`bashCreate a new project (Tailwind CSS included by default)
webspresso new my-app
> Note: New projects include Tailwind CSS by default. Use
--no-tailwind flag to skip it.CLI Commands
$3
Create a new Webspresso project with Tailwind CSS (default).
`bash
Create in a new directory
webspresso new my-appCreate in current directory (interactive)
webspresso new
→ Prompts: "Install in current directory?"
→ If yes, asks for project name (for package.json)
Auto install dependencies and build CSS
webspresso new my-app --installWithout Tailwind
webspresso new my-app --no-tailwind
`Interactive Mode (no arguments):
- Asks if you want to install in the current directory
- If current directory is not empty, shows a warning
- Prompts for project name (defaults to current folder name)
- Asks if you will use a database (SQLite, PostgreSQL, or MySQL)
- If yes, adds the appropriate driver to
package.json and creates webspresso.db.js config
- After project creation, asks if you want to install dependencies
- If yes, runs npm install and npm run build:css
- Then asks if you want to start the development server
- If yes, starts npm run dev automaticallyAuto Installation:
`bash
With --install flag (semi-interactive)
webspresso new my-app --install
→ Automatically runs: npm install && npm run build:css
→ Then prompts: "Start development server?" [Y/n]
→ If yes: starts npm run dev (with watch:css if Tailwind enabled)
Without --install flag (fully interactive)
webspresso new my-app
→ Prompts: "Install dependencies and build CSS now?" [Y/n]
→ If yes: runs npm install && npm run build:css
→ Then: "Start development server?" [Y/n]
→ If yes: starts npm run dev (with watch:css if Tailwind enabled)
`Note: When dev server starts with Tailwind CSS, it automatically runs
watch:css in the background to watch for CSS changes.Database Selection:
During project creation, you'll be asked if you want to use a database:
- SQLite (better-sqlite3) - Recommended for development and small projects
- PostgreSQL (pg) - For production applications
- MySQL (mysql2) - Alternative SQL database
If you select a database:
- The appropriate driver is added to
package.json dependencies
- webspresso.db.js config file is created with proper settings
- migrations/ directory is created
- models/ directory is created
- DATABASE_URL is added to .env.example with a templateSeed Data Generation:
After selecting a database, you'll be asked if you want to generate seed data:
- If yes,
@faker-js/faker is added to dependencies
- seeds/ directory is created with seeds/index.js
- npm run seed script is added to package.json
- The seed script automatically detects models in models/ directory and generates fake data based on their schemasTo run seeds after creating models:
`bash
npm run seed
`The seed script will:
- Load all models from
models/ directory
- Generate 10 fake records per model (by default)
- Use smart field detection based on column names (email, name, title, etc.)You can always add database support later by:
1. Installing the driver:
npm install better-sqlite3 (or pg, mysql2)
2. Creating webspresso.db.js config file
3. Adding DATABASE_URL to your .env file
4. Creating models/ directory and defining your models
5. Optionally adding seed support: npm install @faker-js/faker and creating seeds/index.jsOptions:
-
-i, --install - Auto run npm install and npm run build:css (non-interactive)
- --no-tailwind - Skip Tailwind CSS setupThe 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
$3
Add a new page to your project (interactive prompt).
`bash
webspresso page
`The CLI will ask you:
- Route path (e.g.,
/about or /blog/post)
- Whether to add a route config file
- Whether to add locale files$3
Add a new API endpoint (interactive prompt).
`bash
webspresso api
`The CLI will ask you:
- API route path (e.g.,
/api/users or /api/users/[id])
- HTTP method (GET, POST, PUT, PATCH, DELETE)$3
Start development server with hot reload.
`bash
webspresso dev
or with custom port
webspresso dev --port 3001
`$3
Start production server.
`bash
webspresso start
or with custom port
webspresso start --port 3000
`$3
Add Tailwind CSS to your project with build process.
`bash
webspresso add tailwind
`This command will:
- Install Tailwind CSS, PostCSS, and Autoprefixer as dev dependencies
- Create
tailwind.config.js and postcss.config.js
- Create src/input.css with Tailwind directives
- Add build scripts to package.json
- Update your layout to use the built CSS instead of CDN
- Create public/css/style.css for the compiled outputAfter running this command:
`bash
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 server
`Project 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.js
`API
$3
Creates and configures the Express app.
Options:
-
pagesDir (required): Path to pages directory
- viewsDir (optional): Path to views/layouts directory
- publicDir (optional): Path to public/static directory
- logging (optional): Enable request logging (default: true in development)
- helmet (optional): Helmet security configuration
- true or undefined: Use default secure configuration
- false: Disable Helmet
- Object: Custom Helmet configuration (merged with defaults)
- middlewares (optional): Named middleware registry for routesExample with middlewares:
`javascript
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:
`javascript
// 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:
`javascript
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',
// Timeout error page (503)
timeout: 'errors/503.njk'
}
});
`Error templates receive these variables:
-
404.njk: { url, method }
- 500.njk: { error, status, isDev }
- 503.njk: { url, method, isDev }Request Timeout:
Configure request timeout with
connect-timeout:`javascript
const { app } = createApp({
pagesDir: './pages',
timeout: '30s', // Default: 30 seconds
// timeout: '1m', // 1 minute
// timeout: false, // Disable timeout
});
`Asset Management:
Configure asset handling with versioning and manifest support:
`javascript
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:
`njk
{# Using fsy helpers (auto-resolved) #}
{# 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 URL
- css(href, attrs) - Generates tag
- js(src, attrs) - Generates