TLDR; I built a framework-agnostic tool that gives you the markdown/YAML file -> GraphQL server experience of Gridsome/Gatsby.
Problem
Storing your relational data as files in a git repo, deployed onto a static site target (Netlify, Vercel, etc.) can be an incredibly powerful pattern with the only running cost being the price of the domain name.
I love using NetlifyCMS for generating that data as markdown files, then querying it from a compile-time GraphQL API into a static site. However, I found that when you go this direction, you're typically pigeonholed into using Gatsby, Gridsome, or Hugo - unless you have very simple, non-relational data. This has the consequence of dictating many aspects of your code and effectively vendor-locks you into the strong opinions of those frameworks.
Those frameworks are great and tons of work has gone into them, but I wanted total freedom to use something like SvelteKit, Remix, or Astro while having the same flat file -> local GraphQL server experience.
So as it goes, I built something to scratch my own itch...
Enter, Flatbread
I'm using it for a little tea log that's currently in the process of migrating from Gridsome to SvelteKit + Flatbread. Hopefully others find this useful! π
Installation
pnpm i flatbread@latest
Automatically create a flatbread.config.js
file:
npx flatbread init
If you're lookin for different use cases, take a peek through the various
packages
to see if any of those plugins fit your needs. You can find the relevant usage API contained therein.
Take this example where we have a content folder in our repo containing posts and author data:
content/
ββ posts/
β ββ example-post.md
β ββ funky-monkey-friday.md
ββ authors/
β ββ me.md
β ββ my-cat.md
...
flatbread.config.js
package.json
In reference to that structure, set up a flatbread.config.js
in the root of your project:
import { defineConfig, markdownTransformer, filesystem } from 'flatbread';
const transformerConfig = {
markdown: {
gfm: true,
externalLinks: true,
},
};
export default defineConfig({
source: filesystem(),
transformer: markdownTransformer(transformerConfig),
content: [
{
path: 'content/posts',
collection: 'Post',
refs: {
authors: 'Author',
},
},
{
path: 'content/authors',
collection: 'Author',
refs: {
friend: 'Author',
},
},
],
});
Now hit your package.json
and put the keys in the truck:
// before
"scripts": {
"dev": "svelte-kit dev",
"build": "svelte-kit build",
},
// after becoming based and flatbread-pilled
"scripts": {
"dev": "flatbread start -- svelte-kit dev",
"build": "flatbread start -- svelte-kit build",
},
The Flatbread CLI will capture any script you add in after the --
and appropriately unite them to live in a land of fairies and wonder while they dance into the sunset as you query your brand spankin new GraphQL server however you'd like from within your app.
Run that shit πββοΈ
pnpm run dev
Construct queries π©βπ³
If everything goes well, you'll see a pretty graphql
endpoint echoed out to your console by Flatbread. If you open that link in your browser, Apollo Studio will open for you to explore the schema Flatbread generated. Apollo Studio has some nice auto-prediction and gives you helpers in the schema explorer for building your queries.
You can query that same endpoint in your app in any way you'd like. Flatbread doesn't care what framework you use.
Query arguments
The following arguments are listed in their order of operation.
filter
Each collection in the GraphQL schema can be passed a filter
argument to constrain your results, sifting for only what you want. Any leaf field should be able to be used in a filter.
The syntax for filter
is based on a subset of MongoDB's query syntax.
filter
syntax
A filter is composed of a nested object with a shape that matches the path to the value you want to compare on every entry in the given collection. The deepest nested level that does not have a JSON object as its value will be used to build the comparison where the key
is the comparison operation and value
is the value to compare every entry against.
Example
filter = { postMeta: { rating: { gt: 80 } } };
entries = [
{ id: 1, title: 'My pretzel collection', postMeta: { rating: 97 } },
{ id: 2, title: 'Debugging the simulation', postMeta: { rating: 20 } },
{
id: 3,
title: 'Liquid Proust is a great tea vendor btw',
postMeta: { rating: 99 },
},
{ id: 4, title: 'Sitting in a chair', postMeta: { rating: 74 } },
];
The above filter would return entries with a rating greater than 80:
result = [
{ id: 1, title: 'My pretzel collection', postMeta: { rating: 97 } },
{
id: 3,
title: 'Liquid Proust is a great tea vendor btw',
postMeta: { rating: 99 },
},
];
For more details, please check out the docs in the Github repo: https://github.com/tonyketcham/flatbread
π Sidenotes
This is very much in Alpha and there are a couple quirks that need ironing out.
-
Handling cross-collection, mongoDB-style filtering.
- That type of filtering is currently supported when done within a single collection, such as fetching a single post with a matching
slug
ortitle
field, or all posts with a publish date greater than or equal to a week ago. - This library is ESM-centric, and while it also exports a CJS bundle, I haven't yet tested strongly in environments outside of Vite or Svelte(Kit). So while the plan is to test integration with all sorts of frameworks, all I can currently promise is that it's framework-agnostic by design and should work with all sorts of static-enabled things like Astro, Remix, Next, Nuxt, etc.
- That type of filtering is currently supported when done within a single collection, such as fetching a single post with a matching
I'd love to find more people to work on Flatbread with me as I'm coming up against some interesting challenge areas and feel there's tons of potential here to disrupt this niche of the ecosystem
Top comments (0)