mintiljs
v0.1.5
Published
Minimal SSR web framework for Bun — React pages rendered on the server, zero client JS unless you ask for it
Maintainers
Readme
MintilJS
Minimal SSR web framework for Bun — React pages rendered on the server, zero client JavaScript unless you ask for it.
MintilJS is a full-stack framework built on Bun, Hono, and React 19. Drop files in pages/ and they become SSR routes. Add api/ files for JSON endpoints. Throw in islands/ for partial hydration. All with zero configuration.
bun create mintil my-app
cd my-app
bun install
bun run mintil devFeatures
- File-based routing —
pages/→ SSR routes,api/→ JSON endpoints - SSR by default — zero JS in the browser unless you opt in
useClient— per-page hydration bundles viaBun.build- Islands — independent interactive components, no full-page re-render
getServerSideProps— per-request data fetching- Hierarchical middleware — global, API-wide, directory-scoped
- Layouts — root layout (
components/layouts/base.tsx) + per-directory overrides - Tailwind v4 — automatic CSS processing via PostCSS
- Streaming SSR —
renderToReadableStreamfor pages without client bundles - Auth module —
mintiljs/auth(JWT, sessions, middleware) - i18n module —
mintiljs/i18n(auto-detected messages, placeholders, CSR fetcher) - Plugin system — extend the app at startup
- Auto-reload — file watcher in development
Install
bun create mintil my-app # showcase (default)
bun create mintil my-app -t minimal # minimal template
bun create mintil my-app -t blank # blank template
bun create mintil my-app -i # + install deps
cd my-app
bun run mintil devOr add to an existing project:
bun add mintiljsQuick Start
my-app/
pages/ → SSR routes
index.tsx /
blog/
(:slug).tsx /blog/:slug
layout.tsx layout scoped to /blog/*
api/ → JSON endpoints
hello.ts /api/hello
middleware.ts applies to all /api/*
islands/ → auto-registered hydratable components
Counter.tsx
components/
layouts/
base.tsx root layout
i18n/ → auto-detected messages
messages/
en/0.json
pt-BR.json
mintil.config.ts → project config (optional)
middleware.ts → root middleware (every request)Create a page
// pages/index.tsx → /
export default function Home() {
return <h1 className="text-3xl font-bold">Home</h1>;
}Add an API endpoint
// api/hello.ts → GET /api/hello
import type { ApiHandler } from "mintiljs";
export default (c) => c.json({ message: "Hello" });Make a page interactive
// pages/counter.tsx
import React from "react";
export const useClient = true;
export default function Counter() {
const [count, setCount] = React.useState(0);
return <button onClick={() => setCount(c => c + 1)}>{count}</button>;
}Fetch data on every request
import type { GetServerSideProps } from "mintiljs";
export const getServerSideProps: GetServerSideProps = async ({ params, searchParams }) => {
const data = await db.find(params.id);
return { props: { data } };
};
export default function Page({ data }: { data: any }) {
return <div>{data.name}</div>;
}Configuration
Export a MintilConfig default from mintil.config.ts:
import type { MintilConfig } from "mintiljs";
export default {
port: 3456,
host: false, // true = bind to 0.0.0.0
mode: "development", // "development" | "production"
assetsPath: "/assets", // prefix for public/ files; "/" to serve at root
plugins: [],
} satisfies MintilConfig;CLI
| Command | Description |
|---|---|
| mintil init <name> | Scaffold a new project (-t template, -i install deps) |
| mintil dev | Dev mode with auto-reload |
| mintil start | Production mode |
| mintil g page <n> | Scaffold a page |
| mintil g api <n> | Scaffold an API route |
| mintil g island <n> | Scaffold an island |
Modules
Auth (mintiljs/auth)
// api/login.ts
import { createAuthMiddleware, InMemorySessionStore } from "mintiljs/auth";
const JWT_SECRET = process.env.JWT_SECRET || "dev-secret";
const store = new InMemorySessionStore();
export const auth = createAuthMiddleware({
jwt: { secret: JWT_SECRET, expiresIn: "1h" },
session: { store, maxAge: 86400, cookieName: "session", cookiePath: "/" },
});
// POST /api/login — sign JWT and return it
import type { ApiMethodHandler } from "mintiljs";
export const POST: ApiMethodHandler = async (request, response) => {
const { username, password } = await request.json();
if (username !== "admin" || password !== "123456") {
return response.json({ error: "Invalid credentials" }, 401);
}
const token = await auth.signJWT({ sub: username, userId: username, role: "admin" });
return response.json({ token });
};// api/admin/middleware.ts — protect all /api/admin/* routes
import { auth } from "../login";
export default auth.requireAuth;// api/me.ts — use the verified token
import type { ApiMethodHandler } from "mintiljs";
import { auth } from "./login";
export const GET: ApiMethodHandler = async (request, response) => {
const token = request.header("Authorization")?.slice(7);
const payload = token ? await auth.verifyJWT(token) : null;
return payload
? response.json({ user: { id: payload.userId, name: payload.sub } })
: response.json({ error: "Unauthorized" }, 401);
};i18n (mintiljs/i18n)
i18n/messages/en/0.json → { "greeting": "Hello" }
i18n/messages/en/1.json → { "welcome": "Welcome to {site}!" }
i18n/messages/pt-BR.json → { "greeting": "Olá" }import { getMessages, t, format } from "mintiljs/i18n";
const msgs = await getMessages("en");
t(msgs, "greeting") // "Hello"
t(msgs, "welcome", { site: "MintilJS" }) // "Welcome to MintilJS!"
format("Hello {name}!", { name: "John" }) // "Hello John!"The framework auto-registers /_mintil/i18n/:locale for CSR usage.
Plugin System
import type { MintilPlugin } from "mintiljs";
const health: MintilPlugin = {
name: "health",
setup(app) { app.get("/health", (c) => c.json({ ok: true })); },
};
export default { plugins: [health] } satisfies MintilConfig;API Reference
Full TypeDoc-generated documentation at docs/api/:
bun run docsLicense
MIT
