🧱 Building a Ramshackle Contentful Client API in Rust
RustContentfulAPI
Last updated
9 min read
Originally I thought it would be best to host my WordPress instance and use it to host my blog. I'd have some Rest Service sit in front of it and query it for content and serve it up to my Portfolio front easy peasy. I would have control over everything and most of all for my purposes it would be free. But I soon realised the headache that entailed. I needed to essentially worry about:
- Security
- Plug-ins
- Data Persistence
- Locking down the instance to operate in API mode only
All of these things made building up my blog a chore in my mind, I would need to log in to prod through a risky admin interface exposed to the entire internet that anyone could find and exploit. I needed to handle several plug-ins that seemingly broke constantly, to redirect traffic from the main WordPress frontend to my domain and handle a few other things I wanted to include. I was concerned that if the Cloud machine I am using to host the project ever died, all of my content would be lost forever, as I did not have any backup or persistent storage beyond some Docker volumes to hold all of the data.
It made me not want to use it.
But then I found Contentful.
Contentful is a Content as a Service (CaaS) headless content management system. I had considered them a few times previously, taking a look at their pricing model and functionality, but I was quite busy at the time with a job move to be able to explore what they offered and how I could use it. So I put the idea to the back of my mind and carried on with the tumult.
Recently I decided to take the plunge to add Contentful to my project stack in place of WordPress. When taking a look I could see their free offering was perfect for my needs. they were API-only, and not blog-focused, I would be able to craft my content to my own needs, which opened a lot of scopes to migrate my largely hardcoded portfolio content, that I was bundling with my backend API.
Previously if I ever wanted to make any changes or additions to my portfolio, I would need to redeploy my backend service, which is quite a heavy operation for something as little as simple text changes, requiring:
- Committing the changes to main
- CircleCI running the whole CICD pipeline for both frontend and backend jobs
- Rebuilding both Docker images
- Pushing those images to the registry
- Redeploying the new images with watchtower
I had taken a look at this Contentful Rust Crate and for simple purposes, it was my silver bullet. I'd be able to remove WordPress from my project stack. Refactor my backend service to query data from Contentful using this handy crate, and head off into the sunset.
So that is exactly what I tried to do. I was able to start pulling data from Contentful using my Rust-based backend service. Once I got my head around setting up the correct data structures to house my BlogPost model, I was able to get data up to the front end and start to display it. I was even so lucky to discover there are whole NPM packages ready and waiting to consume Contentful rich text data, so I could easily transplant content from Contentful straight into React components without lifting a finger.
But then I noticed a big snag.
I noticed Rich text with image assets was not being rendered correctly. This is because Contentful NPM-rich text renderers put the onus on you to configure how to render references and image assets. The Contentful crate was not resolving the included content that can be referenced within rich text data, and considering that the existing rich text renderers want you to tell it how to handle images and content references. It somewhat made sense.
To be able to extract and expose the included data, I tried to take advantage of Rusts trait mechanisms to try to implement an extension to the Contentful crate so that I could cobble together the data I needed to be able to render content in the front correctly. Sadly I found that the methods I wished to marry were private at the crate level and I was not able to access them within my trait implementation.
My first option was to just throw out the Contentful crate altogether, and just implement a really simple HTTP request using Reqwest to the Contentful API and then return whatever data they sent back, unmassaged and unmodified. this made me wonder what benefit I was getting from having a backend service in the first place.
With this, I wasn't able to change the content type or use any of the other Contentful API parameters, unless I wanted to implement a whole query builder. overall it was very ineffective not very useful, and wouldn't work for the looming portfolio content migration.
I then decided to essentially fork the Contentful crate itself, the crate has an MIT license and even though half of it didn't apply to my use case, having the actual code that I could manipulate as I saw fit was a good start. I gave the code an overhaul replacing a lot of the if let some() ... statements and trees of if statements with more expressive match statements.
Before:
After:
I was able to trim down the complexity of the code by really leaning into Rust's pattern-matching features and removing unnecessary cases that I didn't need nor were handled in the original anyway. I also removed all of the in my opinion unnecessary result-type objects being returned. they added a lot of complexity to the code for no real benefit. I guess the original point was to have the code return errors if the reference resolution algorithm got into a state it couldn't handle. but the original code only ever returned Ok variants. so to me, it appeared to be dead weight and it was cut.
Now the main reason to fork the Contentful crate was to be able to expose the included data I spoke about earlier. I was able to do this quite easily, the includes were being used to do some basic reference resolution. My next job was then to get this data out by doing the following:
By returning an Entries<T> object I would be able to return both the resolved Contentful entries as well as the included data. This adds a little bit of bloat to the response data, but to be able to render content properly I think this was the best way forward. The client API implementation can be found here
For now, my version of the Contentful client lives as a local library crate within my backend rust service. I can now easily create content models, populate them with entries, declare a model struct in my backend, and then use the Contentful client to retrieve the data for my frontend portfolio and blog to consume.
With all this in place, it was very simple to migrate my hardcoded portfolio content from JSON into Contentful, I was able to essentially follow the same pattern described above, and package the data up to be consumed by the front, the backend implementation of which can be found here.
I may reimplement the parts of the original crate I removed and release it to crates.io as an alternative with a little extra functionality to toggle exposing the included data or not. but for now, this meets my needs. You can look here if you wish to see the whole implementation.
if you made it this far, I hope this has been helpful or at least interesting! ✌
📦 What I had
A WordPress docker container, ready to be hacked, and/or data (my hard work) scattered to the digital void. 🔮
🧩What I needed
To ditch hosting my infrastructure and use a CaaS (Content as a Service) offering to streamline the process of content management. 🚀
😫Headaches
Rust is quite unforgiving regarding software correctness, there were a few moments where I fought with Rust to do what I thought was right. 🥊
✨Successes
Once I had the Client API in place, it was a breeze to implement the portfolio content retrieval, I even started to experiment with async traits just to make things hard again. 🤦♂️
💎Benefits
With everything in place, if I want to add new entries to my portfolio experience, education and projects sections, make general text changes, and write new blog posts, I no longer need to redeploy the entire project. 🎰
Blog
Copyright2025 Daniel William Clarke