GitHub GitHub

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))