Documentation

Middlewares

Define route-level middlewares for fine-grained control over pages and APIs.

Page Middlewares

Define middlewares that run before your page loaders:

app/dashboard/page.server.hook.ts
import type { RouteMiddleware, ServerLoader } from "@lolyjs/core";

export const beforeServerData: RouteMiddleware[] = [
  async (ctx, next) => {
    // Authentication
    const token = ctx.req.headers.authorization;
    if (!token) {
      ctx.res.status(401).json({ error: "Unauthorized" });
      return;
    }
    
    const user = await verifyToken(token);
    ctx.locals.user = user;
    
    await next();
  },
];

export const getServerSideProps: ServerLoader = async (ctx) => {
  const user = ctx.locals.user; // Available from middleware
  return {
    props: { user },
    metadata: {
      title: "Dashboard",
    },
  };
};

API Middlewares

Define middlewares for API routes:

app/api/protected/route.ts
import type { ApiMiddleware, ApiContext } from "@lolyjs/core";

// Global middleware for all methods
export const beforeApi: ApiMiddleware[] = [
  async (ctx, next) => {
    // Authentication
    const user = await verifyUser(ctx.req);
    if (!user) {
      return ctx.Response({ error: "Unauthorized" }, 401);
    }
    
    ctx.locals.user = user;
    await next();
  },
];

export async function GET(ctx: ApiContext) {
  const user = ctx.locals.user; // Available from middleware
  return ctx.Response({ user });
}

Method-Specific Middlewares

Define middlewares that only run for specific HTTP methods:

app/api/posts/route.ts
import type { ApiMiddleware, ApiContext } from "@lolyjs/core";

// Middleware only for POST requests
export const beforePOST: ApiMiddleware[] = [
  async (ctx, next) => {
    // Validation specific to POST
    if (!ctx.req.body.title) {
      return ctx.Response({ error: "Title required" }, 400);
    }
    await next();
  },
];

// Middleware only for DELETE requests
export const beforeDELETE: ApiMiddleware[] = [
  async (ctx, next) => {
    // Check permissions for deletion
    const user = ctx.locals.user;
    if (!user.isAdmin) {
      return ctx.Response({ error: "Forbidden" }, 403);
    }
    await next();
  },
];

export async function GET(ctx: ApiContext) {
  // No middleware runs for GET
  return ctx.Response({ posts: [] });
}

export async function POST(ctx: ApiContext) {
  // beforePOST middleware runs first
  const post = await createPost(ctx.req.body);
  return ctx.Response({ post }, 201);
}

export async function DELETE(ctx: ApiContext) {
  // beforeDELETE middleware runs first
  await deletePost(ctx.params.id);
  return ctx.Response({ deleted: true }, 204);
}

Sharing Data with ctx.locals

Use ctx.locals to share data between middlewares and handlers:

// Middleware sets data
export const beforeApi: ApiMiddleware[] = [
  async (ctx, next) => {
    ctx.locals.user = await getUser(ctx.req);
    ctx.locals.requestId = generateId();
    await next();
  },
];

// Handler accesses data
export async function GET(ctx: ApiContext) {
  const { user, requestId } = ctx.locals;
  return ctx.Response({ user, requestId });
}