SpaceNode
Get a working SpaceNode application in under 2 minutes.
# Create a new project
mkdir my-app && cd my-app
npm init -y
# Install SpaceNode
npm install SpaceNode
Create app.js in your project root:
import { createApp } from 'SpaceNode'
const app = await createApp()
app.listen(3000)
package.json has "type": "module" for ESM imports.Create the folder modules/hello/ and add two files:
export default {
name: 'hello',
prefix: '/hello',
routes: [
['GET', '/', 'index'],
['GET', '/:name', 'greet'],
],
}
export function index({ send }) {
send({ message: 'Hello from SpaceNode!' })
}
export function greet({ params, send }) {
send({ message: `Hello, ${params.name}!` })
}
node app.js
You'll see:
═══════════════════════════════════════
⚡ SpaceNode v1.0.0
═══════════════════════════════════════
Port: 3000
Modules: 1
Routes: 2
Services: 0
═══════════════════════════════════════
GET /hello
GET /hello/:name
curl http://localhost:3000/hello
→ {"message":"Hello from SpaceNode!"}
curl http://localhost:3000/hello/World
→ {"message":"Hello, World!"}
SpaceNode covers every common web scenario out of the box. Pick the approach that matches your project — or combine them freely in a single app.
| Mode | What It Is | Best For |
|---|---|---|
| Static Site | Serve plain HTML/CSS/JS with zero code | Landing pages, portfolios, SPAs (React / Vue / Svelte) |
| REST API | JSON endpoints with modules, validation, auth | Mobile backends, microservices, SPA backends |
| SSR Site | Server-rendered HTML with a built-in template engine | SEO-critical pages, dashboards, full-stack apps |
The simplest option — serve static files from a folder. No modules, no controllers, just one line of config. SpaceNode auto-detects 30+ MIME types, adds ETag caching, and protects against directory traversal.
When to use: landing pages, documentation sites, portfolios, any pre-built SPA (React, Vue, Svelte, Astro, etc.).
// app.js
import { createApp } from 'SpaceNode'
const app = await createApp({
static: './public', // folder with your HTML/CSS/JS
spa: false, // multi-page mode (true → SPA fallback to index.html)
})
app.listen(3000)
my-site/
├── app.js
├── package.json
└── public/
├── index.html
├── about.html
├── style.css
├── 404.html ← custom error page (auto-detected)
└── images/
└── logo.png
| Clean URLs | /about automatically resolves to about.html |
| SPA Mode | Set spa: true (default) — unknown paths fall back to index.html |
| ETag & 304 | Built-in caching: browsers skip re-downloading unchanged files |
| Large files | Files over 256 KB are streamed, not buffered into memory |
| Security | Directory traversal attacks are blocked automatically |
pipe: ['compress', 'security'] for production to enable gzip and security headers.The module-based pattern you saw in the quick start above. Each module lives in its own folder with a module.js (routes), a controller (handlers), and optionally services, DTOs, and guards. SpaceNode auto-discovers modules and wires everything together.
When to use: any JSON backend — mobile apps, SPA backends, microservices, third-party integrations.
| Auto Module Loading | Drop a folder into modules/ — it's registered automatically |
| DTO Validation | Declare expected fields & types in a DTO; invalid requests are rejected before your code runs |
| Auth & Guards | defineAuth() for JWT/session auth, defineGuard() for role checks |
| CORS | Add 'cors' to pipe — preflight and headers are handled for you |
| OpenAPI | Pass openapi: { title, version } and a Swagger spec is generated from your routes |
| Event Bus | request.emit('order.created', data) for decoupled side-effects |
| Services & DI | Export a service.js — it's auto-injected into controllers as the second argument |
my-api/
├── app.js
├── package.json
└── modules/
├── auth/
│ ├── module.js
│ ├── auth.controller.js
│ ├── auth.service.js
│ └── auth.dto.js
└── products/
├── module.js
├── products.controller.js
├── products.service.js
└── products.dto.js
// app.js
import { createApp, defineAuth } from 'SpaceNode'
defineAuth(async (req) => {
const token = req.headers['authorization']?.replace('Bearer ', '')
if (!token) return null
return verifyJWT(token) // return user object or null
})
const app = await createApp({
pipe: ['cors', 'logger'],
openapi: { title: 'My API', version: '1.0.0' },
})
app.listen(3000)
// modules/products/module.js
export default {
name: 'products',
prefix: '/products',
routes: [
['GET', '/', 'list'],
['GET', '/:id', 'getOne'],
['POST', '/', 'create', ['auth', 'dto:createDto']],
['DELETE', '/:id', 'remove', ['auth', 'role:admin']],
],
}
// modules/products/products.controller.js
export function list(req, { productService }) {
const items = await productService.findAll()
req.send(items)
}
export function create(req, { productService }) {
const product = await productService.create(req.body)
req.send(product, 201)
}
['auth', 'dto:createDto'] pipes run authentication and DTO validation automatically before your handler is called.SpaceNode includes a full template engine compiled ahead-of-time. Write .html templates with [= variable] syntax, layouts, partials, pipe filters — and render pages on the server with data from your database. No React, no build step, just HTML and JavaScript.
When to use: SEO-critical websites (content, blogs, e-commerce), admin dashboards, multi-page apps with forms, any project where you want server-rendered HTML without a frontend framework.
| Template Syntax | [= expr] output, [# if/each/block] logic, [> include] partials |
| Auto Escaping | All output is HTML-escaped by default — XSS protection built in |
| Layouts | One-level layout wrapping every page; [= body] injects page content |
| Pipe Filters | [= price | currency] — 10+ built-in helpers, add custom ones via addHelper() |
| AOT Compilation | Templates → AST → JS functions — near-zero render overhead |
| CSRF | Add 'csrf' pipe and [= csrfField] is auto-available in forms |
| Flash Messages | request.flash('success', msg) — displayed once on next page |
| LRU Cache | Compiled templates cached in memory (default 500 entries) |
// app.js
import { createApp } from 'SpaceNode'
const app = await createApp({
views: './views', // templates directory
static: './public', // CSS, JS, images
baseUrl: import.meta.url,
})
app.listen(3000)
my-site/
├── app.js
├── package.json
├── modules/
│ ├── auth/
│ │ ├── module.js
│ │ └── auth.controller.js
│ └── profile/
│ ├── module.js
│ └── profile.controller.js
├── views/
│ ├── settings.js ← layout & globals config
│ ├── layout.html ← site-wide wrapper
│ ├── home.html
│ ├── login.html
│ ├── nav.html ← partial (include)
│ └── footer.html
└── public/
└── css/
└── style.css
<!-- views/layout.html -->
<!DOCTYPE html>
<html>
<head>
<title>[= title] — [= siteName]</title>
<link rel="stylesheet" href="/css/style.css">
</head>
<body>
[> nav]
<main>[= body]</main>
[> footer]
</body>
</html>
<!-- views/home.html -->
<h1>Welcome, [= user.name]!</h1>
[# if items.length]
<ul>
[# each items as item]
<li>[= item.name] — [= item.price | currency]</li>
[/each]
</ul>
[# else]
<p>No items yet.</p>
[/if]
// modules/profile/profile.controller.js
export async function show(req, { userService }) {
const user = await userService.findById(req.user.id)
req.render('profile', {
title: 'My Profile',
user,
})
}
// views/settings.js
export default {
layout: 'layout', // every page wrapped in layout.html
globals: {
siteName: 'My Site',
year: new Date().getFullYear(),
},
}
settings.js are available in every template without passing them from the controller.You're not limited to one mode. A single SpaceNode app can serve static files, expose a JSON API, and render SSR pages — all at the same time:
const app = await createApp({
static: './public', // serve CSS / JS / images
views: './views', // enable SSR template engine
pipe: ['cors'], // enable CORS for API routes
baseUrl: import.meta.url,
})
Then modules can freely mix JSON responses (req.send()) and rendered pages (req.render()) within the same project.
| Static Site | REST API | SSR Site | |
|---|---|---|---|
| Config key | static: './public' |
pipe: ['cors'] |
views: './views' |
| Modules needed | None | Yes | Yes |
| Response type | Files (HTML, CSS, JS) | JSON | Rendered HTML |
| SEO | Full (pre-built HTML) | N/A | Full (server-rendered) |
| Build step | Optional (SPA bundlers) | None | None |
| Auth & validation | — | defineAuth(), DTOs |
Guards, cookies, CSRF |
| Detailed guide | Static Site → | REST API → | SSR Site → |