pikku-services

$npx mdskill add pikkujs/pikku/pikku-services

Configure dependency injection and service layers with Pikku.

  • Sets up singleton and per-request service instances efficiently.
  • Integrates with pikkuServices, pikkuWireServices, and built-in services.
  • Triggers when users request service configuration or dependency patterns.
  • Delivers factory function code for custom or standard service creation.

SKILL.md

.github/skills/pikku-servicesView on GitHub ↗
---
name: pikku-services
description: 'Use when setting up dependency injection, creating custom services, or configuring the service layer in a Pikku app. Covers pikkuServices (singleton), pikkuWireServices (per-request), service typing, built-in services, and tree-shaking.
TRIGGER when: code uses pikkuServices/pikkuWireServices, user asks about services.ts, dependency injection, service factories, or built-in services (ConsoleLogger, JoseJWTService).
DO NOT TRIGGER when: user asks about auth middleware (use pikku-security) or secrets/variables (use pikku-config).'
---

# Pikku Services (Dependency Injection)

Pikku uses factory functions for dependency injection. Singleton services are created once at startup. Wire services are created fresh per request/job/command.

## Before You Start

```bash
pikku info functions --verbose   # See which services existing functions use
pikku info tags --verbose        # Understand project organization
```

See `pikku-concepts` for the core mental model.

## API Reference

### `pikkuServices(factory)`

Create singleton services — instantiated once at server startup.

```typescript
import { pikkuServices } from '#pikku'

const createSingletonServices = pikkuServices(
  async (config, existingServices?) => {
    // config: your CoreConfig object
    // existingServices: optional, for chaining factories
    return {
      config,
      logger: Logger,
      jwt: JWTService,
      database: DatabasePool,
      // ...any custom services
    }
  }
)
```

### `pikkuWireServices(factory)`

Create per-request services — fresh instance for each HTTP request, queue job, CLI command, etc.

```typescript
import { pikkuWireServices } from '#pikku'

const createWireServices = pikkuWireServices(
  async (singletonServices, wire) => {
    // singletonServices: all singleton services
    // wire: transport context (session, channel, etc.)
    // Pikku merges these with singleton services automatically
    return {
      userSession: UserSessionService,
      dbTransaction: DatabaseTransaction,
    }
  }
)
```

### Auto-Generated Service Manifest

After `npx pikku prebuild`, Pikku generates a manifest of which services are actually used:

```typescript
// .pikku/pikku-services.gen.ts (auto-generated)
export const requiredSingletonServices = {
  database: true, // used by getUser, deleteUser
  audit: true, // used by deleteUser
  cache: false, // not used by any wired function
  jwt: true, // used by auth middleware
} as const

export type RequiredSingletonServices = Pick<
  SingletonServices,
  'database' | 'audit' | 'jwt'
> &
  Partial<Omit<SingletonServices, 'database' | 'audit' | 'jwt'>>
```

## Usage Patterns

### Basic Singleton Services

```typescript
const createSingletonServices = pikkuServices(
  async (config, existingServices) => {
    const logger = new ConsoleLogger()
    const database = new DatabasePool(config.database)
    await database.connect()

    const jwt = new JoseJWTService(
      async () => [{ id: 'my-key', value: JWT_SECRET }],
      logger
    )

    return {
      config,
      logger,
      database,
      jwt,
      books: new BookService(),
    }
  }
)
```

### Per-Request Wire Services

```typescript
const createWireServices = pikkuWireServices(
  async (singletonServices, wire) => {
    return {
      userSession: createUserSessionService(wire),
      dbTransaction: new DatabaseTransaction(singletonServices.database),
    }
  }
)
```

### Using Services in Functions

Functions destructure services from the first parameter:

```typescript
const getUser = pikkuFunc({
  title: 'Get User',
  func: async ({ db, logger, jwt }, { userId }) => {
    logger.info('Fetching user', { userId })
    const user = await db.getUser(userId)
    return { user }
  },
})
```

### Dynamic Import Optimization

Use the generated manifest to conditionally import heavy dependencies:

