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)
    โ”‚   +root.css     (global css)
    โ”‚   +error.ts     (error component)
    โ”‚   +root.ts      (error css)
    โ”‚
    โ”œโ”€โ”€โ”€components
    โ”‚       Button.ts      (pure HTML component)
    โ”‚
    โ””โ”€โ”€โ”€routes
        โ”‚   +view.ts      (server component)
        โ”‚   +view.js      (client component)
        โ”‚   +view.css     (client css)
        โ”‚
        โ””โ”€โ”€โ”€api
            โ””โ”€โ”€โ”€v1/user
                    +users.ts       (simple users array for demonstartion)
                    +get.ts         (GET handler)
                    +post.ts        (POST handler)
                    โ”‚
                    โ”‚ ...+<method>.ts  (supported http method 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, init: () => { app.listen({ port: 8080 }); console.log('Server listening on 8080') } },
    props: {
        payload: async (ctx: Context) => {
            return { user: "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>
`}

If you need to style root component, create: web/+root.css

body {
    background: lightblue;
}

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
}

Also you can specify error page when status !== 200

web/+error.ts

import { HTSXProps } from "../mod.ts";

export default (props: HTSXProps) => { return /*html*/`
    <h1>ERROR: ${props.ctx?.response.status}</h1>
`}

web/+error.css

body {
    background: red;
    color: white;
}

Now letโ€™s create REST API endpoint

Create any supported +<method>.ts in any directory inside routes 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, }
}

Supported methods:

  • get
  • head
  • patch
  • options
  • delete
  • post
  • put

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