This is an experiment in building a CRUD-type web UI with server-side JSX components and HTMX for a SPA-like experience. The UI presents a list of records as cards or table rows and provides active search, filters, pagination and a modal dialog for editing entries.
Route callbacks directly render jsx:
router.get("/page", (ctx) => {
await ctx.render(
<SomeComponent />,
);
});Partials/components are async jsx functions:
- they can do async work, load data from the database or an external API
- easy to avoid prop drilling from views/controllers
- looser coupling makes it easier to maintain
async function Badge() {
const count = await db.likes.count();
return <div>Likes: {count}</div>;
}
// async jsx components can be used as any other and will be resolved concurrently
<Page>
<Badge />
</Page>With HTMX it's possible to build interactive UIs without writing frontend
javascript. For example, to search as the user enters text we add hx-
attributes to the search input.
<input
type="search"
name="search"
placeholder="Search..."
hx-get="/profiles/listonly"
hx-trigger="keyup changed delay:200ms, search"
hx-target="#profile-list"
hx-include="[data-filter]"
hx-indicator="#loading-indicator"
data-filter
/>Yup is a great library for schema validation. It provides an easy way to
generate native HTML5 input validation attributes from the type schema via
schema.describe(). We can use the very same schema object to both validate
HTTP requests and have real-time form validation.
Yup.object({
email: Yup.string().email().required(),
});
// validate HTTP request on the backend
try {
obj.validateSync(form);
await ctx.store.create(form);
} catch (err) {
if (err instanceof Yup.ValidationError) {
// ...
}
}Generated HTML5 input attributes for inline validation
<input type="email" required />We don't need to use div-tags for everything. The UI components in this project render custom tags for meaningful and readable html. Custom tags are perfectly valid and supported by all browsers.
For example, <div class="profile-card"></div> becomes
<profile-card></profile-card>.
Like styled-components in the react world, custom HTML tags give us a unique component name to target in our CSS, but without the need for any additional pre-processing or JS libraries.
profile-card {
display: block;
white-space: nowrap;
}CSS classes are used for reusable mixins/utils, eg 'card with shadow',
<profile-card class="card-w-shadow"></profile-card>or conditional styles
<button class={cx("save-button", success && "button-success")} />;Simple & easy to construct dynamic SQL commands, no ORM to learn. Interpolated values are converted to prepared statements.
const query = sql`SELECT * FROM table WHERE col=${value}`;-
Deno - javascript runtime http://deno.land
-
Oak - middleware framework for Deno’s native HTTP server https://oakserver.github.io/oak/
-
HTMX - tools for HTML interfaces http://htmx.org
-
Yup - value parsing and validation https://github.com/jquense/yup
-
Iconoir - icon library https://iconoir.com
-
Faker - fake data for testing and development https://fakerjs.dev/
-
ssr_jsx - JSX library for server-side rendering https://github.com/dsego/ssr_jsx
-
sql_tag - tagged template literals for SQL statements https://github.com/dsego/sql_tag
