Learning Paths & Tutorials

Getting Started

15 minutes from zero to your first service.

This tutorial takes you from an empty directory to a running PURISTA service with a command and a subscription. By the end, you will understand the core mental model: services, commands, subscriptions, and the event bridge.

What you will build

flowchart TB
    B["Browser / Client"] -->|POST /api/sign-up| H["HTTP Server"]
    H -->|command| EB["Event Bridge"]
    EB -->|userSignUp| US["User Service"]
    US -->|success event| EB
    EB -->|newUserRegistered| ES["Email Service"]
    ES -->|subscription| SEND["sendWelcomeEmail"]
ArtifactWhat it doesPattern
userSignUp commandRegisters a user and returns a user IDRequest/response
sendWelcomeEmail subscriptionSends an email when a user is createdEvent-driven

Prerequisites

npx skills add puristajs/purista --skill purista

Step 1: Scaffold the project

Run the PURISTA CLI:

npm create purista@latest

Or for non-interactive setup:

purista init my-app \
  --runtime node \
  --event-bridge default \
  --webserver \
  --linter biome \
  --type module \
  --non-interactive \
  --defaults

This creates:

my-app/
├── src/
│   ├── service/
│   │   └── ping/
│   │       └── v1/
│   │           ├── pingV1ServiceBuilder.ts
│   │           ├── pingV1Service.ts
│   │           └── command/
│   │               └── ping/
│   ├── eventbridge.ts
│   ├── http.ts
│   └── index.ts
├── purista.json
└── package.json

Step 2: Create a service

cd my-app
purista add service

Name it user. The CLI generates:

src/service/user/v1/
├── userServiceV1ServiceBuilder.ts
├── userServiceV1Service.ts
└── userServiceV1Service.test.ts

Step 3: Add a command

purista add command

Select the user service. Name it signUp.

The CLI generates:

src/service/user/v1/command/signUp/
├── schema.ts
├── types.ts
├── signUpCommandBuilder.ts
└── signUpCommandBuilder.test.ts

Edit signUpCommandBuilder.ts:

import { z } from 'zod'
import { userServiceV1ServiceBuilder } from '../../userServiceV1ServiceBuilder.js'

const inputPayloadSchema = z.object({
  email: z.string().email(),
  password: z.string().min(8),
})

const outputSchema = z.object({
  userId: z.string(),
})

export const signUpCommandBuilder = userServiceV1ServiceBuilder
  .getCommandBuilder('signUp', 'Register a new user', 'newUserRegistered')
  .addPayloadSchema(inputPayloadSchema)
  .addOutputSchema(outputSchema)
  .setCommandFunction(async function (context, payload) {
    const userId = crypto.randomUUID()
    context.logger.info({ userId }, 'User created')
    return { userId }
  })

Step 4: Add a subscription

purista add subscription

Select the user service. Name it sendWelcomeEmail.

Edit the subscription:

import { z } from 'zod'
import { userServiceV1ServiceBuilder } from '../../userServiceV1ServiceBuilder.js'

export const sendWelcomeEmailSubscriptionBuilder = userServiceV1ServiceBuilder
  .getSubscriptionBuilder('sendWelcomeEmail', 'Send welcome email on new user')
  .subscribeToEvent('newUserRegistered')
  .addPayloadSchema(z.object({ userId: z.string() }))
  .setSubscriptionFunction(async function (context, payload) {
    context.logger.info({ userId: payload.userId }, 'Welcome email sent')
    return { status: 'ack' }
  })

Step 5: Wire up and run

Edit src/service/user/v1/userServiceV1Service.ts:

import { userServiceV1ServiceBuilder } from './userServiceV1ServiceBuilder.js'
import { signUpCommandBuilder } from './command/signUp/signUpCommandBuilder.js'
import { sendWelcomeEmailSubscriptionBuilder } from './subscription/sendWelcomeEmail/sendWelcomeEmailSubscriptionBuilder.js'

export const userServiceV1Service = userServiceV1ServiceBuilder
  .addCommandDefinition(signUpCommandBuilder.getDefinition())
  .addSubscriptionDefinition(sendWelcomeEmailSubscriptionBuilder.getDefinition())

Edit src/index.ts:

import { DefaultEventBridge } from '@purista/core'
import { userServiceV1Service } from './service/user/v1/userServiceV1Service.js'

const eventBridge = new DefaultEventBridge()
await eventBridge.start()

const userService = await userServiceV1Service.getInstance(eventBridge)
await userService.start()

console.log('Service running. Press Ctrl+C to stop.')

Run:

npm run dev

Test the command:

curl -X POST http://localhost:3000/api/v1/sign-up \
  -H "content-type: application/json" \
  -d '{"email": "test@example.com", "password": "secure123"}'

What you learned

  • Services are domain containers for commands and subscriptions
  • Commands are request/response handlers with typed schemas
  • Subscriptions react to events without blocking the emitter
  • The event bridge routes messages between services
  • The CLI scaffolds boilerplate so you focus on business logic

Next steps

Related

Read Next
Philosophy

from Mental Model & Philosophy