⚡ Zero-dependency TypeScript utilities for modern Bun apps
Type-safe APIs, Redis queues, pub/sub, i18n, caching & auth with tree-shakeable imports
| Module | Description | Import |
|---|---|---|
| 🚀 API Framework | Type-safe REST API with OpenAPI & Bun-native routing | semola/api |
| 📬 Queue | Redis-backed job queue with timeouts & concurrency | semola/queue |
| 📡 PubSub | Type-safe Redis pub/sub for real-time messaging | semola/pubsub |
| 🔐 Policy | Policy-based authorization with type-safe guards | semola/policy |
| 🌍 i18n | Compile-time validated internationalization | semola/i18n |
| 💾 Cache | Redis cache wrapper with TTL & automatic serialization | semola/cache |
| ⏰ Cron | In-memory and OS cron scheduler for periodic task execution | semola/cron |
| 🔁 Workflow | Durable resumable workflows with retries and hooks | semola/workflow |
| Result-based error handling without try/catch | semola/errors |
|
| 📃 Logging | A simple logging utility | semola/logging |
| ⌨️ Prompts | Interactive zero-dependency CLI prompts | semola/prompts |
| 🖥️ CLI | Non-interactive CLI builder with schema validation | semola/cli |
| 🗄️ ORM | Type-safe data layer with query APIs | semola/orm |
| 💡 Extra | A collection of tiny utilities | semola/extra |
# With Bun (recommended)
bun add semola
# With npm
npm install semolaimport { Api } from "semola/api";
import { z } from "zod";
const api = new Api();
api.defineRoute({
path: "/hello/:name",
method: "GET",
request: {
params: z.object({ name: z.string() }),
},
response: {
200: z.object({ message: z.string() }),
},
handler: async (ctx) => {
return ctx.json(200, { message: `Hello, ${ctx.params.name}!` });
},
});
api.listen(3000);
console.log("Server running on http://localhost:3000");import { mightThrow } from "semola/errors";
const [error, data] = await mightThrow(fetch("https://api.example.com"));
if (error) {
console.error("Request failed:", error);
return;
}
console.log("Success:", data);mightThrow and mightThrowSync default their error type to Error. If a promise or function can reject or throw non-Error values, pass a custom error generic.
const [customError] = await mightThrow<never, { code: string }>(
Promise.reject({ code: "RATE_LIMITED" }),
);import { Queue } from "semola/queue";
const queue = new Queue({
name: "emails",
redis: redisClient,
handler: async (data) => {
await sendEmail(data);
},
});
await queue.enqueue({ to: "user@example.com", subject: "Hello" });import { PubSub } from "semola/pubsub";
const pubsub = new PubSub({
subscriber: redisClient,
publisher: redisClient,
channel: "notifications",
});
// Subscribe to messages
const unsubscribe = await pubsub.subscribe((message) => {
console.log("Received:", message);
});
// Publish a message
await pubsub.publish({ userId: 123, text: "New alert!" });
await unsubscribe();import { Cache } from "semola/cache";
const cache = new Cache({
redis: redisClient,
ttl: 3600000, // 1 hour in milliseconds
});
// Store data
await cache.set("user:123", { name: "John", age: 30 });
// Retrieve data
const user = await cache.get("user:123");
console.log(user);import { Cron } from "semola/cron";
const cleanup = new Cron({
name: "daily-cleanup",
schedule: "@daily",
handler: async () => {
await deleteOldLogs();
await archiveInactiveUsers();
}
});
cleanup.run();import { createOrm, defineTable, json, string, uuid } from "semola/orm";
const users = defineTable("users", {
id: uuid("id").primaryKey().notNull(),
name: string("name").notNull(),
email: string("email").unique().notNull(),
metadata: json<{ plan: string }>("metadata"),
});
const db = createOrm({
adapter: "sqlite",
url: ":memory:",
tables: { users },
});
const rows = await db.users.findMany({
where: { name: { contains: "John" } },
take: 10,
});
const user = await db.users.create({
data: {
id: "1",
name: "John Doe",
email: "john@example.com",
},
});
console.log(rows, user);import { Policy, eq, has } from "semola/policy";
type User = { id: number; role: string; permissions: string[] };
const policy = new Policy<User>();
// Allow admins full access
policy.allow({
action: ["create", "update", "delete"],
conditions: { role: eq("admin") },
reason: "Admins have full access",
});
// Allow users with a specific permission
policy.allow({
action: "read",
conditions: { permissions: has("posts:read") },
});
// Check if user can perform an action
const user: User = { id: 1, role: "admin", permissions: [] };
const result = policy.can("update", user);
console.log(result.allowed); // trueimport { I18n } from "semola/i18n";
const i18n = new I18n({
defaultLocale: "en",
locales: {
en: { greeting: "Hello, {name:string}!" },
es: { greeting: "¡Hola, {name:string}!" },
},
});
console.log(i18n.translate("greeting", { name: "World" }));import { ConsoleProvider, Logger } from "semola/logging";
const logger = new Logger("database", [new ConsoleProvider()]);
logger.info("Hello!");import { createRetry } from "semola/extra";
type FriendRequest = {
from: string;
to: string;
};
async function sendFriendRequest(req: FriendRequest) {
console.log(`sending from ${req.from} to ${req.to}`);
await sender(req);
}
const callable = createRetry(
async () => {
await sendFriendRequest({ from: "user1@gmail.com", to: "user2@gmail.com" });
},
{
maxRetries: 3,
onFailedAttempt: () => {
console.log(`Resending the request`);
},
onError: ({ error }) => {
console.error(error.message);
},
},
);
await callable();# Install core package
bun add semola
# Optional: Install validation library (Zod, Valibot, ArkType)
bun add zodSemola (pronounced "seh-MOH-lah") is the batteries-included toolkit TypeScript developers have been waiting for.
Stop piecing together half-baked solutions from npm. Stop wrestling with type definitions that lie to you. Semola gives you everything you need to build production-ready Bun applications with confidence: type-safe APIs, background job queues, real-time messaging, caching, authorization, and error handling. All working together seamlessly out of the box.
| Semola | Express | Fastify | Hono | Elysia | |
|---|---|---|---|---|---|
| Bun Native | ✅ | ❌ | ✅ | ✅ | |
| Zero Dependencies | ✅ | ❌ | ❌ | ✅ | ❌ |
| Type-Safe Routes | ✅ | ❌ | ✅ | ✅ | |
| Auto OpenAPI | ✅ | ❌ | |||
| Tree-Shakeable | ✅ | ❌ | ❌ | ✅ | ✅ |
| Standard Schema | ✅ | ❌ | ❌ | ✅ |
Semola API is the fastest API framework for Bun.
| Framework | Avg Req/Sec | Latency Avg (ms) | vs Semola |
|---|---|---|---|
| Semola | 40,050 | 1.88 | baseline |
| Elysia | 37,185 | 2.13 | 1.1x slower |
| Hono | 34,611 | 2.31 | 1.2x slower |
| Fastify | 26,330 | 3.70 | 1.5x slower |
| Express | 20,031 | 5.02 | 2x slower |
| NestJS | 16,118 | 6.21 | 2.5x slower |
Higher is better for req/sec, lower is better for latency.
- 🎯 Bun-first: Engineered specifically for Bun's performance. No Node.js baggage.
- 🧩 Modular by design: Import only what you need. Your bundle stays lean.
- 🔒 Type safety that actually works: From request validation to response serialization, TypeScript catches errors before they hit production.
- 📄 Documentation writes itself: Auto-generated OpenAPI specs from your code. No more stale docs.
- 🚫 Error handling reimagined: No more try-catch spaghetti. Clean result tuples that compose beautifully.
- ⚡ Schema validation freedom: Use Zod, Valibot, ArkType, or any Standard Schema library. Your choice.
- 🔋 Batteries included: Everything you need in one cohesive toolkit. No 50 dependencies to audit.
- API Framework - Type-safe REST API framework with OpenAPI
- Queue - Redis-backed job queue with timeouts & concurrency
- PubSub - Type-safe Redis pub/sub
- Cron - In-memory and OS cron scheduler for periodic task execution
- Workflow - Durable and resumable workflows with retries and hooks
- Policy - Policy-based authorization
- i18n - Type-safe internationalization
- Cache - Redis cache wrapper with TTL
- Errors - Result-based error handling
- Logging - Logging utility
- Prompts - Interactive CLI prompts
- CLI - Non-interactive CLI builder
- ORM - Type-safe data layer with SQLite support
- Extra - A collection of tiny utilities
# Install dependencies
bun install
# Run tests
bun test
# Build package
bun run build
# Lint & typecheck
bun checkThis package uses GitHub Actions for automated publishing. To release:
- Bump version:
bun pm version <major|minor|patch> - Create a GitHub release with a new tag (e.g.,
v0.4.0) - The GitHub Action automatically publishes to npm with provenance