DEV Community

Wess Cope
Wess Cope

Posted on

Verb: A Fast, Zero-Dependency HTTP Framework for Bun

There's a new HTTP framework in the Bun ecosystem that's worth checking out: Verb. It takes a refreshingly simple approach - leveraging Bun's built-in capabilities instead of reinventing the wheel.

What Makes Verb Interesting

Verb is built directly on Bun's native HTTP server with zero external dependencies. This means you get the raw performance of Bun without any abstraction overhead. Here's the simplest example:

import { createServer, json, text } from "verb";

const app = createServer({ port: 3000 });

app.get("/", () => text("Hello, Verb!"));
app.get("/api/users/:id", (req, params) => json({ id: params.id }));
Enter fullscreen mode Exit fullscreen mode

That's a working server. No configuration files, no boilerplate.

Key Features

Intelligent Route Caching

Verb uses an LRU cache for compiled route patterns. After the first request to a route, subsequent matches are nearly instant:

// These patterns are compiled once, then cached
app.get("/users/:id", handler);
app.get("/posts/:userId/comments/:commentId", handler);
app.get("/static/*", handler); // Including wildcards
Enter fullscreen mode Exit fullscreen mode

Built-in Streaming Support

Streaming is first-class in Verb, making it straightforward to handle real-time data, large exports, or progressive loading:

// Server-Sent Events
app.get("/events", () => {
  async function* eventGenerator() {
    let count = 0;
    while (count < 100) {
      yield {
        data: JSON.stringify({ count, timestamp: Date.now() }),
        event: "update"
      };
      await new Promise(r => setTimeout(r, 1000));
      count++;
    }
  }
  return streamSSE(eventGenerator());
});

// Stream large JSON datasets as JSONL
app.get("/api/export", () => {
  async function* dataGenerator() {
    for (let i = 0; i < 100000; i++) {
      yield { id: i, data: `Record ${i}` };
    }
  }
  return streamJSON(dataGenerator());
});
Enter fullscreen mode Exit fullscreen mode

Middleware System

The middleware API is straightforward and composable:

// Request timing
app.use(async (req, next) => {
  const start = Date.now();
  const response = await next();
  console.log(`${req.method} ${req.url} took ${Date.now() - start}ms`);
  return response;
});

// Built-in compression
app.use(compression());

// Simple auth check
app.use(async (req, next) => {
  if (req.url.startsWith("/api/") && !req.headers.get("authorization")) {
    return error("Unauthorized", 401);
  }
  return next();
});
Enter fullscreen mode Exit fullscreen mode

HTTP/2 Support

Full HTTP/2 support with server push capabilities:

const app = createServer({
  http2: true,
  tls: { cert: "./cert.pem", key: "./key.pem" }
});

// Push critical resources with the response
app.get("/", () => {
  return responseWithPush(
    htmlContent,
    [
      { path: "/styles/critical.css", type: "text/css" },
      { path: "/js/app.js", type: "application/javascript" }
    ]
  );
});
Enter fullscreen mode Exit fullscreen mode

Testing Utilities

Verb includes a mock server for testing without network overhead:

import { createMockServer } from "verb";
import { expect, test } from "bun:test";

test("user endpoint returns correct data", async () => {
  const app = createMockServer();
  app.get("/users/:id", (req, params) => json({ id: params.id }));

  const response = await app.request.get("/users/123");
  const data = await response.json();
  expect(data).toEqual({ id: "123" });
});
Enter fullscreen mode Exit fullscreen mode

Complete Example: REST API

Here's a full CRUD API to show how these pieces fit together:

import { createServer, json, parseBody, error } from "verb";

const app = createServer({ port: 3000 });

// In-memory store
const users = new Map();
let nextId = 1;

// Enable compression
app.use(compression());

// List users
app.get("/api/users", () => {
  return json(Array.from(users.values()));
});

// Get specific user
app.get("/api/users/:id", (req, params) => {
  const user = users.get(parseInt(params.id));
  if (!user) return error("User not found", 404);
  return json(user);
});

// Create user
app.post("/api/users", async (req) => {
  const body = await parseBody(req);
  const user = { id: nextId++, ...body };
  users.set(user.id, user);
  return json(user, 201);
});

// Update user
app.put("/api/users/:id", async (req, params) => {
  const id = parseInt(params.id);
  if (!users.has(id)) return error("User not found", 404);

  const body = await parseBody(req);
  const user = { id, ...body };
  users.set(id, user);
  return json(user);
});

// Delete user
app.delete("/api/users/:id", (req, params) => {
  const id = parseInt(params.id);
  if (!users.delete(id)) return error("User not found", 404);
  return new Response(null, { status: 204 });
});
Enter fullscreen mode Exit fullscreen mode

Static File Serving

Verb handles static files with caching, ETags, and compression:

// Serve files from ./public
app.get("/static/*", serveStatic({
  root: "./public",
  maxAge: 86400,      // 1 day cache
  immutable: true,    // Add immutable directive
  etag: true,         // Generate ETags
  extensions: [".html"] // Try .html if not found
}));

// Simpler syntax for basic use cases
app.get("/assets/*", staticFiles("./assets"));
Enter fullscreen mode Exit fullscreen mode

Performance Considerations

Since Verb is built directly on Bun's HTTP server:

  • No additional parsing or routing layers
  • Route caching eliminates regex compilation on hot paths
  • Native HTTP/2 and TLS support
  • Minimal memory allocations per request

The framework is particularly well-suited for:

  • High-throughput APIs
  • Real-time applications using SSE or WebSockets
  • Services that need to stream large amounts of data
  • Microservices where startup time and memory usage matter

Installation and Usage

# Install from GitHub
bun add github:wess/verb

# Create a simple server
echo 'import { createServer, text } from "verb";
const app = createServer({ port: 3000 });
app.get("/", () => text("Hello from Verb!"));' > server.ts

# Run it
bun server.ts
Enter fullscreen mode Exit fullscreen mode

When to Use Verb

Verb is a good fit if you're:

  • Already using Bun or planning to migrate
  • Building APIs or microservices
  • Need streaming capabilities
  • Want minimal dependencies
  • Prioritizing performance

It might not be the right choice if you:

  • Need to run on Node.js
  • Want a full-stack framework with templating, ORM, etc.
  • Prefer convention-over-configuration frameworks

Wrapping Up

Verb demonstrates what's possible when you build specifically for Bun's strengths. By avoiding unnecessary abstractions and leveraging native capabilities, it delivers both performance and a clean API.

The project is actively maintained and open to contributions. Check out the GitHub repository for more examples, documentation, and to get involved.

If you're exploring Bun for your next project, Verb is definitely worth a look. It's one of those tools that does one thing really well - serving HTTP requests fast.


Have you tried Verb or other Bun frameworks? What's been your experience? Share your thoughts in the comments.

Top comments (1)

Collapse
 
kris_chou_5f6deb607e8cb75 profile image
Kris Chou

Never used this before but seems like it's worth trying