Dulak

Deliberately boring.
Production-shaped by default.

A full-stack starter running entirely on Bun:Hono for HTTP, bun:sqlite for data,Inertia v3 for server-driven UI — with React, Svelte, and Vue templates. Auth, uploads, migrations, tests, Docker — wired end to end.

zsh
Select a template:
1. Default (React 19 + vanilla CSS)
2. Svelte 5 + Tailwind CSS v4
3. React 19 + Tailwind CSS v4
4. Vue 3 + Tailwind CSS v4
? Template number [4]:
↓ Downloading Dulak (Vue 3 + Tailwind CSS v4)...
✓ Dulak project created!
[migrate] applied 0001_users.sql …
Hono Inertia boilerplate → http://localhost:4000
BunHonoInertia v3bun:sqliteTypeBox
1server, three clients
62E2E tests
4templates
0CSS frameworks required

Everything wired, nothing invented

The guardrails a deployed app needs, from day one.

Auth, complete

Register, login, logout, forgot/reset password and Google OAuth — argon2id, DB-backed sessions, CSRF, rate-limited.

Inertia v3 + SSR

Full HTML on first load, SPA after. In-process SSR, 409 version negotiation, partial reloads, one-shot flash.

Resumable uploads

tus protocol v1: creation, resume, checksum, termination, expiration — proven by the avatar upload.

One server, three clients

React, Svelte 5, and Vue 3 templates with Tailwind CSS v4 — same auth, SSR, and test suite.

Migrations & SQL

Versioned SQL applied at startup in transactions. Zero-ORM bun:sqlite, prepared statements only.

Production-shaped ops

Batched logging, gzip, security headers, /health, graceful shutdown, multi-stage Docker, CI.

One server. Three clients.

Pick a frontend — the same Login page, in your framework.

vue-tailwind
<script setup lang="ts">
import { Head, Link, useForm } from "@inertiajs/vue3";
import AuthLayout from "../components/AuthLayout.vue";
import Field from "../components/Field.vue";
defineProps<{ googleEnabled?: boolean; notice?: string | null }>();
const form = useForm({ email: "", password: "" });
function submit() {
form.post("/login");
}
</script>
<template>
<Head><title>Login</title></Head>
<AuthLayout>
<h1>Welcome back</h1>
<form @submit.prevent="submit" novalidate>
<Field id="email" label="Email" :error="form.errors.email">
<input id="email" type="email" v-model="form.email"
@change="form.clearErrors('email')" />
</Field>
<button class="btn btn-primary btn-block" type="submit"
:disabled="form.processing">
{{ form.processing ? "Signing in…" : "Sign in" }}
</button>
</form>
</AuthLayout>
</template>
svelte-tailwind
<script lang="ts">
import { Link, useForm } from '@inertiajs/svelte'
import AuthLayout from '../components/AuthLayout.svelte'
import Field from '../components/Field.svelte'
let { googleEnabled = false, notice = null } = $props()
const form = useForm({ email: '', password: '' })
function submit(e: SubmitEvent) {
e.preventDefault()
form.post('/login')
}
</script>
<AuthLayout>
<h1>Welcome back</h1>
<form onsubmit={submit} novalidate>
<Field id="email" label="Email" error={form.errors.email}>
<input id="email" type="email" bind:value={form.email}
onchange={() => form.clearErrors('email')} />
</Field>
<button class="btn btn-primary btn-block" type="submit"
disabled={form.processing}>
{form.processing ? 'Signing in…' : 'Sign in'}
</button>
</form>
</AuthLayout>
react-tailwind
import { Head, Link, useForm } from "@inertiajs/react";
import AuthLayout from "../components/AuthLayout";
import Field from "../components/Field";
export default function Login() {
const form = useForm({ email: "", password: "" });
const submit = (e: React.FormEvent) => {
e.preventDefault();
form.post("/login");
};
return (
<AuthLayout>
<Head title="Login" />
<h1>Welcome back</h1>
<form onSubmit={submit} noValidate>
<Field id="email" label="Email" error={form.errors.email}>
<input
id="email" type="email" autoComplete="email"
value={form.data.email}
onChange={(e) => form.setData("email", e.target.value)}
/>
</Field>
<button className="btn btn-primary btn-block" type="submit"
disabled={form.processing}>
{form.processing ? "Signing in…" : "Sign in"}
</button>
</form>
</AuthLayout>
);
}

Ship your next app. Boringly.

One command. Auth, uploads, migrations, tests, Docker — already wired.

$ bun create dulak my-app