I was approached by my friend to develop a E-commerce site for his book publishing business. I was mainly responsible for the back-end part.
Requirements were basic:
- A good looking frontend.
- Multivender (multiple book Publishers / Sellers).
- Admin panel.
- Usual E-commerce stuff: Orders, payments, refunds, shipping, invoice generation etc.
Initially we started off with next.js in the frontend, with postgresql, Typeorm and express.js in backend.
We knew from day 1 that it might take a huge time to develop, especially the admin panel, so we were looking for alternatives. Strapi - an Open Source headless CMS was gaining some popularity back then. We gave it a try.
tl;dr: Strapi is an amazing product, but we had some special requirements, which a general CMS couldn't handle, it has some limitations. Thus, we had to change our techstack, but we learnt a lot in the process.
What is Strapi and what is a headless CMS anyway?
Lets compare it with wordpress, to have easier understanding:
A headless CMS is a content management system (this part is somewhat similar to wordpress) that stores and manages content but doesn't dictate how it's presented on a website or app. It lets developers pull content through an API to display it however they want (that's where it differs from traditional CMS like Wordpress), giving flexibility in design and platform.
"head" = the front-end presentation layer.
"body" = the back-end content management system.
Now, "head-less" = back-end content management system without the presentation layer. We have to develop the presentation layer ourselves.
Strapi is such a headless CMS. There are others in the market like: Contentful, Sanity, Picocms etc. We went with the Open Source and most popular one.
What we appreciate
- It has many functionalities like an admin panel, multiple authentication and authorization methods and a lot more. I have listed a few in this article.
- It also has good plugins and providers like AWS S3, image optimizers, image uploaders, SEO, editors and it is increasing day-by-day.
- Best of all, it is open source, self-hosted and very customizable. We can customize the admin frontend (GUI) and the backend API as well.
Content Types
We can define multiple content types: Single types, Collection types and Components.
- Single type can be like Footer, Header etc.
- Collection type are Posts, Authors, Orders etc.
- Then Components are mainly used for dynamic parts of a website like a banner with CTA and image, FAQ, Carousel. We can basically define a whole part of a webpage using components it is very powerful.
What I learnt is that many sites actually use such CMS in the backend to handle dynamic parts of their site (discounts, banners, CTA), which is mostly set by editors, sales and marketing team.
Image Optimization
Images can be stored in multiple formats like large, medium, thumbnail etc, for faster loading time and all of this is handled by strapi itself using the file upload plugin.
On going to: http://localhost:1337/api/upload/files/1
, we get:
{
"id": 1,
"name": "query_builder.png",
"alternativeText": "Query Builder Image",
"caption": "Query Builder",
"width": 600,
"height": 576,
"formats": {
"thumbnail": {
"name": "thumbnail_query_builder.png",
"hash": "thumbnail_query_builder_7d88426f22",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 163,
"height": 156,
"size": 12.1,
"url": "/uploads/thumbnail_query_builder_7d88426f22.png"
},
"small": {
"name": "small_query_builder.png",
"hash": "small_query_builder_7d88426f22",
"ext": ".png",
"mime": "image/png",
"path": null,
"width": 500,
"height": 480,
"size": 67.21,
"url": "/uploads/small_query_builder_7d88426f22.png"
}
},
"hash": "query_builder_7d88426f22",
"ext": ".png",
"mime": "image/png",
"size": 13.03,
"url": "/uploads/query_builder_7d88426f22.png",
"previewUrl": null,
"provider": "local",
"provider_metadata": null,
"createdAt": "2024-01-28T07:56:48.469Z",
"updatedAt": "2024-01-28T07:56:48.469Z"
}
API query and filtering
One of the best thing about strapi is their filtering and query functionality, learnt a lot from there. They use the qs library to handle complex filtering use cases. See here. Also, they have a very impressive query builder. Probably I will use them in a future complex project.
There are more such features. We listed the ones which we have used.
Pain points
Most of the bugs we faced is already present in their Github issues.
One of the most surprising bug is that client can update whatever and however they like, even API clients can update id (primary key) of the model. Related issue
Type System
Strapi uses Koa under the hood. To customize controllers, you have to work with a ctx
(context) object. This wasn't clear until you search through the docs properly. They have just mentioned some examples, I hope they just mention that the ctx is from koa in the Customizing Controllers page, then we could have customized as per our liking. Although this might be a nitpicking (or a skill issue from my side 🙃)
Also, VS Code doesn't provide intellisense even if we use Typescript. You need to install @types/koa to get suggestions.
Primary Keys
Anything which isn't alphanumeric can't be primary key (like slug or UUID can't be primary keys). Related issue. This isn't a big issue, we can circumvent this by creating custom controllers.
JWT Refresh Tokens
JWT tokens are implemented, but there is no refresh token feature as of now (another example of a feature from a forum / blog). JWT access tokens are expired after 30 days.
Dead End
We are trying to build a multi-vendor site. The default User
model wasn't enough. What a typical database schema design would do is just to inherit the User
model. In SQL database terms it is just to declare a One-to-one field with the User
model, thus maintaining a relation with the original User
model.
Why not just add required fields to the original
User
model itself? No this isn't a scalable schema design. Imagine updating theUser
table constantly if new fields need to be added for seller or customer. This isn't ACID compliant.Can't you just use an altogether different user-defined
User
model? No we can't, strapi is closely tied to its defaultUser
model, so that it can provide different auth flows effectively. Simple solution is to just define a One-to-One relation withSeller
andCustomer
.
{
"kind": "collectionType",
"collectionName": "sellers",
...
"attributes": {
"description": {
"type": "text",
"required": false
},
"user": {
"type": "relation",
"relation": "oneToOne",
"target": "plugin::users-permissions.user"
},
"books": {
"type": "relation",
"relation": "oneToMany",
"target": "api::book.book",
"mappedBy": "seller"
},
...
}
}
Strapi has some permission settings through its permission plugin.
- We allowed find and findOne permission for sellers.
- Only findOne for customers, as usual.
- We only applied findOne for User model (not find, because we don't want clients to enumerate all of our users - obviously).
Here comes the problems:
Seller
hasUser
as a related field, now the relatedUser
won't be populated in response, because find isn't allowed on users. In fact we can't directly fetch the relatedUser
from database (bypassing the permission system), due to permissions set earlier - strapi silently excludes theUser
related info.You cannot just create a
Seller
instance with a relation to aUser
instance, again for permissions. You must fire another api request just to "link" the two models.Client (browser) could send related fields like
User
, and it would be updated silently, to fix that add custom code (this isn't a bug - this is definitely expected, just our use case was different).
async update(ctx) {
// ignore the userId passed from client
// it is already set while creating
// (client should not be able to set the userId)
delete ctx.request.body.data.user;
return await super.update(ctx);
}
Conclusion
At last, I will thank all the contributors to the strapi project. It is a wonderful project (Star Here) and I learnt a lot from their work.
They are doing a wonderful work. Open source is mostly a thankless job - where you have to manage a huge community, constant pull requests, feature requests, issues, rewrites and a lot more.
Here I just shared my experience. We observed that this CMS might not be well suited for our project, but strapi has a lot of scope and use cases in various other projects.
We moved on to a different stack altogether (Django + HTMX), why we did that? What about its scalability? How we did that? Stay tuned 😃
Thanks a lot for reading.
Stay safe,
Have a nice day.
Top comments (8)
Thanks for this feedback, it’s Interesting to see the pros and cons of using Strapi, though this apply for your specific usage and context.
I also bet on Strapi, and used it for a couple of websites. One of those was also an e-commerce.
I had issues sometimes configuring relationship logics within components, some of them had been resolved with the V4 I have no used yet.
I would be interested to know the e-commerce stack you used with the front-end, and how did you host your project (personally, I relied on Snipcart and Heroku at the time).
At that time our frontend was in Next.js. We used netlify for hosting the frontend (no specific reason, we used it a lot in the past - that's why)
An aws ec2 instance for strapi. This project is to be used in production and we needed multiple features like CDN, storage, e-mail (currently using zoho zepto mail - dirt cheap transactional mail). We were getting all these from a single provider AWS, at a very cheap rate - but definitely it was quite a work to set up. I would rather recommend using something like digital ocean if you can spend few extra bucks.
Later on as we moved off from strapi, we also realised that next.js, along with its app router and RSC is adding some extra complexity, we were SSRing most of the pages - the frontend server felt like merely a proxy in between. Then we decided to move to good old Django + HTMX. I will post a separate post about all these, the good, the bad - all of that.
Thanks for the details! I wouldn't have think to AWS for hosting Strapi, neither Netlify, as Vercel seems more the way to go with Next. I will have a look at the other tools you mentioned. So I guess the e-commerce part is handmade plugged with a Stripe or something...
We use different provider for Payments and shipping, as Stripe doesn't support some Indian payment methods.
I am just curious about Next.js specifically its backend part. I heard about Vercel edge or something. It probably has some major limitations (maybe like time limit or storage?) Kinda obvious because it is mostly meant for frontend.
We had to use a proper backend hosting strategy - there are webhooks coming from external payment and shipping APIs, background cron jobs, backups, also long running tasks like websockets - can Vercel edge handle it? I haven't used it.
I'm not sure about Vercel capacities, as it's moving very quickly. Storage is pretty new, I know edge functions (internal next api routes actually) are improving as well but I didn't have chance to experience all those stuff yet. I know for sure it's not mean to host a strapi instance as of now 🙂.
I'll definitely try out this thing
Interesting post! I'd definitely just be updating the included User model, just like with Laravel or Rails.
Intresting post