Oak Core Knowledge

SkillFiles & storage

Oak Deno middleware framework inspired by Koa. Covers routing, middleware, context, WebSocket, and static files. Use for Koa-like Deno APIs.

Available today. Use it from your connected AI after setup.

Connect ahel once, and every AI you use reads what you have installed.

Then ask your AI: use the Oak Core Knowledge skill

What this skill tells your AI

The instructions your AI receives, as published by claude-dev-suite/claude-dev-suite in skills/backend-frameworks/oak/SKILL.md and read by ahel’s review.

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-PatternWhy It's BadCorrect Approach
Not calling await next() in middlewareRequest hangs indefinitelyAlways call await next() unless sending response
Using console.log() for loggingNo structured loggingUse structured JSON logging with timestamps
Not handling async errorsUnhandled promise rejections crash appWrap async code in try-catch, use error middleware
Hardcoding URLs in import statementsVersion conflicts, outdated depsUse deps.ts for centralized dependency management
Not setting response status explicitlyDefaults to 200 even for errorsSet ctx.response.status explicitly
Mixing state across requestsMemory leaks, security issuesUse ctx.state for request-scoped data only
Not validating request bodySecurity vulnerabilitiesUse Zod or similar for validation
Using any type extensivelyLoses TypeScript benefitsDefine proper interfaces for requests/responses

Quick Troubleshooting

IssueLikely CauseSolution
Request hangs indefinitelyMiddleware missing await next()Add await next() or send response
"Module not found" errorsIncorrect import URL or versionCheck deps.ts, ensure correct version in URL
CORS errorsCORS middleware not configuredAdd oakCors() middleware before routes
404 for all routesRoutes registered after app.listen()Register routes before calling listen()
State not persistingUsing global variablesUse ctx.state for request-scoped state
Type errors with contextWrong type annotationsUse RouterContext<"/path"> for typed params
WebSocket upgrade failsctx.isUpgradable check missingCheck ctx.isUpgradable before ctx.upgrade()
Static files not servingWrong path in send()Use absolute path with Deno.cwd()

Reference Documentation

Signals

GitHub stars
33
Forks
6
Last commit
Sep 2026
Advanced
Catalog kind
skill
Gateway key
oak
Source
github.com/claude-dev-suite/claude-dev-suite