From harness-claude
Build fully-typed server API endpoints in Nuxt using Nitro's event-handler model and H3 utilities. Covers file-based routing, request/query/body handling, errors, and responses.
npx claudepluginhub intense-visions/harness-engineering --plugin harness-claudeThis skill uses the workspace's default tool permissions.
> Build fully-typed server API endpoints using Nitro's event-handler model and H3 utilities
Guides Nuxt 5 server-side development: API routes, middleware, Nitro v3/h3 v2 patterns, database integration (D1, Drizzle, PostgreSQL), and Nitro v2 migrations.
Develops Nuxt 4 server-side features with Nitro: API routes, server middleware, database integrations (D1, PostgreSQL, Drizzle), file uploads, WebSockets.
Share bugs, ideas, or general feedback.
Build fully-typed server API endpoints using Nitro's event-handler model and H3 utilities
server/api/ or server/routes/server/api/ to expose routes at /api/<name>. Files under server/routes/ map to the root path with no /api/ prefix.users.get.ts, users.post.ts. Omit the suffix to handle all methods.defineEventHandler function — this is the required Nitro entry point:// server/api/users.get.ts
export default defineEventHandler(async (event) => {
const users = await fetchUsersFromDb();
return users; // auto-serialized to JSON
});
getQuery, request body with readBody, route params with getRouterParam:// server/api/users/[id].get.ts
export default defineEventHandler(async (event) => {
const id = getRouterParam(event, 'id');
const query = getQuery(event); // ?include=posts
const user = await db.user.findUnique({ where: { id } });
if (!user) throw createError({ statusCode: 404, message: 'User not found' });
return user;
});
createError — Nuxt serializes these into structured JSON error responses:throw createError({ statusCode: 401, statusMessage: 'Unauthorized' });
readBody — always validate the shape:// server/api/posts.post.ts
export default defineEventHandler(async (event) => {
const body = await readBody(event);
if (!body.title) throw createError({ statusCode: 400, message: 'title required' });
return db.post.create({ data: body });
});
setResponseStatus(event, 201);
setHeader(event, 'X-Custom-Header', 'value');
setHeader(event, 'Content-Type', 'text/plain');
return 'Hello plain text';
server/utils/ for shared server-side helpers — these are auto-imported within the server/ tree.Nuxt's server layer is powered by Nitro, which itself uses H3 as its HTTP framework. H3 is a minimal, composable HTTP toolkit where every request is represented as an H3Event. All H3 utility functions accept this event as their first argument.
File-based routing:
server/
api/
users.get.ts → GET /api/users
users.post.ts → POST /api/users
users/[id].get.ts → GET /api/users/:id
auth/
login.post.ts → POST /api/auth/login
routes/
feed.xml.get.ts → GET /feed.xml
Dynamic catch-all routes:
Use [...slug].ts to match multiple path segments:
// server/api/[...slug].ts
export default defineEventHandler((event) => {
const slug = getRouterParams(event).slug;
return { path: slug };
});
Middleware within server routes:
You can apply server-side logic before any handler using server/middleware/. Files here run on every request automatically (no registration needed):
// server/middleware/auth.ts
export default defineEventHandler((event) => {
const token = getHeader(event, 'authorization');
if (!token) throw createError({ statusCode: 401 });
event.context.user = verifyToken(token);
});
Streaming responses:
Nitro supports streaming via Web Streams API for large payloads or SSE:
export default defineEventHandler((event) => {
setHeader(event, 'Content-Type', 'text/event-stream');
return sendStream(event, createReadableStream());
});
Type safety with $fetch:
On the client side, use $fetch or useFetch — Nuxt infers the return type from the server handler automatically in full-stack TypeScript mode.
When NOT to use:
adapter-cloudflare and adapter-vercel-edge have no Node.js fs modulehttps://nuxt.com/docs/guide/directory-structure/server