```typescript
import { requiredSingletonServices } from '.pikku/pikku-services.gen.js'

const createSingletonServices = pikkuServices(async (config) => {
  const logger = new ConsoleLogger()

  let jwt: JWTService | undefined
  if (requiredSingletonServices.jwt) {
    const { JoseJWTService } = await import('@pikku/jose')
    jwt = new JoseJWTService(keys, logger)
  }

  let database: Database | undefined
  if (requiredSingletonServices.database) {
    database = await createDatabase(config.databaseUrl)
  }

  return { config, logger, jwt, database }
})
```

### Built-in Services

| Service                 | Package              | Purpose                     |
| ----------------------- | -------------------- | --------------------------- |
| `ConsoleLogger`         | `@pikku/core/services` | Console-based logging       |
| `JoseJWTService`        | `@pikku/jose`          | JWT sign/verify via jose    |
| `LocalSecretService`    | `@pikku/core/services` | Local development secrets   |
| `LocalVariablesService` | `@pikku/core/services` | Local environment variables |
| `PinoLogger`            | `@pikku/pino`          | Structured logging via Pino |

## Complete Example

```typescript
// services.ts
import { pikkuServices, pikkuWireServices } from '#pikku'
import { ConsoleLogger } from '@pikku/core/services'
import { JoseJWTService } from '@pikku/jose'

// Custom service
class TodoStore {
  private todos: Map<string, Todo> = new Map()

  async create(title: string, priority: string) {
    const todo = { id: crypto.randomUUID(), title, priority, completed: false }
    this.todos.set(todo.id, todo)
    return todo
  }

  async get(id: string) {
    return this.todos.get(id)
  }
  async list() {
    return [...this.todos.values()]
  }
  async delete(id: string) {
    this.todos.delete(id)
  }
}

export const createSingletonServices = pikkuServices(async (config) => {
  const logger = new ConsoleLogger()
  const jwt = new JoseJWTService(
    async () => [{ id: 'my-key', value: config.jwtSecret }],
    logger
  )

  return {
    config,
    logger,
    jwt,
    secrets: new LocalSecretService(),
    variables: new LocalVariablesService(),
    todoStore: new TodoStore(),
  }
})

export const createWireServices = pikkuWireServices(
  async (singletonServices, wire) => ({
    scopedLogger: new ScopedLogger(wire.session?.initial?.userId),
  })
)

// functions/todos.functions.ts — services are auto-injected
export const createTodo = pikkuFunc({
  title: 'Create Todo',
  func: async ({ todoStore, logger }, { title, priority }) => {
    const todo = await todoStore.create(title, priority)
    logger.info('Created todo', { id: todo.id })
    return { todo }
  },
})
```

More from pikkujs/pikku

SkillDescription
pikku-addon'Use when creating or consuming reusable function packages (addons) in Pikku. Covers wireAddon, addon(), pikkuAddonServices, pikkuAddonWireServices, addon package structure, and cross-project function sharing.
pikku-ai-agent'Use when building AI agents, chatbots, or LLM-powered assistants with Pikku. Covers pikkuAIAgent, tool registration, memory, streaming, and agent invocation.
pikku-ai-vercel'Use when setting up AI agent execution with the Vercel AI SDK in a Pikku app. Covers VercelAIAgentRunner for streaming and non-streaming AI agent steps.
pikku-ai-voice'Use when adding voice input (speech-to-text) or voice output (text-to-speech) to AI agents in a Pikku app. Covers voiceInput/voiceOutput middleware hooks and STT/TTS service interfaces.
pikku-auth-js'Use when integrating Auth.js (NextAuth) with a Pikku app. Covers createAuthHandler, createAuthRoutes, and Auth.js configuration.
pikku-aws'Use when setting up AWS services (S3, SQS, Secrets Manager) in a Pikku app. Covers S3Content for file storage, SQSQueueService for queues, and AWSSecrets for secret management.
pikku-backblaze'Use when setting up Backblaze B2 file storage in a Pikku app. Covers B2Content for file uploads, downloads, and signed URLs.
pikku-cli'Use when building CLI commands with Pikku. Covers wireCLI, pikkuCLICommand, subcommands, options, parameters, custom renderers, and nested command groups.
pikku-concepts'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.
pikku-config'Use when managing secrets, environment variables, config, or OAuth2 credentials in a Pikku app. Covers wireSecret, wireVariable, wireOAuth2Credential, and typed config access.