Awesome-omni-skill solidstart-middleware-auth

SolidStart middleware, sessions, authentication: createMiddleware with onRequest/onBeforeResponse, useSession for cookies, protected routes, WebSocket endpoints.

install
source · Clone the upstream repo
git clone https://github.com/diegosouzapw/awesome-omni-skill
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/diegosouzapw/awesome-omni-skill "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/backend/solidstart-middleware-auth" ~/.claude/skills/diegosouzapw-awesome-omni-skill-solidstart-middleware-auth && rm -rf "$T"
manifest: skills/backend/solidstart-middleware-auth/SKILL.md
safety · automated scan (low risk)
This is a pattern-based risk scan, not a security review. Our crawler flagged:
  • references .env files
Always read a skill's source content before installing. Patterns alone don't mean the skill is malicious — but they warrant attention.
source content

SolidStart Middleware, Sessions & Auth

Middleware

Configure in

app.config.ts
:

export default defineConfig({
  middleware: "src/middleware/index.ts"
});

When to Use Middleware (and When Not To)

Use middleware for:

  • Request/response header management (CSP, cache control, CORS)
  • Request-scoped data via
    event.locals
    (trace IDs, feature flags, auth hints)
  • Early redirects (legacy URLs, locale routing, maintenance mode)
  • Lightweight preprocessing (normalizing headers, basic validation)

Avoid middleware for:

  • Authorization (middleware does not run on every client-side navigation)
  • Heavy computation or database calls (keep it fast and side-effect light)
  • Business logic that depends on user state (put checks in queries/actions/API)

Basic Middleware

// src/middleware/index.ts
import { createMiddleware } from "@solidjs/start/middleware";

export default createMiddleware({
  onRequest: (event) => {
    console.log("Request:", event.request.url);
    event.locals.startTime = Date.now();
  },
  onBeforeResponse: (event) => {
    const duration = Date.now() - event.locals.startTime;
    console.log(`Request took ${duration}ms`);
  }
});

Lifecycle Events

  • onRequest
    : Before route handler (modify headers, store in locals)
  • onBeforeResponse
    : After route handler (modify response, logging)

Returning a

Response
from either hook short-circuits the pipeline and skips remaining middleware and route handlers.

export default createMiddleware({
  onRequest: (event) => {
    event.locals.userAgent = event.request.headers.get("user-agent");
  },
  onBeforeResponse: (event) => {
    event.response.headers.set("X-Response-Time", "100ms");
  }
});

Accessing Locals

Store in middleware, access with

getRequestEvent
:

// middleware/index.ts
export default createMiddleware({
  onRequest: (event) => {
    event.locals.user = { id: "123", name: "John" };
  }
});

// In server function
import { getRequestEvent } from "solid-js/web";

const getUser = query(async () => {
  "use server";
  const event = getRequestEvent();
  return event?.locals?.user;
}, "user");

Typing Locals

// global.d.ts
/// <reference types="@solidjs/start/env" />
declare module "App" {
  interface RequestEventLocals {
    user?: { id: string; name: string };
    startTime?: number;
  }
}

Headers & Cookies

import { getCookie, setCookie } from "vinxi/http";

export default createMiddleware({
  onRequest: (event) => {
    // Read headers
    const userAgent = event.request.headers.get("user-agent");
    
    // Set headers
    event.request.headers.set("x-custom-header", "value");
    event.response.headers.set("Cache-Control", "max-age=3600");
    
    // Cookies
    const theme = getCookie(event.nativeEvent, "theme");
    setCookie(event.nativeEvent, "session", "abc123", {
      httpOnly: true,
      secure: true,
      maxAge: 60 * 60 * 24
    });
  }
});

Custom Responses & Short-Circuiting

Only

Response
objects can be returned from middleware.

import { json, redirect } from "@solidjs/router";

export default createMiddleware({
  onRequest: (event) => {
    const { pathname } = new URL(event.request.url);
    if (pathname === "/old-path") {
      return redirect("/new-path", 301);
    }

    const authHeader = event.request.headers.get("Authorization");
    if (!authHeader) {
      return json({ error: "Unauthorized" }, { status: 401 });
    }
  }
});

Building Custom Middleware (Composable Functions)

Prefer small, focused functions and compose them in order.

import type { FetchEvent } from "@solidjs/start/server";

function withRequestId() {
  return (event: FetchEvent) => {
    const id = crypto.randomUUID();
    event.locals.requestId = id;
    event.response.headers.set("x-request-id", id);
  };
}

function withTiming() {
  return (event: FetchEvent) => {
    event.locals.startTime = Date.now();
  };
}

function withTimingResponse() {
  return (event: FetchEvent) => {
    const ms = Date.now() - event.locals.startTime;
    event.response.headers.set("x-response-time", `${ms}ms`);
  };
}

export default createMiddleware({
  onRequest: [withRequestId(), withTiming()],
  onBeforeResponse: [withTimingResponse()]
});

Extending Existing Middleware

Export shared middleware arrays, then extend them in route-specific configs.

