DEV Community

Cover image for You Probably Don’t Need a Headless CMS. Here’s When a Query Library Is Enough.
Zsolt Tövis
Zsolt Tövis

Posted on

You Probably Don’t Need a Headless CMS. Here’s When a Query Library Is Enough.

I was about to pay $29/month for a Headless CMS. Again.

Not because I needed content collaboration. Not because I had a marketing team editing pages. Just because I had 500 blog posts in Markdown, and I was tired of writing ugly Array.filter chains to query them.

I stopped myself. I realized I was about to solve a code problem with a subscription service. So instead, I built Qar — a 5KB (gzipped) library that brings MongoDB-style queries to plain JavaScript objects.

But let me be clear: This is not “the end” of anything. It’s a choice. And it’s only the right choice in very specific situations.

When You DON’T Need This

Let’s start with honesty. You should NOT use Qar (or any JSON-based approach) if:

  • Non-technical people edit your content. If your marketing team needs to publish blog posts, they need a visual CMS. End of story.
  • You need real-time collaboration. Versioning, drafts, approval workflows — these are not luxuries. They are business requirements.
  • Your data is dynamic. User-generated content, e-commerce inventory, analytics dashboards that update every minute — use a real database.
  • You have millions of records. In-memory querying doesn’t scale to Big Data. Use PostgreSQL or MongoDB.

If any of the above applies to you, close this tab. Go pay for Contentful or Sanity. They exist for good reasons.

When This Actually Makes Sense

Now, let’s talk about the cases where spinning up a CMS or a database is over-engineering.

You are a solo developer (or a small team of devs) building:

  • A portfolio site with 50–1,000 pages of static content
  • A documentation site where all content lives in Markdown
  • A JAMstack app with build-time data (product catalogs, blog archives)
  • An internal dashboard querying static config files

In these scenarios, your “database” is actually just a pile of JSON files. And your problem is not storage. It’s querying.

The Real Problem — Querying Sucks in Vanilla JS

Let’s say you have a posts.json file with 500 blog entries. You want to show the 5 most recent posts in the "JavaScript" category, sorted by date, excluding drafts.

Here’s the “native” way:

const featuredPosts = posts
  .filter((p) => 
    p.category === 'javascript' && 
    p.published === true && 
    p.draft !== true
  )
  .sort((a, b) => new Date(b.date) - new Date(a.date))
  .slice(0, 5);
Enter fullscreen mode Exit fullscreen mode

It works. But it’s imperative. You’re describing how to loop, not what you want. And if you need pagination, complex “OR” logic, or regex matching? The code gets uglier fast.

Enter Qar — Declarative Queries for Static Data

Qar brings the MongoDB query syntax to plain JavaScript. Same query, but cleaner:

import Qar from 'qarjs';

const posts = new Qar(postsData);

const featuredPosts = posts
  .find({ 
    category: 'javascript', 
    published: true,
    draft: { $ne: true }
  })
  .sort({ date: -1 })
  .limit(5)
  .toArray();
Enter fullscreen mode Exit fullscreen mode

It’s not magic. It’s just a cleaner API. But that cleanliness compounds when you’re building an entire site.

Real Use Cases (Where This Actually Helps)

1. Static Site Generation (Next.js, Astro, Eleventy)

In my own portfolio, I have several JSON files: blog-content.json, media.json, skills.json. I use a models.js file to unify them:​

// models.js
import dataBlog from '@/config/blog-content.json';
import dataMedia from '@/config/media.json';
import Qar from 'qarjs';

export const modelBlog = new Qar(dataBlog);
export const modelMedia = new Qar(dataMedia);
Enter fullscreen mode Exit fullscreen mode

Now, instead of writing nested loops to “join” posts with their cover images, I can query them like a database:

const posts = modelBlog.find({ published: true }).toArray();
const postsWithImages = posts.map(post => ({
  ...post,
  coverImage: modelMedia.findOne({ id: post.cover_image })
}));
Enter fullscreen mode Exit fullscreen mode

Is this revolutionary? No. Is it cleaner than manual loops? Yes.

2. Frontend Data Pipelines

Let’s say you fetch a list of 5,000 transactions from an API and want to show “Revenue by Category” in a chart. You could ask the backend to add a new endpoint, or use Qar to aggregate client-side:

const analytics = new Qar(transactions).aggregate([
  { $match: { status: 'completed' } },
  { $group: { 
      _id: '$category', 
      totalRevenue: { $sum: '$amount' }
  }},
  { $sort: { totalRevenue: -1 } }
]);
Enter fullscreen mode Exit fullscreen mode

Again: not a silver bullet. But if the data is already client-side and you need quick analytics, this beats writing a custom reduce function.

3. Serverless Functions Without DB Overhead

In a Lambda or Vercel function, spinning up a MongoDB connection adds latency (cold starts) and complexity (connection pooling). If your “database” is just a static config file, Qar gives you query power without the infrastructure cost.

export async function GET(req) {
  const users = new Qar(userData)
    .find({ 
      role: req.query.role,
      active: true
    })
    .toArray();

  return Response.json(users);
}
Enter fullscreen mode Exit fullscreen mode

What This Is (and Isn’t)

Qar is a convenience library. It makes querying static data cleaner.

That's it.
It doesn’t replace a CMS if you need content workflows.
It doesn’t replace a database if you have dynamic data.
It’s a tool for a specific niche: developers working with build-time JSON who want cleaner query syntax.

If that’s you, it’ll save you time. If it’s not, you don’t need it.

Conclusion

The modern web has a habit of over-engineering. We reach for complex tools before we even understand the problem.

But sometimes, the opposite is true: we reach for simple tools (raw Array.filter) when a slightly better abstraction would save us hours of maintenance.

Qar lives in that middle ground. It's not a revolution. It's just a cleaner way to query the JSON files you already have.

My Portfolio page: https://toviszsolt.stacklegend.com/
GitHub Repo: https://github.com/toviszsolt/qar
NPM: https://www.npmjs.com/package/qarjs

Made with ❤️ for developers who love clean APIs and don’t need a database for everything.

Top comments (1)

Collapse
 
toviszsolt profile image
Zsolt Tövis

I built this because I'm lazy and I love clean code. Let me know what you think!