API routes should feel like the Web: Request in, Response out, validation close to the boundary, typed clients where they reduce mistakes, and no hidden monolithic server.
Use platform primitives. Prefer Fetch, Request, Response, FormData and URLSearchParams over framework-specific transport.
Keep validation at the edge. Parse and validate request bodies before application logic sees them.
Make runtime explicit. Static pages can call APIs, but those APIs still need a serverless or edge deployment target.
| File | URL | Use |
|---|---|---|
| app/routes/api/posts.ts | /api/posts | Collection handlers. |
| app/routes/api/posts/[id].ts | /api/posts/:id | Resource handlers. |
| app/routes/api/search.ts | /api/search?q=kiss | Query-driven endpoints. |
Keep successful responses boring and predictable. Use HTTP status codes for status, JSON bodies for data, and framework errors for structured failures.
return c.json({ posts }, 200);
return c.json({ id, ...created }, 201);
return c.json({ error: { code: 'NOT_FOUND', message: 'Post not found' } }, 404);
LessJS does not force a validation library. Zod with @hono/zod-validator is a practical default when you want typed input.
import { zValidator } from '@hono/zod-validator';
import { z } from 'zod';
const schema = z.object({
title: z.string().min(1),
body: z.string().optional(),
});
app.post('/', zValidator('json', schema), (c) => {
const data = c.req.valid('json');
return c.json({ ok: true, data }, 201);
});
@lessjs/rpc is where type-safe client/server calling conventions can mature. Treat it as an opt-in layer over Hono rather than a replacement for plain API routes.
Future FormData actions should start from native forms, redirects and structured validation errors. They should enhance static pages without turning LessJS into a SPA runtime.