packages/cli/skills/pikku-http/SKILL.md
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-*).
npx skillsauth add pikkujs/pikku pikku-httpInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Use this skill as an execution checklist, not reference material.
pikku-meta when available; otherwise run the relevant pikku meta ... --json command and inspect only the focused output you need..pikku, node_modules, vendored packages, or broad build artifacts.pikku-verify or pikku all when functions, wirings, schemas, or generated clients may have changed.Wire Pikku functions to HTTP endpoints. Supports single routes, composable route groups, auth, middleware, permissions, SSE, and auto-generated type-safe clients.
Run these commands to understand the current project:
pikku info functions --verbose # See existing functions, their types, tags, middleware
pikku info tags --verbose # Understand project organization and naming conventions
pikku info middleware --verbose # See what middleware is already applied
Follow existing patterns you find (naming, tag usage, file organization). See pikku-concepts for the core mental model.
wireHTTP(config)Wire a single function to an HTTP endpoint.
import { wireHTTP } from '@pikku/core/http'
wireHTTP({
method: 'get' | 'post' | 'put' | 'patch' | 'delete' | 'head',
route: string, // e.g. '/books/:bookId' — :params become data fields
func: PikkuFunc, // The function to call
auth?: boolean, // Override default auth (true = require session)
tags?: string[], // For grouping, middleware targeting
permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
middleware?: PikkuMiddleware[],
sse?: boolean, // Enable Server-Sent Events
contentType?: 'xml' | 'json', // Response content type
timeout?: number, // Request timeout in ms
headers?: HTTPHeadersSchema, // Expected headers schema
docs?: HTTPRouteDocsConfig, // OpenAPI docs config
})
defineHTTPRoutes(config) + wireHTTPRoutes(config)Group routes with shared configuration. Groups are composable and nestable.
import { defineHTTPRoutes, wireHTTPRoutes } from '.pikku/pikku-types.gen.js'
const routes = defineHTTPRoutes({
basePath?: string, // Prepended to all route paths
tags?: string[], // Applied to all routes in group
auth?: boolean, // Default auth for all routes (overridable per-route)
middleware?: PikkuMiddleware[],
routes: {
[key: string]: {
method: string,
route: string,
func: PikkuFunc,
auth?: boolean, // Override group auth
permissions?: Record<string, PikkuPermission | PikkuPermission[]>,
middleware?: PikkuMiddleware[],
}
}
})
wireHTTPRoutes({
basePath?: string, // Top-level prefix (e.g. '/api/v1')
middleware?: PikkuMiddleware[],
routes: {
[key: string]: ReturnType<typeof defineHTTPRoutes>,
}
})
Config cascading rules:
basePath — concatenates down the chaintags — merge (union)auth — child overrides parentaddHTTPMiddleware(pattern, middlewares)addHTTPMiddleware('*', [authBearer()]) // All routes
addHTTPMiddleware('/api/*', [rateLimit()]) // Pattern match
addHTTPPermission(pattern, permissions)addHTTPPermission('/admin/*', { admin: [isAdmin] })
Pikku merges route params, query params, and request body into a single data object:
// POST /books/42?format=pdf with body { title: "New Title" }
wireHTTP({ method: 'post', route: '/books/:bookId', func: updateBook })
// → updateBook receives: { bookId: "42", format: "pdf", title: "New Title" }
wireHTTP({
method: 'get',
route: '/books/:bookId',
func: getBook,
})
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 },
create: { method: 'post', route: '/todos', func: createTodo },
get: { method: 'get', route: '/todos/:id', func: getTodo },
},
})
wireHTTPRoutes({
basePath: '/api/v1',
middleware: [cors()],
routes: {
books: booksRoutes,
todos: todosRoutes,
},
})
// Results in: GET /api/v1/books, POST /api/v1/books, etc.
// Public route (no auth)
wireHTTP({ method: 'get', route: '/books', func: listBooks, auth: false })
// Route with permission check
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
permissions: { admin: isAdmin },
})
// Pattern-based permissions
addHTTPPermission('/admin/*', { admin: isAdmin })
import { cors, authBearer } from '@pikku/core/middleware'
// Global middleware
addHTTPMiddleware('*', [
cors({ origin: 'https://app.example.com', credentials: true }),
authBearer(),
])
// Scoped middleware
addHTTPMiddleware('/api/*', [rateLimit({ maxRequests: 100, windowMs: 60_000 })])
// Per-route middleware
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
middleware: [auditLog],
})
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 }
},
})
After npx pikku prebuild, 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,
})
// functions/books.functions.ts
import { pikkuFunc, pikkuSessionlessFunc } from '#pikku'
export const listBooks = pikkuSessionlessFunc({
title: 'List Books',
func: async ({ db }, { limit }) => {
return { books: await db.listBooks(limit) }
},
})
export const getBook = pikkuFunc({
title: 'Get Book',
description: 'Retrieve a book by ID',
func: async ({ db }, { bookId }) => {
return await db.getBook(bookId)
},
permissions: { user: isAuthenticated },
})
export const createBook = pikkuFunc({
title: 'Create Book',
func: async ({ db }, { title, author }) => {
return await db.createBook({ title, author })
},
})
export const deleteBook = pikkuFunc({
title: 'Delete Book',
func: async ({ db }, { bookId }) => {
await db.deleteBook(bookId)
return { deleted: true }
},
})
// wirings/books.http.ts
import { defineHTTPRoutes, wireHTTPRoutes } from '.pikku/pikku-types.gen.js'
import { addHTTPMiddleware } from '@pikku/core/http'
import { cors, authBearer } from '@pikku/core/middleware'
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 },
},
})
wireHTTPRoutes({
basePath: '/api',
routes: { books: booksRoutes },
})
addHTTPMiddleware('*', [cors(), authBearer()])
documentation
Deprecated — use pikku-middleware instead. Tag middleware (addTagMiddleware) is now documented as a section within the pikku-middleware skill, alongside global HTTP middleware, execution order, and the service-to-service bearer auth pattern.
testing
Use when adding authorization checks to Pikku functions or routes — pikkuPermission, pikkuAuth, per-function permissions, pattern-based permissions, or understanding OR/AND permission logic. TRIGGER when: user wants to restrict who can call a function, check resource ownership, add role-based access, or understand where permission checks belong. DO NOT TRIGGER when: user asks about middleware or request interception (use pikku-middleware), authentication strategies (use pikku-security), or session management.
testing
Use when adding any middleware to a Pikku app — global HTTP middleware, tag-scoped middleware (including service-to-service bearer auth), per-route middleware, session-setting middleware, or understanding middleware execution order and priority. TRIGGER when: user wants middleware on some or all routes, machine-to-machine auth, tag-scoped cross-cutting concerns, global interceptors, or middleware priority/order questions. DO NOT TRIGGER when: user asks about permissions/authorization checks (use pikku-permissions), auth strategies like authBearer/authCookie (use pikku-security), or deployment.
documentation
Standard cleanup to run right after a Pikku template is cloned or scaffolded into a new project. TRIGGER when: a Pikku template was just cloned/scaffolded (via `pikku create`, `git clone <template>`, or the user says "I cloned the kanban template / starter / template"), or the working tree still looks like an untouched template (template README, placeholder `@project/*` name in package.json). DO NOT TRIGGER when: working in an established project mid-feature, or editing the template repo itself.