// src/middleware/base.ts
import type { FetchEvent } from "@solidjs/start/server";

export const baseOnRequest: Array<(e: FetchEvent) => void> = [
  (event) => {
    event.locals.userAgent = event.request.headers.get("user-agent");
  }
];
// src/middleware/index.ts
import { createMiddleware } from "@solidjs/start/middleware";
import { baseOnRequest } from "./base";
import type { FetchEvent } from "@solidjs/start/server";

function withMaintenanceMode() {
  return (event: FetchEvent) => {
    if (process.env.MAINTENANCE === "true") {
      return new Response("Maintenance", { status: 503 });
    }
  };
}

export default createMiddleware({
  onRequest: [...baseOnRequest, withMaintenanceMode()]
});

Redirects & Responses

import { redirect, json } from "@solidjs/router";

export default createMiddleware({
  onRequest: (event) => {
    const { pathname } = new URL(event.request.url);
    if (pathname === "/old-path") {
      return redirect("/new-path", 301);
    }
    
    const authHeader = event.request.headers.get("Authorization");
    if (!authHeader) {
      return json({ error: "Unauthorized" }, { status: 401 });
    }
  }
});

Chaining Middleware

function middleware1(event: FetchEvent) {
  event.request.headers.set("x-header1", "value1");
}

function middleware2(event: FetchEvent) {
  event.request.headers.set("x-header2", "value2");
}

export default createMiddleware({
  onRequest: [middleware1, middleware2]
});

Order matters: middleware runs in array order; dependencies must come first.

Sessions

Basic Session

// src/lib/session.ts
import { useSession } from "vinxi/http";

type SessionData = {
  userId?: string;
  theme?: "light" | "dark";
};

export async function useAppSession() {
  "use server";
  const session = await useSession<SessionData>({
    password: process.env.SESSION_SECRET!, // Must be 32+ chars
    name: "app-session"
  });
  return session;
}

// Generate: openssl rand -base64 32

Session Operations

// Get session data
export async function getSessionUserId() {
  "use server";
  const session = await useAppSession();
  return session.data.userId;
}

// Update session
export async function updateSession(updates: Partial<SessionData>) {
  "use server";
  const session = await useAppSession();
  await session.update(updates);
}

// Clear session
export async function clearSession() {
  "use server";
  const session = await useAppSession();
  await session.clear();
}

Using with Queries

const getCurrentUser = query(async () => {
  "use server";
  const session = await useAppSession();
  if (!session.data.userId) {
    throw redirect("/login");
  }
  return await db.getUser(session.data.userId);
}, "currentUser");

Authentication

Protected Routes

// routes/admin.tsx
import { query, redirect, createAsync } from "@solidjs/router";

const getAdminData = query(async () => {
  "use server";
  const session = await useAppSession();
  if (!session.data.userId) {
    throw redirect("/login");
  }
  
  const user = await db.getUser(session.data.userId);
  if (!user.isAdmin) {
    throw redirect("/");
  }
  
  return await db.getAdminData();
}, "adminData");

export default function AdminPage() {
  const data = createAsync(() => getAdminData(), { deferStream: true });
  return <div>{data()}</div>;
}

Important: Use

deferStream: true
- server-side redirects can't occur after streaming starts.

Login Action

const loginAction = action(async (formData: FormData) => {
  "use server";
  const email = formData.get("email")?.toString();
  const password = formData.get("password")?.toString();
  
  const user = await db.verifyUser(email, password);
  if (!user) {
    return { error: "Invalid credentials" };
  }
  
  const session = await useAppSession();
  await session.update({ userId: user.id });
  
  throw redirect("/dashboard");
}, "login");

Logout Action

const logoutAction = action(async () => {
  "use server";
  const session = await useAppSession();
  await session.clear();
  throw redirect("/");
}, "logout");

WebSocket Endpoints

Experimental - use with caution.

Configuration

// app.config.ts
export default defineConfig({
  server: {
    experimental: { websocket: true }
  }
}).addRouter({
  name: "ws",
  type: "http",
  handler: "./src/ws.ts",
  target: "server",
  base: "/ws"
});

WebSocket Handler

// src/ws.ts
import { eventHandler } from "vinxi/http";

export default eventHandler({
  handler() {},
  websocket: {
    async open(peer) {
      console.log("Connection:", peer.id);
    },
    async message(peer, msg) {
      const message = msg.text();
      peer.send(message); // Broadcast
    },
    async close(peer, details) {
      console.log("Closed:", peer.id);
    },
    async error(peer, error) {
      console.error("Error:", error);
    }
  }
});

Best Practices

  1. Keep middleware lightweight - no heavy computation
  2. Use sessions for auth state - secure, encrypted cookies
  3. Use
    deferStream: true
    for protected routes
  4. Type your locals for better TypeScript support
  5. Don't rely on middleware for authorization (doesn't run on all requests)
  6. Return a
    Response
    only when you want to terminate the pipeline
  7. Compose small middleware functions and keep ordering explicit