MageContext

MageContext is the context object passed to every middleware and handler in Mage. It provides access to the request and response, along with helper methods for sending responses, setting headers, and sharing data between middleware.

Quick Start

import { MageApp } from "@mage/app";

const app = new MageApp();

app.get("/users/:id", (c) => {
  const id = c.req.params.id;
  return c.json({ id, name: "Alice" });
});

Deno.serve(app.handler);

Request Access

Access the incoming request through c.req:

app.get("/profile", (c) => {
  const userId = c.req.params.id;
  const authHeader = c.req.header("authorization");
  const userAgent = c.req.header("user-agent");

  return c.json({ userId, userAgent });
});

See Request & Response for the complete request API.

Response Helpers

MageContext provides helper methods for sending common response types. Each helper sets the response body, status code, and appropriate Content-Type header.

Text Responses

Send plain text responses:

app.get("/", (c) => {
  return c.text("Hello, World!");
});

app.get("/error", (c) => {
  return c.text("Something went wrong", 500);
});

JSON Responses

Send JSON responses with automatic serialization:

app.get("/users", (c) => {
  return c.json({ users: [] });
});

app.post("/users", async (c) => {
  const body = await c.req.json();
  return c.json({ id: 1, ...body }, 201);
});

HTML Responses

Send HTML responses:

app.get("/", (c) => {
  return c.html("<h1>Hello, World!</h1>");
});

app.get("/page", (c) => {
  const html = `
    <!DOCTYPE html>
    <html>
      <body><h1>Welcome</h1></body>
    </html>
  `;
  return c.html(html);
});

Empty Responses

Send responses with no body:

app.delete("/users/:id", (c) => {
  deleteUser(c.req.params.id);
  return c.empty(); // 204 No Content
});

app.post("/webhook", (c) => {
  processWebhook(c.req.body());
  return c.empty(202); // 202 Accepted
});

Error Responses

Send common error responses:

app.get("/users/:id", (c) => {
  const user = findUser(c.req.params.id);

  if (!user) {
    return c.notFound("User not found");
  }

  return c.json(user);
});

app.get("/admin", (c) => {
  const isAdmin = checkAdmin(c);

  if (!isAdmin) {
    return c.forbidden("Admin access required");
  }

  return c.json({ data: "secret" });
});

Response Methods Reference

Method Status Content-Type Description
c.text(body, status?) 200 text/plain Send plain text
c.json(body, status?) 200 application/json Send JSON
c.html(body, status?) 200 text/html Send HTML
c.empty(status?) 204 - Send empty response
c.notFound(text?) 404 text/plain Send 404 Not Found
c.forbidden(text?) 403 text/plain Send 403 Forbidden
c.redirect(location, status) 307 - Redirect to URL
c.rewrite(location) varies varies Proxy request to another URL
c.file(filepath) varies auto-detected Serve static file
c.webSocket(handleSocket) 101 - Upgrade to WebSocket connection

Header Management

Set response headers using c.header():

app.get("/api/data", (c) => {
  c.header("X-Custom-Header", "value");
  c.header("Cache-Control", "max-age=3600");
  return c.json({ data: [] });
});

Headers set with c.header() are merged with headers set by response helpers:

app.get("/", (c) => {
  c.header("X-Request-ID", "123");
  return c.json({ message: "Hello" });
  // Response includes both X-Request-ID and Content-Type: application/json
});

Context Storage

Share data between middleware using c.set() and c.get():

import { MageApp } from "@mage/app";

const app = new MageApp();

// Middleware stores user in context
app.use(async (c, next) => {
  const token = c.req.header("authorization");
  const user = await validateToken(token);
  c.set("user", user);
  await next();
});

// Handler retrieves user from context
app.get("/profile", (c) => {
  const user = c.get<{ id: string; name: string }>("user");
  return c.json(user);
});

Type the stored data for safety:

interface User {
  id: string;
  name: string;
  email: string;
}

// Store typed data
c.set("user", user);

// Retrieve with type assertion
const user = c.get<User>("user");

Important: Context storage is scoped to a single request. Data is not shared across requests.

File Serving

Serve static files from the filesystem using c.file():

import { MageApp } from "@mage/app";

const app = new MageApp();

app.get("/files/:filename", async (c) => {
  const filename = c.req.params.filename;
  const filepath = `./public/${filename}`;

  try {
    await c.file(filepath);
  } catch {
    return c.notFound("File not found");
  }
});

Deno.serve(app.handler);

c.file() automatically:

  • Detects the correct Content-Type based on file extension
  • Sets appropriate caching headers
  • Handles range requests for partial content
  • Integrates with Deno's optimized file serving

Security: Always validate file paths to prevent directory traversal attacks. Use middleware like serveFiles for production file serving with built-in security.

Redirects

Redirect requests to a different URL using c.redirect():

app.get("/old-path", (c) => {
  return c.redirect("/new-path");
});

app.get("/login", (c) => {
  return c.redirect("https://auth.example.com/login", 302);
});

The default redirect status is 307 Temporary Redirect, which preserves the HTTP method. Use 302 for traditional redirects that change POST to GET.

Rewrites

Proxy requests to another URL using c.rewrite():

app.get("/api/*", async (c) => {
  // Proxy to backend API
  await c.rewrite("https://backend.example.com" + c.req.url.pathname);
});

app.get("/proxy/:path", async (c) => {
  const path = c.req.params.path;
  await c.rewrite(`https://api.example.com/${path}`);
});

c.rewrite() makes a network request to the target URL and returns the response. The original request method, headers, and body are forwarded.

Important: c.rewrite() creates a network request, so it's not optimal for local path rewrites. Use it for proxying to external services.

Query strings from the original request are preserved:

// GET /api/users?limit=10
app.get("/api/*", async (c) => {
  // Rewrites to https://backend.example.com/api/users?limit=10
  await c.rewrite("https://backend.example.com" + c.req.url.pathname);
});

WebSocket Support

Upgrade connections to WebSocket using c.webSocket():

import { MageApp } from "@mage/app";

const app = new MageApp();

app.get("/ws", (c) => {
  return c.webSocket((socket) => {
    socket.onmessage = (event) => {
      console.log("Received:", event.data);
      socket.send("Echo: " + event.data);
    };

    socket.onclose = () => {
      console.log("Connection closed");
    };

    socket.onerror = (error) => {
      console.error("WebSocket error:", error);
    };
  });
});

Deno.serve(app.handler);

If the request is not a WebSocket upgrade request, c.webSocket() returns 501 Not Implemented.