Protect API endpoints
Public routes, optional session, requireAuth, requireAdmin, and rate limits.
TurboJet does not use oRPC-style publicProcedure / protectedProcedure. Protection is ordinary Hono middleware composed on each route.
Patterns
| Pattern | How |
|---|---|
| Public | No auth middleware (e.g. feedback POST /) |
| Optional session | Call getSessionFromHeaders inside the handler |
| Authenticated | requireAuth → c.get("session") |
| Admin | requireAuth then requireAdmin (role ADMIN) |
Public + optional session
Feedback is public but can attach a signed-in user when cookies are present:
export const feedbackRouter = new Hono().post(
"/",
rateLimit("WRITE"),
zValidator("json", createFeedbackSchema),
async (c) => {
const session = await getSessionFromHeaders(c.req.raw.headers);
const result = await createFeedback({
data: c.req.valid("json"),
session,
ipAddress: c.req.header("x-forwarded-for") ?? null,
});
return c.json(result);
},
);getSessionFromHeaders returns null for guests — it does not reject the request.
Authenticated (requireAuth)
Use requireAuth so unauthenticated callers get 401. Typed routers use Hono<AuthEnv> so c.get("session") is available:
import { requireAuth, type AuthEnv } from "../../middleware/auth";
export const meRouter = new Hono<AuthEnv>().patch(
"/name",
rateLimit("WRITE"),
requireAuth,
zValidator("json", changeNameSchema),
async (c) => {
const session = c.get("session");
const result = await changeName(session.user.id, c.req.valid("json"));
return c.json(result);
},
);Admin (requireAuth + requireAdmin)
Admin routes chain both middleware. Spreading a shared guard keeps the order consistent:
import { requireAuth, type AuthEnv } from "../../middleware/auth";
import { requireAdmin } from "../../middleware/admin";
const adminGuard = [rateLimit("READ"), requireAuth, requireAdmin] as const;
const adminWriteGuard = [rateLimit("WRITE"), requireAuth, requireAdmin] as const;
const usersRouter = new Hono<AuthEnv>().get(
"/",
...adminGuard,
zValidator("query", usersListInputSchema),
async (c) => {
const result = await getUsersList(c.req.valid("query"));
return c.json(result);
},
);requireAdmin checks that the session user has role ADMIN.
Custom middleware
Compose additional Hono middleware the same way — before or after auth as needed. There is no Permix / org-procedure layer in this kit.
Rate limiting
Almost every route should use rateLimit("READ" | "WRITE" | …) early in the chain. Buckets and limits live in packages/api/src/middleware/rate-limit.ts and packages/api/src/lib/ratelimit.ts.