返回 Skill 列表
extension
分类: 开发与工程无需 API Key

oak

受Koa启发的Deno中间件框架。涵盖了路由、中间件、上下文、WebSocket和静态文件等功能。用于类似Koa的Deno API。使用场景:用户提到“Oak”、“oak”、“Deno中间件”、“Deno中的Koa”、询问关于“Deno Web框架”、“Deno API服务器”、“基于上下文的Deno路由”、“Deno的Koa替代品”。不适用场景:Node.js应用-请改用`express`或`nestjs`,Fresh框架-请改用`fresh`技能,边缘运行时-请改用`hono`,Deno中的React SSR-请改用`fresh`

person作者: jakexiaohubgithub

Oak Core Knowledge

Deep Knowledge: Use mcp__documentation__fetch_docs with technology: oak for comprehensive documentation.

Full Reference: See advanced.md for WebSocket patterns, Error Handling, Validation with Zod, and Production Readiness (health checks, graceful shutdown, logging).

Basic Setup

import { Application, Router } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const app = new Application();
const router = new Router();

router.get("/", (ctx) => {
  ctx.response.body = "Hello, World!";
});

app.use(router.routes());
app.use(router.allowedMethods());

console.log("Server running on http://localhost:8080");
await app.listen({ port: 8080 });

Configuration

// deps.ts - Centralized dependencies
export {
  Application,
  Router,
  Context,
  Status,
  isHttpError,
} from "https://deno.land/x/oak@v12.6.1/mod.ts";
export type {
  Middleware,
  RouterContext,
  State,
} from "https://deno.land/x/oak@v12.6.1/mod.ts";

// main.ts
import { Application, Router } from "./deps.ts";

Routing

Basic Routes

import { Router } from "./deps.ts";

const router = new Router();

router
  .get("/users", listUsers)
  .get("/users/:id", getUser)
  .post("/users", createUser)
  .put("/users/:id", updateUser)
  .delete("/users/:id", deleteUser);

// Handler functions
function listUsers(ctx: RouterContext<"/users">) {
  ctx.response.body = { users: [] };
}

function getUser(ctx: RouterContext<"/users/:id">) {
  const { id } = ctx.params;
  ctx.response.body = { id };
}

Path Parameters

const router = new Router();

// Single parameter
router.get("/users/:id", (ctx) => {
  const id = ctx.params.id;
  ctx.response.body = { userId: id };
});

// Multiple parameters
router.get("/users/:userId/posts/:postId", (ctx) => {
  const { userId, postId } = ctx.params;
  ctx.response.body = { userId, postId };
});

// Optional parameter
router.get("/files/:path*", (ctx) => {
  const path = ctx.params.path;
  ctx.response.body = { path };
});

Route Prefixes

const apiRouter = new Router({ prefix: "/api" });

apiRouter
  .get("/users", listUsers)    // GET /api/users
  .post("/users", createUser); // POST /api/users

const v1Router = new Router({ prefix: "/api/v1" });
const v2Router = new Router({ prefix: "/api/v2" });

app.use(v1Router.routes());
app.use(v2Router.routes());

Context

Request Data

router.post("/users", async (ctx) => {
  // Path params
  const id = ctx.params.id;

  // Query params
  const page = ctx.request.url.searchParams.get("page") || "1";

  // Headers
  const auth = ctx.request.headers.get("Authorization");

  // Body
  const body = ctx.request.body;

  if (body.type() === "json") {
    const data = await body.json();
    console.log(data);
  }

  if (body.type() === "form") {
    const form = await body.form();
    const name = form.get("name");
  }

  ctx.response.body = { success: true };
});

Response

router.get("/users/:id", (ctx) => {
  // JSON response
  ctx.response.body = { id: ctx.params.id, name: "Alice" };
  ctx.response.type = "application/json";

  // Status code
  ctx.response.status = 200;

  // Headers
  ctx.response.headers.set("X-Custom-Header", "value");
});

// Redirect
router.get("/old-path", (ctx) => {
  ctx.response.redirect("/new-path");
});

State

interface AppState {
  user?: { id: string; email: string };
  requestId: string;
}

const app = new Application<AppState>();

// Set state in middleware
app.use(async (ctx, next) => {
  ctx.state.requestId = crypto.randomUUID();
  await next();
});

// Access state in handler
router.get("/me", (ctx: RouterContext<"/me", Record<string, string>, AppState>) => {
  const user = ctx.state.user;
  if (!user) {
    ctx.response.status = 401;
    return;
  }
  ctx.response.body = user;
});

Middleware

Application Middleware

import { Application, Status, isHttpError } from "./deps.ts";

const app = new Application();

// Logger middleware
app.use(async (ctx, next) => {
  const start = Date.now();
  await next();
  const ms = Date.now() - start;
  console.log(`${ctx.request.method} ${ctx.request.url.pathname} - ${ms}ms`);
});

