| name | pikku-http |
| description | Use when adding HTTP routes, REST APIs, web endpoints, or SSE streams to a Pikku app. Covers wireHTTP, defineHTTPRoutes, route groups, auth, middleware, permissions, SSE, and generated fetch client. TRIGGER when: code uses wireHTTP/defineHTTPRoutes/wireHTTPRoutes, user asks about REST endpoints, API routes, SSE, or the generated fetch client. DO NOT TRIGGER when: user asks about WebSocket (use pikku-websocket), queue workers (use pikku-queue), or deployment (use pikku-deploy-*). |
| installGroups | ["core"] |
Pikku HTTP Wiring
Agent Operating Procedure
Use this skill as an execution checklist, not reference material.
- Discover before editing. Prefer OpenCode tools such as
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need.
- Identify the source files that own the behavior. Do not start by reading generated output,
.pikku, node_modules, vendored packages, or broad build artifacts.
- Make the smallest source change that satisfies the task. Keep generated files generated, and avoid hand-editing SDKs, schema output, or typegen.
- Validate with the narrowest relevant command first, then run
pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.
- If validation fails, fix the source cause and rerun validation. Do not paper over generated errors by editing generated files.
Wire Pikku functions to HTTP endpoints. Supports single routes, composable route groups, auth, middleware, permissions, SSE, and auto-generated type-safe clients.
Before You Start
Run these commands to understand the current project:
pikku info functions --verbose
pikku info tags --verbose
pikku info middleware --verbose
Follow existing patterns you find (naming, tag usage, file organization). See pikku-concepts for the core mental model.
API Reference
wireHTTP(config) (from @pikku/core/http) — wire one function to one endpoint.
defineHTTPRoutes(config) + wireHTTPRoutes(config) (from .pikku/pikku-types.gen.js) — group routes with shared config; composable/nestable.
Function input/output types come from the function's own input:/output: zod schemas — never declared in the wiring. Route :params, query params, and body are merged into the function's data arg (see Data Flow).
Config cascading across groups: basePath concatenates down the chain, tags merge (union), auth child overrides parent.
For the full option tables (every wireHTTP field, the defineHTTPRoutes/wireHTTPRoutes config shape), read references/http-options.md.
addHTTPMiddleware(pattern, middlewares)
addHTTPMiddleware('*', [authBearer()])
addHTTPMiddleware('/api/*', [rateLimit()])
addHTTPPermission(pattern, permissions)
addHTTPPermission('/admin/*', { admin: [isAdmin] })
Data Flow
Pikku merges route params, query params, and request body into a single data object:
wireHTTP({ method: 'post', route: '/books/:bookId', func: updateBook })
Usage Patterns
Single Route
wireHTTP({
method: 'get',
route: '/books/:bookId',
func: getBook,
})
Route Groups (Recommended for CRUD)
const booksRoutes = defineHTTPRoutes({
tags: ['books'],
routes: {
list: { method: 'get', route: '/books', func: listBooks, auth: false },
get: { method: 'get', route: '/books/:bookId', func: getBook },
create: { method: 'post', route: '/books', func: createBook },
delete: { method: 'delete', route: '/books/:bookId', func: deleteBook },
},
})
const todosRoutes = defineHTTPRoutes({
auth: false,
tags: ['todos'],
routes: {
list: { method: 'get', route: '/todos', func: listTodos },
},
})
wireHTTPRoutes({
basePath: '/api/v1',
middleware: [cors()],
routes: { books: booksRoutes, todos: todosRoutes },
})
Auth & Permissions
wireHTTP({ method: 'get', route: '/books', func: listBooks, auth: false })
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
permissions: { admin: isAdmin },
})
addHTTPPermission('/admin/*', { admin: isAdmin })
Middleware
import { cors, authBearer } from '@pikku/core/middleware'
addHTTPMiddleware('*', [
cors({ origin: 'https://app.example.com', credentials: true }),
authBearer(),
])
addHTTPMiddleware('/api/*', [rateLimit({ maxRequests: 100, windowMs: 60_000 })])
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
middleware: [auditLog],
})
SSE (Server-Sent Events)
wireHTTP({
method: 'get',
route: '/todos',
func: getTodos,
sse: true,
})
const getTodos = pikkuFunc({
title: 'Get Todos',
func: async ({ db, channel }, {}) => {
const todos = await db.getTodos()
if (channel) {
for (const todo of todos) {
channel.send({ todo })
await sleep(100)
}
return
}
return { todos }
},
})
Generated Fetch Client
After npx pikku all, a type-safe client is generated:
import { pikkuFetch } from '.pikku/pikku-fetch.gen.js'
pikkuFetch.setServerUrl('http://localhost:4002')
const books = await pikkuFetch.get('/api/v1/books', {})
const book = await pikkuFetch.get('/api/v1/books/:bookId', { bookId: '42' })
const created = await pikkuFetch.post('/api/v1/books', {
title: 'The Pikku Guide',
author: 'You',
})
pikkuFetch.setAuthorizationJWT(token)
const deleted = await pikkuFetch.delete('/api/v1/books/:bookId', {
bookId: created.bookId,
})
Complete Example
Functions live in their own files (one per file) and supply behavior + permissions; the wiring file imports them and wires routes. Sessionless funcs need no session; pikkuFunc does.
import { pikkuFunc, pikkuSessionlessFunc } from '#pikku'
export const listBooks = pikkuSessionlessFunc({
title: 'List Books',
func: async ({ db }, { limit }) => ({ books: await db.listBooks(limit) }),
})
export const getBook = pikkuFunc({
title: 'Get Book',
description: 'Retrieve a book by ID',
func: async ({ db }, { bookId }) => await db.getBook(bookId),
permissions: { user: isAuthenticated },
})
import { addHTTPMiddleware } from '@pikku/core/http'
import { cors, authBearer } from '@pikku/core/middleware'
addHTTPMiddleware('*', [cors(), authBearer()])