.claude/skills/pikku-concepts/SKILL.md
Foundational guide to Pikku framework concepts. Use this skill when working with any Pikku codebase, starting a new Pikku project, or migrating a backend to Pikku. Covers the core mental model, function types, project structure, code generation, testing, and how Pikku maps to traditional backend patterns. TRIGGER when: user asks "what is Pikku?", starts a new Pikku project, migrates from Express/NestJS/Hono, or needs to understand how Pikku works. DO NOT TRIGGER when: user is doing a specific wiring task (use the specific skill instead, e.g. pikku-http, pikku-websocket).
npx skillsauth add pikkujs/pikku pikku-conceptsInstall 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.
Pikku is a TypeScript framework that separates business logic from transport mechanisms. You define a function once, then wire it to HTTP, WebSocket, queues, schedulers, MCP, CLI, or RPC — without the function knowing how it's being called.
For deep-dive on each topic, see the dedicated skills:
pikku-http, pikku-websocket, pikku-rpc, pikku-mcp, pikku-queue, pikku-cron, pikku-trigger, pikku-cli, pikku-ai-agent, pikku-workflowpikku-services, pikku-security, pikku-configpikku-infopikkuFunc (pure business logic)
│
├── wireHTTP → Express, Fastify, Next.js, Lambda, Cloudflare...
├── wireChannel → WebSocket (real-time)
├── wireQueueWorker → BullMQ, PgBoss (async jobs)
├── wireScheduler → Cron (scheduled tasks)
├── wireMCPTool → Model Context Protocol (AI tools)
├── wireCLI → CLI commands
├── wireTrigger → Event-driven (Redis pub/sub, PG LISTEN/NOTIFY)
├── pikkuAIAgent → AI agents / chatbots
├── pikkuWorkflow → Multi-step durable workflows
└── wire.rpc → Internal function-to-function calls
A pikkuFunc receives three things:
pikku-services.The function never imports Express, never reads req.body, never touches ws.send(). It just works with typed data and services.
| Generic Backend Concept | Pikku Equivalent | Skill |
| --------------------------------------- | --------------------------------------------------------------- | ----------------- |
| Controller / Route Handler | pikkuFunc / pikkuSessionlessFunc | (this skill) |
| Route definition (GET /users/:id) | wireHTTP({ route, method, func }) | pikku-http |
| Middleware (Express/Koa-style) | pikkuMiddleware | pikku-security |
| Auth Guard / Auth Middleware | authBearer() / authCookie() / authApiKey() | pikku-security |
| Authorization / Permissions | pikkuPermission / pikkuAuth | pikku-security |
| DTO / Request Validation | Standard Schema (Zod, Valibot, ArkType) | (this skill) |
| Dependency Injection | pikkuServices (singleton) + pikkuWireServices (per-request) | pikku-services |
| WebSocket handlers | wireChannel | pikku-websocket |
| Job Queue workers | wireQueueWorker | pikku-queue |
| Cron / Scheduled tasks | wireScheduler | pikku-cron |
| Module / Feature grouping | Tags + wiring files | (this skill) |
| Error handling | Throw typed errors (NotFoundError, ForbiddenError) | (this skill) |
| Type-safe API client | npx pikku prebuild generates clients | (this skill) |
| Secrets / Config | wireSecret, wireVariable, services.variables | pikku-config |
Three main function types:
// Requires authentication — receives session in wire context
const updateTodo = pikkuFunc<UpdateInput, TodoOutput>(
async (services, data, wire) => {
const session = await wire.session.get()
return services.todoStore.update(data.id, data)
}
)
// No authentication required
const listTodos = pikkuSessionlessFunc<ListInput, TodoListOutput>(
async (services, data) => {
return { todos: services.todoStore.list(data.filters) }
}
)
// No input or output (for scheduled tasks, lifecycle hooks)
const cleanup = pikkuVoidFunc(async (services) => {
services.todoStore.cleanOldItems()
})
Config object form (recommended):
const createTodo = pikkuSessionlessFunc({
title: 'Create Todo',
description: 'Create a new todo item',
input: CreateTodoInputSchema,
output: CreateTodoOutputSchema,
func: async ({ logger, todoStore }, { title, priority }) => {
const todo = todoStore.createTodo(title, priority)
logger.info(`Created todo: ${todo.id}`)
return { todo }
},
})
Full config options:
pikkuFunc({
title?: string, // Human-readable name
description?: string, // What the function does
version?: number, // Contract version (see pikku-config for versioning)
tags?: string[], // For grouping and middleware targeting
expose?: boolean, // Allow external RPC calls (see pikku-rpc)
remote?: boolean, // Allow remote RPC calls
mcp?: boolean, // Expose as MCP tool (see pikku-mcp)
auth?: boolean, // Override default auth requirement
input?: ZodSchema, // Input validation schema
output?: ZodSchema, // Output validation schema
permissions?: PermissionGroup, // See pikku-security
middleware?: PikkuMiddleware[], // See pikku-security
func: async (services, data, wire) => { ... },
})
Pikku uses Standard Schema — works with Zod, Valibot, ArkType:
import { z } from 'zod'
const CreateTodoInputSchema = z.object({
title: z.string().min(1).max(200),
priority: z.enum(['low', 'medium', 'high']).optional(),
tags: z.array(z.string()).optional(),
})
Schemas serve triple duty: runtime validation, TypeScript types, and OpenAPI documentation.
Every Pikku app follows the same bootstrap pattern regardless of runtime:
import '../../functions/.pikku/pikku-bootstrap.gen.js' // Generated — registers all wirings
const config = await createConfig()
const singletonServices = await createSingletonServices(config)
// Pick your runtime:
const server = new PikkuFastifyServer(
config,
singletonServices,
createWireServices
)
// or: new PikkuExpressServer(config, singletonServices, createWireServices)
// or: pikkuAWSLambdaHandler(singletonServices)
// or: PikkuCloudflareHandler(singletonServices)
// or: pikkuNextHandler(singletonServices)
await server.init()
await server.start()
Run npx pikku prebuild to generate:
pikku-types.gen.ts — Typed function factories and wiring functionspikku-fetch.gen.ts — Type-safe HTTP clientpikku-websocket.gen.ts — Type-safe WebSocket clientpikku-bootstrap.gen.js — Runtime initialization (auto-imports all wirings)pikku-services.gen.ts — Service factory typesConfig lives in pikku.config.json:
{
"tsconfig": "./tsconfig.json",
"srcDirectories": ["src"],
"outDir": ".pikku"
}
src/
├── functions/ # Business logic (pikkuFunc definitions)
│ ├── todos.functions.ts
│ ├── auth.functions.ts
│ └── scheduled.functions.ts
├── wirings/ # Transport bindings
│ ├── todos.http.ts
│ ├── channel.wiring.ts
│ ├── scheduler.wiring.ts
│ └── queue.wiring.ts
├── schemas.ts # Zod/Valibot schemas
├── services.ts # Service factories (see pikku-services)
├── middleware.ts # Middleware definitions (see pikku-security)
├── permissions.ts # Permission definitions (see pikku-security)
└── .pikku/ # Generated (gitignored)
├── pikku-types.gen.ts
├── pikku-fetch.gen.ts
└── pikku-bootstrap.gen.js
Never use process.env inside Pikku functions. Use the variables service (see pikku-config):
const apiKey = services.variables.get('API_KEY')
process.env belongs in server bootstrap code (start.ts) only.
Functions are easily testable because they're pure:
const mockServices = {
logger: new MockLogger(),
todoStore: new MockTodoStore(),
}
// Call function directly — no HTTP, no framework
const result = await listTodos.func(mockServices, { userId: 'test' })
expect(result.todos).toHaveLength(3)
| Package | Use Case |
| ----------------------------- | ------------------------------------- |
| @pikku/express-server | Express standalone server |
| @pikku/express-middleware | Express as middleware in existing app |
| @pikku/fastify-server | Fastify standalone |
| @pikku/fastify-plugin | Fastify plugin |
| @pikku/next | Next.js API routes |
| @pikku/aws-lambda | AWS Lambda handlers |
| @pikku/cloudflare | Cloudflare Workers |
| @pikku/uws-server | uWebSockets.js (high perf) |
| @pikku/modelcontextprotocol | MCP server |
| Package | Provides |
| ------------------------ | ---------------------------------- |
| @pikku/jose | JWT (sign/verify) via jose library |
| @pikku/schema-ajv | Schema validation via AJV |
| @pikku/schema-cfworker | Schema validation for Cloudflare |
| @pikku/pino | Structured logging via Pino |
| @pikku/kysely | Type-safe SQL via Kysely (PostgreSQL, SQLite, MySQL) |
| @pikku/redis | Redis client |
| @pikku/queue-bullmq | Job queues via BullMQ |
| @pikku/queue-pg-boss | Job queues via PgBoss |
| @pikku/aws-services | AWS SDK (SQS, DynamoDB, etc.) |
@Get() or @Injectable()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.
development
Make a Pikku frontend work in both English (LTR) and Arabic / right-to-left languages. Direction is derived from the active locale, applied once at the document root, and the layout mirrors itself — but only if styling is written flow-relative (margin-inline-start, text-align: start, Mantine ms/me) instead of left/right. TRIGGER when: adding Arabic (or Hebrew/Farsi/Urdu), asked to "support RTL / right-to-left / bidi / mirror the layout", or writing layout styles in an app that may run RTL. Builds on pikku-i18n (an RTL language is just another locale file). DO NOT TRIGGER for backend functions or for LTR-only copy changes.
development
Wire i18n into a Pikku frontend (Vite SPA, Vite SSR, or Next.js app-router) with react-i18next + i18next. English by default, every user-facing string goes through a `t()` token, and additional languages are served under `/de` `/es` URL prefixes. TRIGGER when: scaffolding or editing a frontend and writing user-facing text, adding a second language, or asked to "make this translatable / use tokens / add i18n". DO NOT TRIGGER for backend functions, error messages thrown from functions, or log output.
development
Use when translating an n8n Code node body into a real Pikku function body. Triggered when the user opens or points at a stub generated by @pikku/n8n-import (look for `STUB — generated from n8n Code node` in the file's JSDoc), or when the user says 'translate this n8n code', 'port this n8n code node', 'finish the codeStub__... function', etc. The stub file is a `pikkuSessionlessFunc` with a Zod input/output, a JSDoc preserving the original n8n JavaScript verbatim, and a `throw new Error('… — implement me')` body.