Event Store logo

Menu

Documentation

Show Table of Contents

Writing to a Stream

Writing to a stream over HTTP is simply a POST to the resource of the stream. If the stream does not exist then the stream will be implicitly created.

Single Event

Writing a single event has changed from Event Store 2.X.X to Event Store 3.X.X. In Event Store 3.X.X a post of application/json assumes the post data to be the actual event. When you need to write multiple events, use application/vnd.eventstore.events+json instead.

Writing an event to a stream is quite simple and can be done from any client that supports HTTP. To write an event simply POST it to the head URI of the stream. This process can be seen:

myevent.txt

{
  "something" : "has data"
}
ouro@ouroboros:~$ curl -i -d@/home/greg/myevent.txt "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json" -H "ES-EventType: SomeEvent" -H "ES-EventId: C322E299-CB73-4B47-97C5-5054F920746E"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/1
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 20:59:22 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

The event will be available in the stream. Some clients however may not be able to generate a GUID (or may not want to generate a GUID) for the ID. This ID is needed for idempotence purposes but can also be generated by the server. If we were to leave off the ES-EventId header a slightly different behaviour would be observed.

ouro@ouroboros:~$ curl -i -d@/home/ouro/myevent.json "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json" -H "ES-EventType: SomeEvent"
HTTP/1.1 301 FOUND
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/incoming/ad1c1288-0d61-4995-88b2-06c57a42495b
Content-Type: ; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:01:29 GMT
Content-Length: 28
Keep-Alive: timeout=15,max=100

In this case the Event Store has replied with a 301 redirect. The location points to another URI that the event can be posted to. This new URI that gets passed back will be idempotent for posting to even without an event ID.

ouro@ouroboros:$ curl -i -d @/home/ouro/myevent.json "http://127.0.0.1:2113/streams/newstream/incoming/ad1c1288-0d61-4995-88b2-06c57a42495b" -H "Content-Type: application/json" -H "ES-EventType: SomeEvent"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:15:33 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

It is generally recommended to include an event ID if possible as it will result in fewer round trips between the client and the server.

When posting to either the stream or to the returned redirect it is required that clients include the EventType header. If you forget to include the header you will be given an error.

ouro@ouroboros:~$ curl -i -d @/home/greg/myevent.json "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json"
HTTP/1.1 400 Must include an event type with the request either in body or as ES-EventType header.
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Content-Type:
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:05:45 GMT
Content-Length: 0
Connection: close

Event Store Events Media Type

The event store also supports a custom media type for posting events application/vnd.eventstore.events (+json/+xml). This format allows for a few things that posting events as above does not. As example it allows you to post multiple events in a single batch. If you want to post multiple events in a transactional batch you must use the custom media type.

simple-event.txt

[
  {
    "eventId": "fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e4",
    "eventType": "event-type",
    "data": { "a": "1" }
  }
]

or for XML

<Events>
  <Event>
    <EventId>fbf4a1a1-b4a3-4dfe-a01f-ec52c34e16e4</EventId>
    <EventType>event-type</EventType>
    <Data>
      <MyEvent>
        <Something>1</Something>
      </MyEvent>
    </Data>
  </Event>
</Events>

The data can be represented by the following jschema (note that eventId must be a UUID).

[
    {
	  "eventId"    : "string",
      "eventType"  : "string",
      "data"       : "object",
      "metadata"   : "object"
    }
]

Create

Then posting this data to the /streams/newstream resource will result in the stream being created.

ouro@es:~/src/ES.wiki$ curl -i -d @test.js "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/vnd.eventstore.events+json"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:17:59 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

Appending Events

If you edit the Message ID GUID and post it again it will append to the stream.

o@es:~/src/ES.wiki$ curl -i -d @test.js "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/vnd.eventstore.events+json"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/1
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:32:18 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

Data only events

Version 3.7.0 adds support for the application/octet-stream content type to support data only events. When creating these events, you will need to provide the ES-EventType and ES-EventId headers and cannot have metadata associated with the Event.

o@es:~/src/ES.wiki$ curl -i -d "SGVsbG8gV29ybGQ=" "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/octet-stream" -H "ES-EventType:rawDataType" -H "ES-EventId:eeccf3ce-4f54-409d-8870-b35dd836cca6"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-Forwarded-Host, X-PINGOTHER, Authorization, ES-LongPoll, ES-ExpectedVersion, ES-EventId, ES-EventType, ES-RequiresMaster, ES-HardDelete, ES-ResolveLinkTo, ES-ExpectedVersion
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 27 Jun 2016 13:15:27 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

