TypeScript Everywhere
End-to-End Type Safety
Complete type safety from function definitions to auto-generated clients, across all protocols and deployment targets.
The Problem: Type Safety Ends at the API Boundaryβ
Most backend frameworks don't provide end-to-end type safety:
- Manual type synchronization β Copy-paste types between backend and frontend, hoping they stay in sync
- Runtime-only validation β Errors surface in production, not during development
- Protocol-specific solutions β Type-safe HTTP clients don't help with WebSockets, queues, or other protocols
The result: Runtime errors, API contract drift, and wasted time debugging type mismatches between client and server.
Pikku's Solution: Types Flow Everywhereβ
With Pikku, types flow seamlessly from your backend functions to your clients, across all protocols.
1. Define Type-Safe Functionsβ
import { pikkuFunc } from '@pikku/core'
export const getUser = pikkuFunc<
{ userId: string },
{ id: string; name: string; email: string }
>({
func: async ({ database }, { userId }) => {
return await database.users.findById(userId)
},
permissions: { authenticated: true }
})
export const updateUser = pikkuFunc<
{ userId: string; name: string; email: string },
{ success: boolean }
>({
func: async ({ database }, data) => {
await database.users.update(data.userId, {
name: data.name,
email: data.email
})
return { success: true }
},
permissions: { owner: true }
})
2. Wire Functions to Any Protocolβ
The same function types work across HTTP, WebSockets, queues, scheduled tasks, RPC, MCP, and CLI:
// HTTP
wireHTTP({ method: 'get', route: '/users/:userId', func: getUser })
// WebSocket
wireChannel({
name: 'users',
onMessageWiring: {
getUser: { func: getUser }
}
})
// Queue
wireQueueWorker({ queue: 'user-updates', func: updateUser })
// Scheduled Task
wireScheduler({ cron: '0 * * * *', func: syncUsers })
// RPC (internal function calls)
const user = await rpc.invoke('getUser', { userId: '123' })
// ^? { id: string; name: string; email: string }
// MCP (AI agents)
wireMCPTool({
name: 'getUser',
description: 'Retrieve user by ID',
func: getUser
})
// CLI
wireCLI({
program: 'users',
commands: {
get: pikkuCLICommand({
parameters: '<userId>',
func: getUser
})
}
})
Every protocol gets the same type safety. Zero duplication.
3. Auto-Generated Type-Safe Clientsβ
Run npx pikku and Pikku generates fully typed clients for all your functions:
HTTP Clientβ
// Auto-generated HTTP client
import { PikkuFetch } from './.pikku/pikku-fetch.gen'
const client = new PikkuFetch({ serverUrl: 'https://api.example.com' })
// β
Full IntelliSense and type checking
const user = await client.get('/users/:userId', { userId: '123' })
// ^? { id: string; name: string; email: string }
// β TypeScript error: missing required fields
await client.patch('/users/:userId', { userId: '123' })
// ^^^^^^^^^^^^^^^^ Type error: missing 'name' and 'email'
// β
Correct usage
await client.patch('/users/:userId', {
userId: '123',
name: 'John Doe',
email: 'john@example.com'
})
RPC Clientβ
// Auto-generated RPC client for external calls
import { PikkuRPC } from './.pikku/pikku-rpc.gen'
const rpc = new PikkuRPC()
rpc.setServerUrl('https://api.example.com')
// β
Type-safe RPC calls
const user = await rpc.invoke('getUser', { userId: '123' })
// ^? { id: string; name: string; email: string }
await rpc.invoke('updateUser', {
userId: '123',
name: 'John Doe',
email: 'john@example.com'
})
WebSocket Clientβ
// Auto-generated WebSocket client
import { PikkuWebSocket } from './.pikku/pikku-websocket.gen'
const ws = new PikkuWebSocket<'users'>('wss://api.example.com', apiKey)
// Get a typed route
const route = ws.getRoute('action')
// β
Type-safe message sending
route.send('getUser', { userId: '123' })
// β
Type-safe message receiving
route.subscribe('userUpdated', (user) => {
// ^? { id: string; name: string; email: string }
console.log(user.name)
})
Queue Clientβ
// Auto-generated Queue client
import { PikkuQueues } from './.pikku/pikku-queues.gen'
const queues = new PikkuQueues()
// β
Type-safe queue job dispatch
await queues.addJob('processUser', { userId: '123' })
// ^? Type-checked against function input
await queues.addJob('sendEmail', {
to: 'user@example.com',
subject: 'Welcome',
body: 'Hello!'
})
Benefitsβ
β Catch Errors at Compile Timeβ
// β TypeScript catches this before it runs
await client.get('/users/:userId', { id: '123' })
// ^^^ Type error: expected 'userId', got 'id'
// β TypeScript catches this too
const user = await client.get('/users/:userId', { userId: 123 })
// ^^^ Type error: expected string, got number
β Auto-Complete Everywhereβ
Your editor knows exactly what data each function expects and returns:
- IntelliSense in your IDE
- Auto-complete for function parameters
- Type hints for return values
- Instant feedback on type errors
β Refactor with Confidenceβ
Change a function signature and TypeScript tells you everywhere that needs updating:
// Change function signature - remove 'email' field
export const getUser = pikkuFunc<
{ userId: string },
{ id: string; name: string } // Removed 'email'
>({
// ...implementation
})
// TypeScript immediately catches everywhere using the removed field
const user = await client.get('/users/:userId', { userId: '123' })
console.log(user.email)
// ^^^^^ Type error: Property 'email' does not exist
// Or in destructuring
const { id, name, email } = await client.get('/users/:userId', { userId: '123' })
// ^^^^^ Type error: Property 'email' does not exist
β API Contract Enforcementβ
Types become the single source of truth for your API contracts:
- Frontend and backend can't drift apart
- Breaking changes are caught immediately
- Documentation stays in sync (types generate OpenAPI schemas)
Real-World Impactβ
Before Pikkuβ
// Backend (Express)
app.get('/users/:userId', async (req, res) => {
const user = await database.users.findById(req.params.userId)
res.json(user)
})
// Frontend - NO TYPE SAFETY
const response = await fetch(`/users/${userId}`)
const user = await response.json() // 'any' type - hope for the best
console.log(user.name) // Runtime error if API changed
With Pikkuβ
// Backend (type-safe function)
export const getUser = pikkuFunc<{ userId: string }, User>({
func: async ({ database }, { userId }) => {
return await database.users.findById(userId)
}
})
// Frontend - FULL TYPE SAFETY
const user = await client.get('/users/:userId', { userId: '123' })
// ^? User - TypeScript knows the exact shape
console.log(user.name) // β
Compile-time verified
TypeScript + Automatic Runtime Validationβ
Pikku automatically generates runtime validation from your TypeScript types:
// Just define your TypeScript types
type CreateUserInput = {
name: string
email: string
age: number
}
export const createUser = pikkuFunc<
CreateUserInput,
{ id: string; success: boolean }
>({
func: async ({ database }, data) => {
// data is guaranteed to match CreateUserInput at runtime
const id = await database.users.create(data)
return { id, success: true }
}
})
How it works:
- Run
npx pikkuto generate JSON schemas from your TypeScript types - Pikku validates all incoming data against these schemas automatically
- If validation fails, the function isn't calledβan error is returned immediately
// β
Compile-time: TypeScript checks shape
// β
Runtime: Auto-generated schema validates data
await client.post('/users', {
name: 'John',
email: 'john@example.com',
age: 25
})
// β Runtime validation fails before function runs
await client.post('/users', {
name: 'John',
email: 'john@example.com'
// Missing 'age' - validation error (422 status)
})
// β Runtime validation fails for wrong type
await client.post('/users', {
name: 'John',
email: 'john@example.com',
age: '25' // String instead of number - validation error
})
No manual validation code needed. Just define TypeScript types, and Pikku handles the rest.
The Bottom Lineβ
Types aren't just documentation. They're guarantees.β
Pikku makes your entire backend type-safeβacross all protocols, all runtimes, all clients.
Catch errors before they reach production. Refactor with confidence. Ship faster.
Next Stepsβ
- Explore type-safe functions β Learn how to write type-safe Pikku functions
- Get started β Build your first type-safe backend
Get Started
Ready to try Pikku? Get up and running in 5 minutes.
Learn More
Dive deeper into why Pikku gives you the flexibility to succeed.
Questions or Feedback?
- π» GitHub
- π¬ Discussions
- π Documentation
- π¦ Twitter/X
- π¬ Discord