Skip to content

Reading Events

This guide shows how to read events from EventSourcingDB using its HTTP API. You'll learn how to read either all or a desired subset of the previously stored events.

Reading From a Single Subject

Once you've written events, the next step is typically to read them back. The simplest approach is to read the events for a single subject. All you need is the subject name, e.g. /books/42.

To read events, send an HTTP request using POST to the /api/v1/read-events endpoint.

Why POST for Reading?

You might wonder why POST is used for reading instead of GET. The main reason is that POST allows parameters to be passed in the request body, which is much more convenient than encoding everything in the query string – especially when parameters become complex.

For this reason, almost all API routes of EventSourcingDB use POST, regardless of whether they perform actions or retrieve data.

To read all events for the subject /books/42, run the following command:

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\",
    \"options\": {
      \"recursive\": false
    }
  }" \
  http://localhost:3000/api/v1/read-events

If everything worked as expected, the server will respond with HTTP status code 200 OK:

HTTP/1.1 200 OK

The response body will then contain a list of all stored events for that subject. If you've followed the steps in Writing Events, you should see the following two events:

{
  "type": "event",
  "payload": {
    "specversion": "1.0",
    "id": "0",
    "time": "...",
    "source": "https://library.eventsourcingdb.io",
    "subject": "/books/42",
    "type": "io.eventsourcingdb.library.book-acquired",
    "datacontenttype": "application/json",
    "data": {
      "author": "Arthur C. Clarke",
      "isbn": "978-0756906788",
      "title": "2001 – A Space Odyssey"
    },
    "predecessorhash": "...",
    "hash": "...",
    "signature": null
  }
}
{
  "type": "event",
  "payload": {
    "specversion": "1.0",
    "id": "1",
    "time": "...",
    "source": "https://library.eventsourcingdb.io",
    "subject": "/books/42",
    "type": "io.eventsourcingdb.library.book-borrowed",
    "datacontenttype": "application/json",
    "data": {
      "borrowedBy": "/readers/23",
      "borrowedUntil": "..."
    },
    "predecessorhash": "...",
    "hash": "...",
    "signature": null
  }
}

In reality, the output will look slightly different – the events are returned as compact NDJSON, with each event occupying a single line.

Why NDJSON Instead of JSON?

Note that the response is formatted as NDJSON, which is not valid JSON.

According to the JSON standard, only one top-level element is allowed. If multiple items need to be returned – as is the case here – they would have to be wrapped in an array. However, this would be impractical for stream-based processing, since a client would need to buffer the entire array before it could begin parsing.

That's why EventSourcingDB uses the NDJSON format whenever multiple items need to be returned: each item appears on its own line, separated by newline characters. This allows for efficient streaming and incremental parsing.

If you specify a non-existent subject, you won't get an error. Instead, you will get an HTTP status code 200 OK as well, but with an empty response body.

Reading From Multiple Subjects

Suppose you want to read not just the events for a single book, but all events for all books – in their original (interleaved) order. To do that, specify the parent subject /books.

However, just changing the subject alone won't work, because no events have been written to /books itself.

To include the sub-subjects (e.g., /books/42), you must explicitly set the recursive option to true:

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books\",
    \"options\": {
      \"recursive\": true
    }
  }" \
  http://localhost:3000/api/v1/read-events

Reading All Events

You can use the same mechanism to read all events stored in EventSourcingDB, in chronological order. Simply use the root subject / and ensure to set recursive to true, because all subjects form a hierarchy starting from /:

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/\",
    \"options\": {
      \"recursive\": true
    }
  }" \
  http://localhost:3000/api/v1/read-events

Lots of Events

Before doing this in a production environment, keep in mind that it may result in a large number of events being read.

From a database perspective, this is not a problem – EventSourcingDB is optimized for reading large event streams. But you may generate a significant amount of network traffic. Use this option responsibly.

Fine-Tuning Reads

In addition to the required recursive parameter, there are several optional parameters you can use to control how events are read.

Reading in Reverse Order

You can control the order in which events are read using the order parameter. The default is chronological. However, sometimes you may want to read in reverse – starting with the most recent events. This can be useful e.g. when displaying a history view where the most recent changes should be shown first.

To do this, set order to antichronological:

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\",
    \"options\": {
      \"recursive\": false,
      \"order\": \"antichronological\"
    }
  }" \
  http://localhost:3000/api/v1/read-events

Reading a Specific Range

By default, all events for the given subject are read. But sometimes you may want to restrict the range – for example, when implementing pagination.

You can use the lowerBound and upperBound parameters (individually or together) to limit the result. Both bounds can be configured to be inclusive or exclusive:

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\",
    \"options\": {
      \"recursive\": false,
      \"lowerBound\": {
        \"id\": \"2001\",
        \"type\": \"inclusive\"
      },
      \"upperBound\": {
        \"id\": \"3001\",
        \"type\": \"exclusive\"
      }
    }
  }" \
  http://localhost:3000/api/v1/read-events

IDs Are Strings

Even if IDs look numeric, you must pass them as strings. This is required by the CloudEvents specification, which defines event IDs to be strings.

Reading From the Last Event of a Given Type

Sometimes it's useful to read all events that occurred after a certain type of event – for example, everything that happened since a book was last borrowed.

You can achieve this using the fromLatestEvent option (which starts reading from the most recent event of a given type):

curl \
  -i \
  -X POST \
  -H "authorization: Bearer <API_TOKEN>" \
  -H "content-type: application/json" \
  -d "{
    \"subject\": \"/books/42\",
    \"options\": {
      \"recursive\": false,
      \"fromLatestEvent\": {
        \"subject\": \"/books/42\",
        \"type\": \"io.eventsourcingdb.library.book-borrowed\",
        \"ifEventIsMissing\": \"read-everything\"
      }
    }
  }" \
  http://localhost:3000/api/v1/read-events

Reading from the latest event is especially useful for performance optimization when combined with Snapshots.

A key question here is: what happens if no matching event of that type is found? That behavior is controlled using the ifEventIsMissing option:

  • If set to read-everything, all events for the given subject will be read.
  • If set to read-nothing, the response will be empty.

Incompatible Parameters

You cannot combine fromLatestEvent with lowerBound.

Also, reading from the latest event of a given type is only supported in chronological order – not antichronological.

Your Turn

Try writing some events about books (such as book acquired and book borrowed) and readers (such as reader applied and reader accepted). Then read these events in different ways.

Here are some ideas:

  • Read all events for a specific book in anti-chronological order.
  • Read all events for all books and all readers using a single request.
  • Read all events for a specific book since it was last borrowed.

You can also extend the model – for example, by introducing another sub-subject for book pages, such as /books/42/pages/23, to record events like a page being damaged.

Then try the following:

  • Read all events for a book including its pages.
  • Read all events for a specific book and its pages since the book was last borrowed.