Expected Version

The expected version header is normally a number representing the version of the stream you read from. As an example if you read from the stream and it was at version 5 then you expect it to be at version 5. This can allow for optimistic locking when having multiple things reading/writing to streams. If your expected version is not the current version you will receive a HTTP status code of 400.

See Idempotency section below, if you post the same event twice it will be idempotent and will not give a version error.

o@es:~/src/ES.wiki$ curl -i -d @test.js "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json" -H "ES-ExpectedVersion: 3"
HTTP/1.1 400 Wrong expected EventNumber
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Content-Type: text/plain; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Fri, 28 Jun 2013 12:33:30 GMT
Content-Length: 0
Connection: close

There are also some special values you can put into the expected version header.

  • -2 states that this write should never conflict with anything and should always succeed.
  • -1 states that the stream should not exist at the time of the writing (this write will create it)
  • 0 states that the stream should exist but should be empty

Batch Writes

You can include more than one write in a single post. To do this you place multiple events inside of the array representing the events (and metadata if you wish to place metadata on your events).

This can be seen in the following post body that would insert two events.

[
  {
    "eventId": "fbf4b1a1-b4a3-4dfe-a01f-ec52c34e16e4",
    "eventType": "event-type",
    "data": {

      "a": "1"
    }
  },
  {
    "eventId": "0f9fad5b-d9cb-469f-a165-70867728951e",
    "eventType": "event-type",
    "data": {

      "a": "1"
    }
  }
]

When you write multiple events in a single post, they are treated transactionally. All events will be written together or all will fail.

Idempotency

Appends to streams are idempotent based upon the EventId assigned in your post. If I were to re-run the last cURL command it will return the same value again.

o@es:~$ curl -i -d @simple-event.txt "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: DELETE, GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER
Access-Control-Allow-Origin: *
Location: http://127.0.0.1:2113/streams/newstream/2
Content-Type: ; charset: utf-8
Server: Mono-HTTPAPI/1.0
Date: Wed, 03 Apr 2013 15:21:53 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

This is a very important behaviour as this is how error handling works. If you get a timeout, broken connection, no answer, etc from your HTTP POST then your job is to retry the post. You must also keep the same uuid that you assigned to the event in the first post.

If you are using expected version with your message the event store is 100% idempotent. If you are using Any as your expected version the Event Store will do its best to keep things idempotent but cannot always assure that everything is fully idempotent and you will end up in atleast-once messaging. This is discussed further in the idempotency document.

This idempotency also applies to the URIs generated by the server if you post a body as an event without the ES-EventId header associated with the request.

 ouro@ouroboros:$ curl -i -d @/home/ouro/myevent.json "http://127.0.0.1:2113/streams/newstream" -H "Content-Type:application/json" -H "ES-EventType: SomeEvent"
HTTP/1.1 301 FOUND
Access-Control-Allow-Methods: POST, DELETE, GET, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/incoming/c7248fc1-3db4-42c1-96aa-a071c92649d1
Content-Type: ; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:11:59 GMT
Content-Length: 28
Keep-Alive: timeout=15,max=100

We can then post multiple times to the generated redirect URI and the requests will be made idempotent for us.

ouro@ourobors:$ curl -i -d @/home/ouro/myevent.json "http://127.0.0.1:2113/streams/newstream/incoming/c7248fc1-3db4-42c1-96aa-a071c92649d1" -H "Content-Type: application/json" -H "ES-EventType: SomeEvent"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:14:28 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

If you retry the post you will receive

ouro@ouroboros:$ curl -i -d @/home/ouro/myevent.json "http://127.0.0.1:2113/streams/newstream/incoming/c7248fc1-3db4-42c1-96aa-a071c92649d1" -H "Content-Type: application/json" -H "ES-EventType: SomeEvent"
HTTP/1.1 201 Created
Access-Control-Allow-Methods: GET, POST, OPTIONS
Access-Control-Allow-Headers: Content-Type, X-Requested-With, X-PINGOTHER, Authorization, ES-LongPoll
Access-Control-Allow-Origin: *
Access-Control-Expose-Headers: Location, ES-Position
Location: http://127.0.0.1:2113/streams/newstream/0
Content-Type: text/plain; charset=utf-8
Server: Mono-HTTPAPI/1.0
Date: Mon, 21 Apr 2014 21:15:33 GMT
Content-Length: 0
Keep-Alive: timeout=15,max=100

Event Store docs are hosted on GitHub. The repository is public and it’s open to issues and pull requests. Contributions, corrections and feedback are all welcome.