跳转到内容

快速开始

安装 Bun(>= 1.0.0):

Terminal window
curl -fsSL https://bun.sh/install | bash
Terminal window
mkdir my-ventostack-app
cd my-ventostack-app
bun init -y
Terminal window
bun add @ventostack/core

创建 src/main.ts

import { createApp, createRouter } from "@ventostack/core";
const router = createRouter();
router.get("/", async (ctx) => {
return ctx.json({ message: "Hello, VentoStack!" });
});
router.get("/users/:id", async (ctx) => {
const { id } = ctx.params;
return ctx.json({ id, name: "Alice" });
});
// 使用类型标记,ctx.params.id 自动推导为 number
router.get("/items/:id<int>", async (ctx) => {
return ctx.json({ id: ctx.params.id, type: typeof ctx.params.id });
});
const app = createApp({ port: 3000 });
app.use(router);
await app.listen();
console.log("Server running at http://localhost:3000");
Terminal window
bun --hot src/main.ts

访问 http://localhost:3000,你会看到:

{ "message": "Hello, VentoStack!" }
import { createApp, createRouter } from "@ventostack/core";
import type { Middleware } from "@ventostack/core";
// 日志中间件
const logger: Middleware = async (ctx, next) => {
const start = Date.now();
const response = await next();
console.log(`${ctx.method} ${ctx.path} - ${Date.now() - start}ms`);
return response;
};
// 认证中间件示例
const auth: Middleware = async (ctx, next) => {
const token = ctx.headers.get("authorization");
if (!token) {
return ctx.json({ error: "Unauthorized" }, 401);
}
return next();
};
const router = createRouter();
router.get("/protected", async (ctx) => {
return ctx.json({ data: "secret" });
}, auth);
const app = createApp({ port: 3000 });
app.use(logger);
app.use(router);
await app.listen();

首先安装额外依赖:

Terminal window
bun add @ventostack/database @ventostack/auth @ventostack/cache @ventostack/observability
import { createApp, createRouter, requestLogger, errorHandler } from "@ventostack/core";
import { createDatabase, defineModel, column } from "@ventostack/database";
import { createJWT, createRBAC, createPasswordHasher } from "@ventostack/auth";
import { createCache, createMemoryAdapter } from "@ventostack/cache";
import { createLogger } from "@ventostack/observability";
// 定义数据模型
const UserModel = defineModel("users", {
id: column.bigint({ primary: true, autoIncrement: true }),
email: column.varchar({ length: 255, unique: true }),
password: column.varchar({ length: 255 }),
name: column.varchar({ length: 255 }),
role: column.varchar({ length: 50 }),
});
// 初始化依赖
const logger = createLogger({ level: "info" });
// 传入 url 即可自动使用 Bun.sql,无需手动配置 executor
const db = createDatabase({ url: "sqlite://data/abc.db" });
const jwt = createJWT({
secret: process.env.JWT_SECRET || "please_change_me_please_change_me",
defaultOptions: { expiresIn: 7 * 24 * 60 * 60 }, // 7 天
});
const passwordHasher = createPasswordHasher();
const cache = createCache(createMemoryAdapter());
const rbac = createRBAC();
// 定义权限角色
rbac.addRole({
name: "admin",
permissions: [
{ resource: "users", action: "read" },
{ resource: "users", action: "write" },
{ resource: "users", action: "delete" },
],
});
rbac.addRole({
name: "user",
permissions: [{ resource: "users", action: "read" }],
});
const router = createRouter();
// 注册路由
router.post("/auth/register", async (ctx) => {
const { email, password, name } = await ctx.request.json() as {
email: string;
password: string;
name: string;
};
const existing = await db.query(UserModel).where("email", "=", email).get();
if (existing) {
return ctx.json({ error: "Email already registered" }, 409);
}
const passwordHash = await passwordHasher.hash(password);
const user = await db
.query(UserModel)
.insert({ email, password: passwordHash, name, role: "user" }, { returning: true });
return ctx.json({ id: user?.id, email, name }, 201);
});
// 登录路由
router.post("/auth/login", async (ctx) => {
const { email, password } = await ctx.request.json() as { email: string; password: string };
const user = await db.query(UserModel).where("email", "=", email).get();
if (!user) {
return ctx.json({ error: "Invalid credentials" }, 401);
}
const valid = await passwordHasher.verify(password, user.password);
if (!valid) {
return ctx.json({ error: "Invalid credentials" }, 401);
}
const token = await jwt.sign({ sub: String(user.id), role: user.role as string });
return ctx.json({ token });
});
// 受保护的路由
router.get("/users", async (ctx) => {
const token = ctx.headers.get("authorization")?.replace("Bearer ", "");
if (!token) {
return ctx.json({ error: "Unauthorized" }, 401);
}
const payload = await jwt.verify(token);
if (!rbac.can([payload.role as string], "users", "read")) {
return ctx.json({ error: "Forbidden" }, 403);
}
const users = await cache.remember("users:all", 300, async () => {
return db.query(UserModel).select("id", "name", "email").list();
});
return ctx.json(users);
});
const app = createApp({ port: 3000 });
app.use(errorHandler());
app.use(requestLogger());
app.use(router);
// 启动前自动建表(示例用;生产环境应使用迁移工具)
await db.raw(`
CREATE TABLE IF NOT EXISTS users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
email TEXT UNIQUE,
password TEXT,
name TEXT,
role TEXT
)
`);
app.lifecycle.onAfterStart(() => {
logger.info("Server started", { port: 3000 });
});
await app.listen();