Recently as part of my work at Supercool I had the opportunity to create an API connection between two Craft sites. The brief was to push entry data from one site to the other, creating a single point of editing for the client but two websites on which it could appear.
Thankfully we built both of the sites in question so implementing it was pretty painless as far as these things go. Having never built an API before I started reading Phil Sturgeons book Build APIs You Won't Hate, if you haven’t read it and are slightly daunted by the idea of building an API it is a really good introduction and easy to read so I would highly recommend it! I found it gave me a good grounding in ideas and concepts I’d been happy using before when working with established APIs, but when faced with deciding over them myself I was feeling out of my depth.
In the end I broke the project up into a few tasks, which I’ll go over here.
1. Decide exactly what data needs to be sent
Is it just simple text fields, or are we talking images and Matrix content?
For this particular project we concluded that I needed to send data that was stored in Rich Text, Table, Number, Plain Text, Asset and Category fields. Thankfully most of those lend themselves to just being sent as they are, but I did find I had to encode the Table field content properly, and parse out the Rich Text fields to a simple string first. Assets and Categories I will get to later on.
2. Plan out the endpoints
What endpoints do I think I need, and what data am I expecting them to send or receive?
I loosely ended up with the following:
/api/entries/XXXto receive the main body of field data
/api/categoriesto give me back a list of categories
/api/imagesto receive my images
/api/images/check-signatureto check if an image already exists
3. Build the first iteration of the API as a Craft plugin on the target site
First off I implemented only the simplest of the endpoints (the
/api/categories GET) and got it working with a test client (I used the Advanced REST Client Chrome extension).
Once I had that working, I set about deciding on the authentication layer. I spent quite a while agonizing how best to do this and in the end went with OAuth 2. Even though its a very simple little scenario, I still don’t want to be responsible for some kind of breach or data loss due to an over-simplistic approach. Phil goes into a good discussion on all the options in chapter 9 of his book.
It turned out to be quite easy in the end to get what I needed, which was a single Client Credentials Grant using the awesome league/oauth2-server package.
4. Send some data to it from the source Craft site
I used the undocumented template hook
cp.entries.edit.right-pane to add a pane with a button in it to the right sidebar of the entry view and from there I used some js to ping a controller action in my plugin. That controller action did the actual sending of entry data to my API, it also handled all the possible responses.
I could have just as easily used the
entries.onSaveEntry event but then the data would have been sent each time the user hit save - which was not desirable in this case.
5. Deal with Categories
For the Categories I built a tool that allowed the user to map the Categories on the source website to ones already in existence on the target one. Then I could just send the Category IDs across with the request and pick up the matching elements at the other end.
This is why I needed the
/api/categories GET endpoint, so that I could fetch the categories first and then present the user with a dropdown of options to choose from.
6. Deal with Images
Images were a bit trickier, thankfully I only had to handle one at a time and in the end I sent it in a separate request. To get around the situation where the user would keep sending the same image again and again I came up with a solution that used the
I first computed the signature of the image on the source site, then sent that to the target site using the
/api/images/check-signature endpoint. If that came back telling me there wasn’t an image already there that matched, I would send the image. Then when storing the image on the target site I would also store the signature alongside to it for comparison the next time.
In a nutshell, that was it! To actually match up the entries on both sides I used a value that we already had on both sides from a third-party API we were importing from. Without that I would have had to have some kind of tool that let the user select from a list of entries on the target site and map one of those to the entry on the source site. Not a big deal but not needed in this case.
If you are interested in a more in-depth tutorial or series on how to get your two Craft sites communicating in this way then drop me a note in the comments below and I’ll make some time to write it up! Who knows, I might do it anyway.