// Error handler middleware
app.use(async (ctx, next) => {
  try {
    await next();
  } catch (err) {
    if (isHttpError(err)) {
      ctx.response.status = err.status;
      ctx.response.body = { error: err.message };
    } else {
      console.error(err);
      ctx.response.status = Status.InternalServerError;
      ctx.response.body = { error: "Internal server error" };
    }
  }
});

Authentication Middleware

import { Middleware, Status } from "./deps.ts";

interface AuthState {
  user: { id: string; email: string; role: string };
}

const authMiddleware: Middleware<AuthState> = async (ctx, next) => {
  const authHeader = ctx.request.headers.get("Authorization");

  if (!authHeader?.startsWith("Bearer ")) {
    ctx.response.status = Status.Unauthorized;
    ctx.response.body = { error: "Missing or invalid token" };
    return;
  }

  const token = authHeader.slice(7);

  try {
    const user = await validateToken(token);
    ctx.state.user = user;
    await next();
  } catch {
    ctx.response.status = Status.Unauthorized;
    ctx.response.body = { error: "Invalid token" };
  }
};

// Apply to router
const protectedRouter = new Router<Record<string, string>, AuthState>();
protectedRouter.use(authMiddleware);
protectedRouter.get("/me", (ctx) => {
  ctx.response.body = ctx.state.user;
});

Role-Based Access

function requireRole(...roles: string[]): Middleware<AuthState> {
  return async (ctx, next) => {
    const user = ctx.state.user;

    if (!user) {
      ctx.response.status = Status.Unauthorized;
      ctx.response.body = { error: "Not authenticated" };
      return;
    }

    if (!roles.includes(user.role)) {
      ctx.response.status = Status.Forbidden;
      ctx.response.body = { error: "Insufficient permissions" };
      return;
    }

    await next();
  };
}

// Usage
const adminRouter = new Router({ prefix: "/admin" });
adminRouter.use(authMiddleware);
adminRouter.use(requireRole("admin"));
adminRouter.get("/users", listAllUsers);

CORS

import { oakCors } from "https://deno.land/x/cors@v1.2.2/mod.ts";

const app = new Application();

// Allow all origins
app.use(oakCors());

// Custom configuration
app.use(oakCors({
  origin: ["https://example.com", "https://app.example.com"],
  methods: ["GET", "POST", "PUT", "DELETE"],
  allowedHeaders: ["Content-Type", "Authorization"],
  credentials: true,
  maxAge: 86400,
}));

Static Files

import { Application, send } from "https://deno.land/x/oak@v12.6.1/mod.ts";

const app = new Application();

// Serve static files
app.use(async (ctx, next) => {
  const path = ctx.request.url.pathname;

  if (path.startsWith("/static")) {
    await send(ctx, path, {
      root: `${Deno.cwd()}/public`,
      index: "index.html",
    });
    return;
  }

  await next();
});

When NOT to Use This Skill

  • Node.js Projects: Use Express, Fastify, or NestJS for Node.js-based applications
  • Islands Architecture: Use Fresh for server-rendered Deno apps with client islands
  • Edge Runtimes: Use Hono for Cloudflare Workers or Vercel Edge
  • Enterprise DI: Use NestJS if you need dependency injection and decorators
  • Static Site Generation: Use Fresh or other SSG tools
  • WebSocket-Heavy Apps: Use dedicated WebSocket skill for complex real-time features

Anti-Patterns

| Anti-Pattern | Why It's Bad | Correct Approach | |--------------|--------------|------------------| | Not calling await next() in middleware | Request hangs indefinitely | Always call await next() unless sending response | | Using console.log() for logging | No structured logging | Use structured JSON logging with timestamps | | Not handling async errors | Unhandled promise rejections crash app | Wrap async code in try-catch, use error middleware | | Hardcoding URLs in import statements | Version conflicts, outdated deps | Use deps.ts for centralized dependency management | | Not setting response status explicitly | Defaults to 200 even for errors | Set ctx.response.status explicitly | | Mixing state across requests | Memory leaks, security issues | Use ctx.state for request-scoped data only | | Not validating request body | Security vulnerabilities | Use Zod or similar for validation | | Using any type extensively | Loses TypeScript benefits | Define proper interfaces for requests/responses |

Quick Troubleshooting

| Issue | Likely Cause | Solution | |-------|--------------|----------| | Request hangs indefinitely | Middleware missing await next() | Add await next() or send response | | "Module not found" errors | Incorrect import URL or version | Check deps.ts, ensure correct version in URL | | CORS errors | CORS middleware not configured | Add oakCors() middleware before routes | | 404 for all routes | Routes registered after app.listen() | Register routes before calling listen() | | State not persisting | Using global variables | Use ctx.state for request-scoped state | | Type errors with context | Wrong type annotations | Use RouterContext<"/path"> for typed params | | WebSocket upgrade fails | ctx.isUpgradable check missing | Check ctx.isUpgradable before ctx.upgrade() | | Static files not serving | Wrong path in send() | Use absolute path with Deno.cwd() |

Reference Documentation