"Use a CMS as your backend" sounds like the kind of decision you defend in a meeting and regret in production. A CMS is for marketing pages and blog posts, isn't it? Not for drivers, vehicles, contracts, and bookings.
I've worked on several internal and customer-facing tools at G&M where Strapi is the backend, and it has held up better than that reputation suggests. It also has clear limits. Knowing which side of the line a project sits on is the whole decision.
What you actually get for free
Strip away the "CMS" label and Strapi is a Node.js app that gives you, out of the box:
- Content types (effectively database models) with relations, defined in a UI or in schema files
- REST and GraphQL APIs generated from those types, with filtering, sorting, pagination, and population of relations
- An admin panel where non-developers can view and edit records
- Role-based permissions on every endpoint
- File uploads with pluggable storage like S3
For an internal operations tool, that list covers a surprising share of the first few months of work. The admin panel alone is worth a lot. Operations staff get a working way to fix a bad record on day one, instead of waiting for someone to build an edit screen.
Where it fits
The pattern that works is CRUD-heavy domain, custom frontend. Staff and customers use a purpose-built Next.js app. Strapi sits behind it as the data and auth layer, and the admin panel is a fallback for anything the custom UI doesn't cover yet.
It fits best when:
- Most operations are create, read, update, delete on well-defined entities, plus filtering. Vehicles, drivers, customers, appointments.
- Requirements change often. Adding a field or a relation is minutes, and the API updates with it.
- Non-developers need to edit content anyway, like course material on a driver training platform or service descriptions on a corporate site.
- The team is small and the backend would otherwise be the bottleneck.
Where it stops fitting
The signals I watch for:
Business logic outgrows lifecycle hooks. Strapi lets you run code before and after writes and override controllers. That's fine for "send an email when a booking is confirmed." It gets painful when a single action has to update five related records atomically under rules that change every quarter. Once the interesting code lives in custom controllers more than in content types, you're writing a NestJS app inside a CMS, with fewer guardrails than an actual NestJS app.
Transactions and concurrency start to matter. Anything with "two people must not book the same slot" or balance-like semantics needs careful database-level guarantees (I wrote about the money version of this here). You can get them, but you're working against the framework's defaults, not with them.
Query shapes get complex. The generated APIs are great for "list vehicles where status is available, with their driver." They're awkward for reporting queries with aggregations across several tables. At that point a direct SQL query or a dedicated reporting endpoint is cleaner, and if you have to build those anyway, the case for the CMS layer weakens.
Upgrades become a project. Major-version upgrades of a CMS can touch your custom code in ways that a plain framework upgrade wouldn't. The more you've customized, the more this costs.
The middle path
It doesn't have to be all or nothing. A setup that has worked: Strapi stays the source of truth for content-shaped, admin-edited data, and a separate service handles the transactional core. The frontend talks to both. Splitting along that line means the CMS does what it's good at and nothing that needs strong guarantees.
The trade-off
Starting on a headless CMS trades long-term flexibility for a fast start and an admin panel you didn't have to build. For internal tools in a domain that's mostly records and workflows, that trade has been worth it for me. The mistake isn't choosing it. The mistake is not noticing when the project has moved past it, and pushing more and more custom logic into hooks until the CMS is just an awkward framework you can't easily leave.