Skip to main content
Deno 2 is finally here 🎉️
Learn more


HTSX - Hyper Text (on) Server eXtended


HTSX - Minimal Deno SSR framework on vanilla HTML

🔗 Useful Links

💾 Installation

import { HTSX } from 'https://deno.land/x/htsx/mod.ts'
import { Application } from 'https://deno.land/x/oak/mod.ts'
import { Router } from 'https://deno.land/x/oak/router.ts'

📄 Quickstart

HTSX - basically, template engine with extra functions, like:

  • Automatic handler for HTTP requests
  • Filesystem-based routing
  • Minifier on endpoints
  • Pure HTML components
  • REST API constructor

To start, replicate this filesystem

Root
│   main.ts      (main file)
│
└───web
    │   +root.ts      (<App/> like component)
    │
    ├───components
    │       Button.ts      (pure HTML component)
    │
    └───routes
        │   +view.ts      (server component)
        │   +view.js      (client component)
        │   +view.css     (client css)
        │
        └───api
            └───v1/user
                    +get.ts      (GET handler)
                    +post.ts     (POST handler)

For starters, let’s create basic Oak server and pass app and router in HTSX class

main.ts

import { HTSX } from 'https://deno.land/x/htsx/mod.ts'
import { Application } from 'https://deno.land/x/oak/mod.ts'
import { Router } from 'https://deno.land/x/oak/router.ts'

const app = new Application()
const router = new Router()

await new HTSX({ 
    root: './web', 
    server: { app, router, () => { app.listen({ port: 8080 })} },
    props: {
        payload: async (_ctx: Context) => {
            return { name: 'John' };
        }
    }
})

Then you need to create +root.ts file on root folder that you provide to HTSX class

It’s like </App> component in React

web/+root.ts

import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";

export default (props: HTSXProps) => { return /*html*/`
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        ${props.body} <!-- required -->
    </body>
    </html>
`}

Basic config is over, now you can create endpoints for your server in routes folder, for example - view endpoint on / and Button component

web/components/Button.ts

import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";

export default (props: HTSXProps) => { return /*html*/`
    <button onclick="${props.onclick}">
        ${props.name}
    </button>

    <style>
        button {
            background: violet
        }
    </style>
`}

web/routes/+view.ts

import Button from "../components/Button.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";

// In view components you can export HEAD if you want to specify title, preload something or put script from CDN exclusively on this endpoint
export const HEAD = /*html*/`
    <title>Hello World</title>
    <link rel="preconnect" href="https://fonts.googleapis.com">
    <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
    <link href="https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300..900;1,300..900&display=swap" rel="stylesheet">
    <script src="https://cdn.jsdelivr.net/npm/@tma.js/sdk@1.3.0"></script>
    <style>
        * {
            font-family: "Rubik", sans-serif;
            font-optical-sizing: auto;
            font-weight: 400;
            font-style: normal;
        }
    </style>
`
export default (props: HTSXProps) => { return /*html*/`
    <main>
        <h1>hello, ${props.payload.name}! press da button!</h1>
        ${Button({ name: 'PRESS ME!!!!', onclick: `click_handler()` })}
    </main>
`}

web/routes/+view.js

// This is vanilla browser JS
function click_handler() {
    alert('Hello World!')
}

web/routes/+view.css

h1 {
    color: red
}

Now let’s create REST API endpoint

Create +get.ts or +post.ts in any directory inside routest directory, for example

/web/routes/api/v1/user/users.ts

export const users = [
    { id: 1, name: 'John Doe', age: 28 },
    { id: 2, name: 'Foo Bar', age: 99 },
    { id: 3, name: 'I love Deno', age: 21 },
    { id: 4, name: 'I love Bun', age: 1 }
]

/web/routes/api/v1/user/+get.ts

import { Context } from "https://deno.land/x/oak@v13.0.0/mod.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";
import { users } from './users.ts'

export default (ctx: Context, props: HTSXProps) => {
    const id = Number(ctx.request.url.searchParams.get('id'))
    const user = users.find(user => user.id === id)

    if (user === undefined)
        return { error: `no user with ${id} id` }; 
    else
        return user
}

/web/routes/api/v1/user/+post.ts

import { Context } from "https://deno.land/x/oak@v13.0.0/mod.ts";
import { HTSXProps } from "https://deno.land/x/htsx/mod.ts";

export default (ctx: Context, props: HTSXProps) => {
    return { id: 1, name: 'John Doe', age: 28, }
}

So here is example how to quickly create basic API and HTML site with cool DX

Since it’s pure HTML, you can safely plug in any library from React to HTMX

🤔 Use case

This “framework” was created for convenient work with Telegram mini applications, for the lack of need to use something like Fresh for a basic one-page interface the idea to create this library was born.

Your use case may be different, but it will still be a handy tool for achieving simple goals

📜 License

MIT