This week I struggled with implementing image uploads via the API. The first part of the struggle centered on hosting images locally. The second part focused on getting the uploads to work via the API.
Hosting Images Locally
Out of the box, KeystoneJS uses Cloudinary for image hosting. They are a fine service, but in my experience the life expectancy of internet companies is much lower than the lifetime of many websites I build. I really wanted to figure out how to host images locally. There were a lot of false starts on this aspect of the project, but ultimately I found the LocalFiles data type mentioned in the Keystone Database Documentation. That allowed me to create a new database model for uploading images.
The magic of KeystoneJS is that once you get the model coded correctly, the interface for it automagically appears in the Admin UI. That's great if you are building a site for yourself. However my primary focus on KeystoneJS is to use it as a CMS for non-tech-savvy clients. Turning them loose in the Admin UI could lead to all sorts of issues. That's why I wrote the tutorial on opening up the API, so that I could design custom back end pages to walk clients through the update of their sites.
Image Upload via the API
Uploading images via the API was not very straightforward, despite the fact that several people had resolved this issue before me. I found several historical threads on the KeystoneJS group mailing list asking for help with this same aspect. Unfortunately none of them came back to document a clear solution to their problem. Hopefully that will be resolved with this blog post.
Long story short, here are the routes/index.js and routes/api/imageupload.js files I put together to solve this issue. The export.create subfunction in the imageupload.js file is the only subfunction that differs greatly from the front end widget file used in the API tutorial. Here is an example of a simple HTML form that can upload images via the API once those files (and the model) are in place:
<form action="http://<your ip>:3000/api/imageupload/create" method="POST" enctype='multipart/form-data'>
<input type="file" id="image_upload">
<br>
<input type="submit" value="Submit">
</form>
The important caveats of the code are:
- Based on historical mailing list threads, my understanding is that both the field name in the routes/api/imageupload.js file and the name of the HTML input field need to be named <field>_upload. In my case 'image_upload'. I might be wrong about both of them needing this, but that's what worked for me.
- The <form> enctype must be set as 'multipart/form-data'.
- I can only upload the image. If I try to set other features like the name or alt-tag, the POST request will puke. I plan to get around this by having two steps in my front end JavaScript. The first step will upload the image. The second step will update the image data with name, alt tag, ect. Again, there may be a work around for this. I just haven't found it yet.
That's the gist of how I got image uploads to work via the API. Check out this thread and this thread where I recorded some of the historical solutions I found and that summarize the information in this post.