Skip to content

Quick Start

In this guide, you’ll:

  1. Start an EvidentSource server locally
  2. Create a database and store your first events
  3. Query events using the TypeScript client or REST API
  4. Execute a State Change and query a State View

New to event sourcing? Read What is EvidentSource? first to understand the programming model and benefits.


This guide will help you get EvidentSource up and running quickly using the in-memory adapter for local development.

  • Docker and Docker Compose (see Installation Guide)
  • Node.js 20+ and pnpm (for TypeScript examples)

Clone the SDK repository and start the development server:

Terminal window
# Clone the repository
git clone https://github.com/evidentsystems/evidentsource-sdks
cd evidentsource-sdks
# Start the server with docker compose
docker compose -f docker-compose.evidentsource.yml up -d

The server will start with:

  • HTTP API at http://localhost:3000
  • gRPC API at localhost:50051
  • Web UI at http://localhost:3000
Terminal window
curl http://localhost:3000/health
# Returns: OK
Terminal window
curl -X POST http://localhost:3000/api/v1/databases/mydb

First, set up a project:

Terminal window
mkdir quickstart && cd quickstart
pnpm init
pnpm add github:evidentsystems/evidentsource-sdks#main:typescript/packages/evidentsource-core
pnpm add github:evidentsystems/evidentsource-sdks#main:typescript/packages/evidentsource-client

Create index.ts:

import {
EvidentSource,
databaseName,
DatabaseError,
} from "@evidentsource/client";
async function main() {
// Connect to the server
const es = EvidentSource.connect("http://localhost:50051");
// Create a database
const dbName = databaseName("mydb");
try {
await es.createDatabase(dbName);
console.log("Database created!");
} catch (e) {
if (e instanceof DatabaseError && e.code === "ALREADY_EXISTS") {
console.log("Database already exists");
} else {
throw e;
}
}
es.close();
}
main().catch(console.error);

Events are stored via transactions with optimistic concurrency control:

import {
EvidentSource,
databaseName,
streamName,
eventType,
subject,
prospectiveEvent,
} from "@evidentsource/client";
import { v4 as uuidv4 } from "uuid";
async function main() {
const es = EvidentSource.connect("http://localhost:50051");
const dbName = databaseName("mydb");
const conn = await es.connectDatabase(dbName);
// Create events
const events = [
prospectiveEvent({
id: uuidv4(),
stream: streamName("users"),
type: eventType("com.example.user.created"),
subject: subject("user-123"),
data: JSON.stringify({
username: "alice",
email: "alice@example.com",
}),
dataContentType: "application/json",
}),
];
// Commit the transaction
const db = await conn.transact(events);
console.log(`Events stored at revision: ${db.revision}`);
await conn.close();
es.close();
}
main().catch(console.error);

Get all events from a specific stream:

import { EventSelector, streamName } from "@evidentsource/core";
// Query events from the "users" stream
const events = await db.queryEvents({
selector: EventSelector.streamEquals(streamName("users")),
limit: 100,
});
for (const event of events) {
console.log(`${event.type}: ${event.data}`);
}

Get all events for a specific entity:

import { EventSelector, subject } from "@evidentsource/core";
// Query all events for user-123
const userEvents = await db.queryEvents({
selector: EventSelector.subjectEquals(subject("user-123")),
});

State Changes are WebAssembly components that handle commands and emit events. Once deployed to the server:

import {
stateChangeName,
jsonCommandRequest,
} from "@evidentsource/client";
// Execute a state change
const db = await conn.executeStateChange(
stateChangeName("create-user"),
1, // version
jsonCommandRequest({
userId: "user-456",
username: "bob",
email: "bob@example.com",
})
);
console.log(`State change executed, new revision: ${db.revision}`);

State Views are WebAssembly components that materialize read models from events:

import {
stateViewName,
stateViewContentAsJson,
stringAttribute,
} from "@evidentsource/client";
// Query a state view
const params = new Map([["user_id", stringAttribute("user-123")]]);
const view = await db.viewStateWithParams(
stateViewName("user-profile"),
1, // version
params
);
if (view) {
interface UserProfile {
id: string;
username: string;
email: string;
}
const profile = stateViewContentAsJson<UserProfile>(view);
console.log(`User: ${profile.username} (${profile.email})`);
}

All operations are also available via REST:

Terminal window
# Create a database
curl -X POST http://localhost:3000/api/v1/databases/mydb
# Execute a state change
curl -X POST http://localhost:3000/api/v1/databases/mydb/state-changes/create-user/versions/1 \
-H "Content-Type: application/json" \
-d '{"userId": "user-789", "username": "carol", "email": "carol@example.com"}'
# Query a state view
curl "http://localhost:3000/api/v1/databases/mydb/state-views/user-profile/versions/1?user_id=user-123"

State Changes and State Views are WebAssembly components built with our SDKs:

Complete examples are available in the SDK repository:

For production deployment, see our AWS Deployment Guide.

If port 3000 is taken, modify the docker-compose environment:

environment:
EVIDENT_HTTP_PORT: "3001"
ports:
- "3001:3001"

Ensure the server is running:

Terminal window
# Check server health
curl http://localhost:3000/health
# Check container status
docker compose -f docker-compose.evidentsource.yml ps
Terminal window
docker compose -f docker-compose.evidentsource.yml logs -f evidentsource