Namespaces

bones.http is a CQRS implementation built on yada. It offers authentication with Buddy and validation with clojure.spec.alpha. It has the goal of a slim API to make getting started as easy as possible. For a quick implementation example see dev/user.clj.

Lets say we have a function that writes data to a database, and we want to connect it to the web.

We can do this by creating a bones command handler. This is a function that takes three arguments. The first is a clojure.spec.alpha-defined map. The second is also a map, and contains identification information gathered from the request.

When bones receives a command it will execute the function of the command matching the name given, and pass the args of the request as the first parameter. The response body will consist of the return value of this function.

note: only edn is accepted currently

note: "/api" is the default mount point and can be configured ...

Almost ready, test this out and see that this will result in a 401 response:

If "find-user" returns, let's say, "{:user-id 123}", then "{:user-id 123}" will be the second parameter to all of the command handlers.

If the "login" function returns nil, the login attempt is taken as invalid and an error response is returned.

A valid login response contains a "Set-Cookie" header for the browser. This cookie's value is the "auth-info" data encoded with a secret. This encoded data is also provided in the response as "token". The same encoded data can be used to make api requests and to keep a browser session.

Take note of two important things here. Keep the "auth-info" small, there is a limit to the cookie size. Keep your secret safe. You'll want to put it into a configuration file or environment variable.

You can generate a unique random secret with bones.http.auth/gen-secret

The browser will keep the session for you. To logout of the session, make a request to the logout resource, which will clear the cookie with another "Set-Cookie" header.

To make authenticated API requests use a header called "Authorization" with a value of the encoded data prefixed with "Token " like this: "Authorization: Token WYdJ21cgv2g-2BlNkgdyYv.."

The response of the "login" function above could be altered to add "share" data to the response, along with the token. This is useful for sharing groups or roles the user is in. The share data is sent via meta data:

Let's say we have a function that gets data from a database. We'll connect this function to the web by creating a query handler. This is a function that takes three arguments, similar to the command handler. The first parameter will be the parsed query string from the web request, conformed to a spec. The second is the auth-info, and the third is the whole request.

There is a protocol that all the browsers have implemented to maintain a persistent connection to the web server. We can use this to push data in real time to the browser. It is really simple to set up - if you have some experience with streaming data or Clojure's lazy sequences. Here are some things you need to know about a bones event-handler: There can be only one event-stream handler. It does not have a spec attached to it like the other handlers, and it must return a stream, or anything that Manifold can turn into a source.

(defn event-stream-handler [request auth-info]
(range 10))

The stream can consist of anything and it will be sent as "data: " in the SSE protocol. If the message is a map with the special keys ":event" or ":id", they will be added to the sent event. In this case three keys should be provided such as:

Another way to consume an event-stream is via a WebSocket. You can use the same function to serve both SSE and WebSocket connections. The WebSocket connection will only serve the :data attribute. The :event and :id will be dropped because those features aren't in the protocol.

Normally we put our connections into a single global atom so we can control the life cycle of the connections easily. Here, we're going to put all our configuration for the bones system in this atom as well. Well bring it all together like this: