vibe-express
v0.1.0
Published
Express, but the LLM is the router. A satirical Node web framework where every request is dispatched by a language model.
Maintainers
Readme
🪄 vibe-express
Express, but the LLM is the router.
vibe-express is a thin wrapper over Express and the Vercel AI SDK in which every HTTP request is dispatched by a language model. You don't write routes. You write tools — functions with English-language descriptions — and the model decides which one to call.
import { vibexpress, z } from 'vibe-express';
import { mydb } from './mydb';
const JWT_SECRET = 'hunter2';
const JWT_RULE = `
Authorization: Bearer <jwt>, HMAC-SHA256 signed with: ${JWT_SECRET}
Payload: { sub, role: "user" | "admin", exp }
Verify the signature. Reject if invalid or expired.
`.trim();
const app = vibexpress({
describe: 'a personal notes API with JWT bearer-token auth',
model: 'claude-opus-4-7',
});
app.tool('listNotes', 'returns the existing notes', {
auth: `${JWT_RULE}\nAllow any verified token.`,
}, async () => mydb.notes.list());
app.tool('createNote', 'creates a new note', {
inputSchema: z.object({ title: z.string().min(1), body: z.string() }),
auth: `${JWT_RULE}\nAllow any verified token.`,
}, async ({ title, body }) => mydb.notes.create({ title, body }));
app.listen(3000);Install
pnpm add vibe-express @ai-sdk/anthropic # or @ai-sdk/openaiSet your API key:
export ANTHROPIC_API_KEY=sk-ant-...How it works
For every incoming HTTP request, vibe-express:
- Serializes
{ method, path, query, body, headers, params }into a prompt. - Calls the model with your registered tools attached (via AI SDK
generateText). - The model picks one tool and invokes it with arguments inferred from the request.
- If the tool has an
authrule, a second model call judges whether the request is allowed. - Tool errors are routed through an
onErrorstrategy:'apologize'(default) — model writes a polite 500 message.'fabricate'— model invents a plausible successful response. Your users see no errors. Your database also sees no writes.'throw'— surfaces the error like a normal Express crash.
The URL path is a hint to the model, not a route table. GET /notes, GET /my/stuff, and GET /everything-i-wrote all dispatch to the same tool if the model thinks they mean the same thing.
API
vibexpress(config)
| option | type | default | what it does |
| ------------ | ------------------------------------------------ | -------------- | ------------ |
| describe | string | required | Plain-English summary of the API. Fed to the router model. |
| model | string \| LanguageModel | required | Either a model id ('claude-opus-4-7', 'gpt-5') or a fully-constructed AI SDK model. |
| onError | 'apologize' \| 'fabricate' \| 'throw' | 'apologize' | Error recovery strategy. |
| vibe | string | — | Tone hint, e.g. 'helpful but slightly tired'. |
| agentic | boolean | false | If true, the model may chain multiple tool calls per request. |
| maxSteps | number | 5 | Step cap when agentic is enabled. |
| bodyParser | boolean | true | Auto-register Express JSON + urlencoded parsers. |
app.tool(name, description, [options], handler)
app.tool('listNotes', 'returns the existing notes', async (args, ctx) => { /* ... */ });
app.tool(
'createNote',
'creates a new note',
{
inputSchema: z.object({ title: z.string(), body: z.string() }),
auth: 'allow if the Authorization header carries a valid bearer token',
},
async ({ title, body }, ctx) => { /* ... */ },
);| option | type | default | notes |
| ------------- | --------------------- | ----------- | ----- |
| inputSchema | ZodTypeAny | z.any() | Validated by the AI SDK before execute runs. |
| auth | string | — | A natural-language rule. If set, a separate LLM judge call must allow: true before the handler runs. Denials return 403 with the model's reason. |
Handlers receive (args, ctx) where ctx exposes { method, path, query, body, headers, params, req, res }. Returning a value sends it as JSON with 200. Throwing triggers the configured onError strategy.
app.fallback(handler)
Handles requests where the model chose to call no tool (e.g. it had no idea what you meant). Receives the same ctx.
app.use(...middleware)
Register normal Express middleware (CORS, logging, etc) before the AI router takes over. Useful for things you don't want the model second-guessing.
app.raw
The underlying express() instance — escape hatch for anything vibe-express doesn't expose.
Examples
See examples/notes for a runnable demo.
cd examples/notes
ANTHROPIC_API_KEY=sk-ant-... pnpm tsx index.tsRoadmap
Features tracked for future releases:
- Synthetic users (auto-generated seed data so empty apps look alive)
- Roleplay middleware (
roleplay('AWS Lambda')adds cold starts and 502s) - Post-hoc OpenAPI generator (docs are written by asking the model what your API "probably does")
- Streaming responses
License
$$\tiny \textit{this is satire.}$$
