Skip to main content
Wire Type: HTTP

Your functions, now
REST endpoints.

wireHTTP maps your Pikku functions to type-safe HTTP routes — with automatic input merging, OpenAPI generation, and optional SSE streaming.

The Basics

Function to endpoint in two lines

Define your function once, wire it to a route. Pikku handles the rest.

getBook.func.tsfunc.ts
const getBook = pikkuFunc({
title: 'Get Book',
description: 'Retrieve a book by ID',
func: async ({ db }, { bookId }) => {
return await db.getBook(bookId)
},
permissions: { user: isAuthenticated }
})
books.wiring.tswiring.ts
wireHTTP({
method: 'get',
route: '/books/:bookId',
func: getBook
})

Path params extracted

:bookId becomes a typed input field automatically

Errors mapped to status codes

Throw NotFoundError → 404, UnauthorizedError → 401

OpenAPI generated

Every wired route appears in your OpenAPI spec for free

Data Flow

Everything merges into one typed input

Path params, query strings, and request bodies combine into a single object your function receives.

Path Params

:bookId → "42"

Query String

?format=pdf

Request Body

{ title: "..." }

Merged Input

{ bookId, format, title }
books.wiring.ts
wireHTTP({
method: 'post',
route: '/books/:bookId',
func: updateBook
})

// POST /books/42?format=pdf
// Body: { title: "New Title" }
//
// → updateBook receives:
// { bookId: "42", format: "pdf", title: "New Title" }

Same key in multiple sources with different values? Pikku throws a validation error — no silent overwrites.

Auth & Permissions

Auth and permissions, everywhere

Every HTTP route inherits Pikku's auth system. Override per-route or apply globally.

Per-route auth control

Set auth: false on public routes. Everything else requires a valid session by default.

Permission guards

Attach permission checks to individual routes. Pikku rejects unauthorized requests before your function runs.

Global policies

Use addHTTPPermission to apply rules to entire path prefixes — every route under /admin requires admin access.

books.wiring.ts
// Public route — no auth required
wireHTTP({
method: 'get',
route: '/books',
func: listBooks,
auth: false
})

// Protected with permissions
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
permissions: {
admin: isAdmin
}
})

// Global route-level permission
addHTTPPermission(
'/admin/*',
{ admin: isAdmin }
)

Middleware

Hooks at every level

Apply middleware globally, by path prefix, or on individual routes. They run in onion order — outer middleware wraps inner.

Global

*

addHTTPMiddleware('*', [...]) — runs on every HTTP request. CORS, logging, auth.

Prefix-based

/prefix

addHTTPMiddleware('/api/*', [...]) — scoped to a path prefix. Rate limiting, admin guards.

Per-route

route

middleware: [...] on a single wireHTTP call. Audit trails, special validation.

Built-in middleware: cors, authBearer, authCookie, authAPIKey — all from @pikku/core/middleware.

middleware.ts
import { cors, authBearer } from '@pikku/core/middleware'

// Global: CORS + bearer auth on every route
addHTTPMiddleware('*', [
cors({ origin: 'https://app.example.com', credentials: true }),
authBearer()
])

// Prefix: rate limiting on API routes only
addHTTPMiddleware('/api/*', [
rateLimit({ maxRequests: 100, windowMs: 60_000 })
])

// Per-route: audit logging on a single wire
wireHTTP({
method: 'delete',
route: '/books/:bookId',
func: deleteBook,
middleware: [auditLog]
})

Route Groups

Organize routes with defineHTTPRoutes

Group related routes into contracts. Compose them with shared base paths, middleware, and auth settings — then wire them all at once.

http.wiring.tswiring.ts
import { defineHTTPRoutes, wireHTTPRoutes } from '.pikku/pikku-types.gen.js'

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 },
},
})

// Compose everything under /api/v1
wireHTTPRoutes({
basePath: '/api/v1',
middleware: [cors()],
routes: {
books: booksRoutes,
todos: todosRoutes,
},
})

Config cascades down

basePath, tags, and middleware from the group apply to every route inside it

Routes can override

Set auth: false on a single route even if the group requires auth

Compose contracts

Define route groups in separate files, import and compose them in one wireHTTPRoutes call

Type-Safe Client

Call your API with full IntelliSense

Pikku generates a typed fetch client from your HTTP wirings. Every route, every input, every return type — autocompleted.

client.tsauto-generated types
import { pikkuFetch } from '.pikku/pikku-fetch.gen.js'

pikkuFetch.setServerUrl('http://localhost:4002')

// Fully typed — route, input, and output are inferred
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'
})

// Auth: set a JWT and all subsequent requests include it
pikkuFetch.setAuthorizationJWT(token)

const deleted = await pikkuFetch.delete('/api/v1/books/:bookId', {
bookId: created.bookId
})

Generated from your wirings

Run npx @pikku/cli fetch and get a PikkuFetch class with typed overloads for every HTTP route you've wired.

Auth built in

setAuthorizationJWT(), setAPIKey() — set once, included on every request.

Works everywhere

Built on the Fetch API — works in the browser, Node, Deno, Next.js server components, or anywhere that has fetch.

Server-Sent Events

When you need streaming, just add sse: true

The same route serves both regular HTTP clients and SSE clients. Non-SSE clients get a JSON response, SSE clients get a stream.

Regular HTTP Client200 OK
// Instant JSON response
{
  "todos": [
    { "id": 1, "text": "Buy milk" },
    { "id": 2, "text": "Write docs" },
    { "id": 3, "text": "Ship feature" },
    { "id": 4, "text": "Deploy app" }
  ]
}
SSE Clientstreaming
// Progressive streaming
data: { "todo": { "id": 1, "text": "Buy milk" } }
data: { "todo": { "id": 2, "text": "Write docs" } }
data: { "todo": { "id": 3, "text": "Ship feature" } }
data: { "todo": { "id": 4, "text": "Deploy app" } }
todos.wiring.tssse: true
wireHTTP({
method: 'get',
route: '/todos',
func: getTodos,
sse: true // ← that's it
})
getTodos.func.tsfunc.ts
const getTodos = pikkuFunc({
title: 'Get Todos',
func: async ({ db, channel }, {}) => {
const todos = await db.getTodos()

// If client supports SSE, stream them
if (channel) {
for (const todo of todos) {
channel.send({ todo })
await sleep(100)
}
return
}

// Otherwise, return the full list
return { todos }
}
})

Start wiring HTTP in 5 minutes

One command to scaffold a project with HTTP wiring already configured.

$ npm create pikku@latest

MIT Licensed  ·  Works with Express, Fastify, Lambda & Cloudflare