Examples
Real, runnable code examples. Copy, paste, adapt.
Hello World
import { LiteNode } from "litenode"
const app = new LiteNode()
app.get("/", (req, res) => {
res.end("Hello, LiteNode!")
})
app.startServer() // → http://localhost:5000
Routing
Named route parameters
app.get("/users/:id", (req, res) => {
res.json({ userId: req.params.id })
})
Optional parameters
// Matches both /users and /users/123
app.get("/users/:id?", (req, res) => {
if (req.params.id) {
res.json({ user: req.params.id })
} else {
res.json({ all: true })
}
})
Chained optional parameters
app.get("/blog/:category?/:post?", (req, res) => {
const { category, post } = req.params
if (post) return res.json({ post, category })
if (category) return res.json({ category })
res.json({ all: true })
})
// Matches /blog, /blog/tech, /blog/tech/nodejs
Wildcard routes
// Single segment: /files/image.png (not /files/sub/image.png)
app.wildcard("/files", (req, res) => {
res.txt(`File: ${req.params["*"]}`)
})
// Multi-segment: /api/users/123/posts
app.catchAll("/api", (req, res) => {
res.json({ path: req.params["**"] })
})
Query parameters
app.get("/search", (req, res) => {
const q = req.queryParams.get("q")
const page = req.queryParams.get("page") || "1"
res.json({ query: q, page })
})
// GET /search?q=node&page=2
All HTTP methods on one route
app.any("/health", (req, res) => {
res.json({ status: "ok", method: req.method })
})
Combined routing patterns
app.get("/admin/users/:file?", (req, res) => {
const { file } = req.params
if (file) {
res.json({ viewing: file })
} else {
res.json({ viewing: "all users" })
}
})
app.get("/catalog/:category?/:id?", (req, res) => {
const { category, id } = req.params
if (id) return res.json({ category, id })
if (category) return res.json({ category })
res.json({ catalog: true })
})
Response Methods
// Plain text
app.get("/ping", (req, res) => {
res.txt("pong")
})
// JSON
app.get("/api/users", (req, res) => {
res.json([{ id: 1, name: "Alice" }])
})
// HTML
app.get("/welcome", (req, res) => {
res.html("<h1>Welcome</h1>")
})
// XML / RSS
app.get("/feed", (req, res) => {
res.xml(`<rss version="2.0"><channel><title>Feed</title></channel></rss>`)
})
// Send a file
app.get("/resume", (req, res) => {
res.sendFile("./files/resume.pdf")
})
// Redirect (permanent)
app.get("/old-page", (req, res) => {
res.redirect("/new-page", 301)
})
// Status + chain
app.post("/items", (req, res) => {
res.status(201).json({ created: true })
})
// Error responses
app.onError(async (err, req, res) => {
if (err.type === "unauthorized") {
return res.status(401).json({ error: "Unauthorized" })
}
if (err.type === "not_found") {
return res.status(404).html("<h1>Not Found</h1>")
}
res.status(500).html("<h1>Something went wrong</h1>")
})
Middleware
Request logger
app.use(async (req, res) => {
console.log(`[${new Date().toISOString()}] ${req.method} ${req.url}`)
})
Auth middleware
const authenticate = async (req, res) => {
const token = req.headers["authorization"]
if (!token) {
return res.status(401).json({ error: "Unauthorized" })
}
req.user = await verifyToken(token)
}
const requireAdmin = async (req, res) => {
if (!req.user?.isAdmin) {
return res.status(403).json({ error: "Forbidden" })
}
}
app.get("/admin/dashboard", authenticate, requireAdmin, (req, res) => {
res.render("admin/dashboard.html", { user: req.user })
})
Rate limiting middleware
const requestCounts = new Map()
const rateLimiter = async (req, res) => {
const ip = req.socket.remoteAddress
const count = (requestCounts.get(ip) || 0) + 1
requestCounts.set(ip, count)
if (count > 100) {
return res.status(429).json({ error: "Too Many Requests" })
}
setTimeout(() => requestCounts.delete(ip), 60_000)
}
app.get("/api/data", rateLimiter, (req, res) => {
res.json({ data: "ok" })
})
Body Parsing
JSON — client
<form onsubmit="handleSubmit(event)">
<input type="text" name="username" required />
<input type="email" name="email" required />
<input type="password" name="password" required />
<button type="submit">Register</button>
</form>
<script>
async function handleSubmit(event) {
event.preventDefault()
const data = Object.fromEntries(new FormData(event.target).entries())
const res = await fetch("/register", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(data),
})
const result = await res.json()
if (!res.ok) return alert("Error: " + result.error)
alert(result.message)
}
</script>
JSON — server
app.post(
"/register",
async (req, res, data) => {
const { username, email, password } = data
if (!username || !email || !password) {
return res.status(400).json({ error: "Missing required fields" })
}
// database logic...
res.status(201).json({ message: "User registered successfully" })
},
0.5, // 512 KB max
)
URL-encoded form — client
<form action="/create-file" method="POST">
<input type="text" name="file[title]" required />
<textarea name="file[description]" required></textarea>
<input type="url" name="file[image]" required />
<input type="date" name="file[publish_date]" required />
<input type="text" name="file[tags]" placeholder="tag1, tag2" />
<button type="submit">Create</button>
</form>
URL-encoded form — server
app.post("/create-file", (req, res, data) => {
const { file } = data
// file.title, file.description, file.image, file.publish_date, file.tags
res.status(201).json({ message: "File created" })
})
app.put("/update-file/:id", (req, res) => {
const fileId = req.params.id
const { file } = req.body
res.json({ message: `File ${fileId} updated` })
})
File upload — client
<form action="/upload" method="post" enctype="multipart/form-data">
<input type="file" name="files" multiple />
<button type="submit">Upload</button>
</form>
File upload — server
import { writeFile } from "node:fs/promises"
app.post(
"/upload",
async (req, res, data) => {
const { files } = data
for (const file of files) {
await writeFile(`uploads/${file.filename}`, file.body)
}
res.redirect("/uploads")
},
5, // 5 MB max
)
// PATCH — update existing files
app.patch(
"/update-images/:folder",
async (req, res) => {
const { folder } = req.params
const { files } = req.body
for (const file of files) {
await writeFile(`${folder}/${file.filename}`, file.body)
}
res.json({ message: "Files updated" })
},
5,
)
Mixed form (file + text fields) — client
<form action="/profile" method="post" enctype="multipart/form-data">
<input type="file" name="avatar" accept="image/*" required />
<input type="text" name="username" required />
<input type="number" name="age" required />
<button type="submit">Save profile</button>
</form>
Mixed form (file + text fields) — server
In a multipart form, every field — file or text — arrives as an array of objects. Text field values live in a body Buffer and must be converted to a string explicitly. This differs from JSON and URL-encoded forms, where text fields are plain strings.
import { writeFile } from "node:fs/promises"
// Helper to safely extract a text field value
function getField(data, name, fallback = "") {
const field = data[name]
if (!field || !Array.isArray(field) || !field[0]) return fallback
return field[0].body.toString("utf-8").trim() || fallback
}
app.post(
"/profile",
async (req, res, data) => {
const avatar = data.avatar?.[0]
const username = getField(data, "username")
const age = parseInt(getField(data, "age"), 10)
if (!avatar || !username || isNaN(age)) {
return res.status(400).json({ error: "Missing required fields." })
}
await writeFile(`avatars/${avatar.filename}`, avatar.body)
res.status(201).json({ username, age })
},
2, // 2 MB max
)
Cookies
Basic cookie — language preference
import { LiteNode } from "litenode"
const app = new LiteNode()
app.enableCookieParser()
app.get("/set-language", (req, res) => {
const lang = req.queryParams.get("lang") || "en"
res.setCookie("language", lang, { maxAge: 365 * 24 * 60 * 60 })
res.redirect("/")
})
app.get("/", (req, res) => {
const language = req.cookies.language || "en"
res.render("home.html", { language })
})
app.startServer()
Authentication with signed cookies
import { LiteNode } from "litenode"
const app = new LiteNode()
app.enableCookieParser()
const signedCookies = app.createSignedCookies("your-strong-secret-key-here")
const users = {
alice: { password: "pass1", name: "Alice" },
bob: { password: "pass2", name: "Bob" },
}
app.post("/login", async (req, res) => {
const { username, password } = req.body
if (!users[username] || users[username].password !== password) {
return res.status(401).txt("Invalid credentials")
}
await signedCookies.setCookie(res, "session", username, {
maxAge: 3600,
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "Lax",
})
res.redirect("/dashboard")
})
app.get("/dashboard", async (req, res) => {
const username = await signedCookies.getCookie(req, "session")
if (!username || !users[username]) return res.redirect("/login")
res.render("dashboard.html", { name: users[username].name })
})
app.get("/logout", (req, res) => {
res.clearCookie("session")
res.redirect("/login")
})
app.startServer()
User preferences in a cookie
app.post("/save-preferences", (req, res) => {
const { theme, fontSize, notifications } = req.body
const prefs = JSON.stringify({
theme: theme || "light",
fontSize: fontSize || "medium",
notifications: notifications === "on",
})
res.setCookie("userPrefs", prefs, { maxAge: 365 * 24 * 60 * 60, path: "/" })
res.redirect("/settings?saved=true")
})
app.use((req, res) => {
let userPrefs = { theme: "light", fontSize: "medium", notifications: true }
if (req.cookies.userPrefs) {
try {
userPrefs = JSON.parse(req.cookies.userPrefs)
} catch (_) {}
}
req.userPreferences = userPrefs
})
app.get("/settings", (req, res) => {
const saved = req.queryParams.get("saved") === "true"
res.render("settings.html", { preferences: req.userPreferences, saved })
})
Environment Variables
Full configuration object
import { LiteNode } from "litenode"
const app = new LiteNode()
app.loadEnv()
const env = process.env.NODE_ENV || "development"
app.loadEnv(`.env.${env}`, { override: true, silent: true })
const config = {
server: {
port: app.getEnv("PORT", 5000),
host: app.getEnv("HOST", "localhost"),
cors: app.getEnv("ENABLE_CORS", true),
},
database: {
host: app.getEnv("DB_HOST", "localhost"),
port: app.getEnv("DB_PORT", 5432),
user: app.getEnv("DB_USER", "postgres"),
password: app.getEnv("DB_PASS", ""),
name: app.getEnv("DB_NAME", "app"),
ssl: app.getEnv("DB_SSL", false),
},
cache: {
enabled: app.getEnv("ENABLE_CACHE", true),
ttl: app.getEnv("CACHE_TTL", 60),
},
}
if (config.server.cors) {
app.use((req, res) => {
res.setHeader("Access-Control-Allow-Origin", "*")
res.setHeader("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE")
res.setHeader("Access-Control-Allow-Headers", "Content-Type, Authorization")
})
}
app.get("/", (req, res) => {
res.html(`
<h1>${app.getEnv("APP_NAME", "LiteNode App")}</h1>
<p>Running on ${config.server.host}:${config.server.port}</p>
`)
})
app.startServer(config.server.port)
Merge and Nest Routers
Modular application with auth-protected admin
import { LiteNode } from "litenode"
const app = new LiteNode()
const api = new LiteNode("__NO_STATIC_DIR__")
const users = new LiteNode("__NO_STATIC_DIR__")
const admin = new LiteNode("__NO_STATIC_DIR__")
const authenticate = async (req, res) => {
if (!req.headers["authorization"]) {
return res.status(401).json({ error: "Unauthorized" })
}
}
users.get("/", (req, res) => res.json({ users: [] }))
users.get("/:id", (req, res) => res.json({ id: req.params.id }))
users.post("/", (req, res) => res.status(201).json({ created: req.body }))
admin.get("/stats", (req, res) => res.json({ stats: "ok" }))
admin.get("/reports", (req, res) => res.json({ reports: [] }))
api.nest("/users", users)
api.nest("/admin", admin, authenticate) // auth-protected
app.merge(api)
app.notFound((req, res) => res.status(404).json({ error: "Not found" }))
app.startServer()
Templates
Render a page with data
app.get("/about", (req, res) => {
res.render("pages/about.html", {
title: "About",
team: ["Alice", "Bob", "Carol"],
isLoggedIn: true,
})
})
Static site generation (SSG)
import { LiteNode } from "litenode"
import { marked } from "marked"
import { mkdir, rm, cp } from "node:fs/promises"
const app = new LiteNode()
async function build() {
await rm("_site", { recursive: true, force: true })
await mkdir("_site/docs", { recursive: true })
await cp("static", "_site/static", { recursive: true })
await app.renderToFile("layouts/index.html", { title: "Home" }, "_site/index.html")
const pages = await app.parseMarkdownFileS("pages")
pages.sort((a, b) => a.frontmatter.titleIndex - b.frontmatter.titleIndex)
const menuArray = await app.extractMarkdownProperties(pages, ["title", "href"])
for (const page of pages) {
const html = marked.parse(page.content)
const toc = app.generateTOC(html)
await mkdir(`_site/docs/${page.frontmatter.href}`, { recursive: true })
await app.renderToFile(
"layouts/docs.html",
{
title: page.frontmatter.title,
html_content: html,
html_toc: toc,
tocLength: toc.length,
menuArray,
},
`_site/docs/${page.frontmatter.href}/index.html`,
)
}
console.log("✅ Build complete → _site/")
}
build()
Markdown
Blog with pagination
import { LiteNode } from "litenode"
import { marked } from "marked"
const app = new LiteNode()
app.get("/blog", async (req, res) => {
const page = parseInt(req.queryParams.get("page")) || 1
const pagination = await app.paginateMarkdownFiles("posts", page, 10)
res.render("layouts/blog.html", {
posts: pagination.data,
currentPage: pagination.page,
totalPages: pagination.total_pages,
prevPage: pagination.prev_page,
nextPage: pagination.next_page,
})
})
app.get("/blog/:slug", async (req, res) => {
const pages = await app.parseMarkdownFileS("posts")
const post = pages.find((p) => p.frontmatter.slug === req.params.slug)
if (!post) return res.redirect("/404")
const html = marked.parse(post.content)
const toc = app.generateTOC(html)
res.render("layouts/post.html", {
title: post.frontmatter.title,
html_content: html,
html_toc: toc,
tocLength: toc.length,
})
})
app.get("/categories", async (req, res) => {
const grouped = await app.groupByMarkdownProperty("posts", ["title", "href", "category"], "category")
res.render("layouts/categories.html", { grouped })
})
app.startServer()
Documentation site
app.get("/docs/:filename", async (req, res) => {
const pages = await app.parseMarkdownFileS("pages")
pages.sort((a, b) => parseInt(a.frontmatter.titleIndex, 10) - parseInt(b.frontmatter.titleIndex, 10))
const current = pages.find((f) => f.frontmatter.href === req.params.filename)
const menuArray = await app.extractMarkdownProperties(pages, ["title", "href"])
if (!current) return res.redirect("/404")
const html = marked.parse(current.content)
const toc = app.generateTOC(html)
res.render("layouts/docs.html", {
title: current.frontmatter.title,
description: current.frontmatter.description,
html_content: html,
html_toc: toc,
tocLength: toc.length,
menuArray,
})
})
Error Handling
app.onError(async (err, req, res) => {
console.error(err)
res.status(500).render("layouts/error.html", { title: "Server Error" })
})
app.notFound(async (req, res) => {
res.status(404).render("layouts/404.html", { title: "Not Found" })
})
// Route-level try/catch
app.get("/posts/:slug", async (req, res) => {
try {
const post = await db.getPost(req.params.slug)
if (!post) return res.status(404).json({ error: "Post not found" })
res.json(post)
} catch (err) {
res.status(500).json({ error: err.message })
}
})
Full Application
A complete application combining routing, middleware, markdown, cookies, and error handling:
import { LiteNode } from "litenode"
import { marked } from "marked"
const app = new LiteNode()
app.loadEnv()
app.enableCookieParser()
const signedCookies = app.createSignedCookies(app.getEnv("SESSION_SECRET", "change-me"))
app.onError(async (err, req, res) => {
res.status(500).render("layouts/error.html", { title: "Error" })
})
app.notFound(async (req, res) => {
res.status(404).render("layouts/404.html", { title: "Not Found" })
})
const requireAuth = async (req, res) => {
const userId = await signedCookies.getCookie(req, "userId")
if (!userId) return res.redirect("/login")
req.userId = userId
}
app.get("/", (req, res) => {
res.render("layouts/index.html", { title: "Home" })
})
app.get("/blog", async (req, res) => {
const page = parseInt(req.queryParams.get("page")) || 1
const pagination = await app.paginateMarkdownFiles("posts", page, 5)
res.render("layouts/blog.html", { ...pagination })
})
app.get("/blog/:slug", async (req, res) => {
const pages = await app.parseMarkdownFileS("posts")
const post = pages.find((p) => p.frontmatter.slug === req.params.slug)
if (!post) return res.redirect("/404")
const html = marked.parse(post.content)
res.render("layouts/post.html", { title: post.frontmatter.title, html_content: html })
})
app.get("/dashboard", requireAuth, (req, res) => {
res.render("layouts/dashboard.html", { userId: req.userId })
})
app.post("/login", async (req, res) => {
const { username, password } = req.body
const user = await authenticateUser(username, password)
if (!user) return res.status(401).txt("Invalid credentials")
await signedCookies.setCookie(res, "userId", user.id, {
maxAge: 86400,
httpOnly: true,
secure: true,
sameSite: "Lax",
})
res.redirect("/dashboard")
})
app.get("/logout", (req, res) => {
res.clearCookie("userId")
res.redirect("/")
})
app.startServer(app.getEnv("PORT", 5000))