Getting Started
Get up and running with j17 in under five minutes.
Prerequisites
You'll need:
- A j17 account (sign up at j17.dev)
- An API key for your instance
- curl or any HTTP client
1. Create your spec
Define your aggregate types and events in JSON:
{
"aggregate_types": {
"user": {
"events": {
"was_created": {
"schema": {
"type": "object",
"properties": {
"name": { "type": "string" },
"email": { "type": "string", "format": "email" }
},
"required": ["name", "email"]
},
"handler": [
{ "set": { "target": "", "value": "$.data" } },
{ "set": { "target": "created_at", "value": "$.metadata.timestamp" } }
]
},
"had_email_updated": {
"schema": {
"type": "object",
"properties": {
"email": { "type": "string", "format": "email" }
},
"required": ["email"]
},
"handler": [
{ "merge": { "target": "", "value": "$.data" } }
]
}
}
}
},
"agent_types": ["user", "admin"]
}
The spec defines: - aggregate_types: Kinds of entities in your system - events: What can happen to those entities - handlers: How events transform state (using the Tick language) - agent_types: Who can trigger events
Upload via the dashboard or Admin API.
2. Post an event
curl -X POST https://myapp.j17.dev/user/550e8400-e29b-41d4-a716-446655440000/was_created \
-H "Authorization: Bearer $J17_API_KEY" \
-H "Content-Type: application/json" \
-d '{
"data": { "name": "Alice", "email": "alice@example.com" },
"metadata": {
"actor": { "type": "admin", "id": "550e8400-e29b-41d4-a716-446655440001" }
}
}'
Every event needs:
- URL path: /{aggregate_type}/{aggregate_id}/{event_type}
- data: The event payload (validated against your schema)
- metadata.actor: Who performed the action
3. Query the aggregate
curl https://myapp.j17.dev/user/550e8400-e29b-41d4-a716-446655440000 \
-H "Authorization: Bearer $J17_API_KEY"
Response:
{
"ok": true,
"state": {
"name": "Alice",
"email": "alice@example.com",
"created_at": 1705312800
},
"length": 1
}
That's it. You're event sourcing.
What just happened?
- You defined the shape of your data (the spec)
- You recorded a fact (the event)
- j17 computed current state by applying the handler
The event is stored forever. The aggregate is derived on demand. This is the core trade-off: you give up simple SQL queries, you get immutable history.
Next steps
- Learn the core concepts: events, aggregates, handlers
- Build something real with AI-assisted development
- Explore the full API reference
- Read the spec reference to model complex domains
Common gotchas
Events are immutable. There's no DELETE. To "undo," write a compensating event.
IDs matter. Use UUIDv4 for most things. For human-readable codes (booking references, promo codes), use 9-character humane codes.
Actors are required. Every event must include metadata.actor with type and id. This isn't bureaucracy -- it's your audit trail.