Looking for a GraphQL caching solution in Bun? It’s time to BuQL Up
Contributing authors: Jacob Diamond, Julien Kerekes, Joseph McGarry
The Problem
The Bun-time Environment
Any software developer knows that the profession exists within an ever-changing landscape. There is always room for iteration. Even aspects that are seen as foundational can be improved upon, so there has been little surprise to the Bun runtime environment’s sharp ascent to popularity. Hoping to replace the dominant Node.js with a drop-in solution, Jared Sumner crafted Bun as so much more than the standard runtime; Bun brings with it bundling capabilities that rival webpack, a testing framework akin to Jest, and a package manager more efficient than npm, all “baked” in. Beyond functionality, Sumner’s creation offers impressive speeds as a result of the Zig programming language and JavaScriptCore engine it utilizes. These tools lead to a performance that is two to ten times faster than Node.js.
Bun’s Dough-lemma
With its full release being September 8th of 2023, Bun is still “fresh.” The world surrounding it is still very young, which is what drew our attention. We realized there had yet to be a package that served as a native GraphQL caching solution in Bun.
This is where the development of BuQL (pronounced “buckle”) began.
The Utility Belt
The BuQL
BuQL is the harmonizing of Bun and GraphQL into one easy-to-use npm package, with ioredis included for the most optimal query response times. Now any developer with a Bun-based codebase can utilize BuQL as an Express middleware to intercept queries, returning the responses from a cache when possible and caching them otherwise. BuQL also brings peace of mind in its application due to the built-in security. In order to understand it on a deeper level, it is important to first briefly explain the technologies it employs.
The Graph-ling Hook
As the source for the QL in BuQL’s name, GraphQL is the focal point of BuQL. Beginning development in 2012, GraphQL is a query language designed by Facebook engineers. Meta says that the team needed, “…a data-fetching API powerful enough to describe all of Facebook, yet simple enough to be easy to learn and use…” so that is exactly what they built. GraphQL responses mirror the queries requesting them, making the response more predictable and the queries easy to write, given the data being searched. As a result, one only gets what is asked for, never more or less. This simple system helps avoid the problem of overly complex join statements or RESTful services making the costly effort of multiple round trips. This resulted in an API management experience so favorable that the language is now used by the likes of Airbnb, Medium, PayPal, Netflix, The New York Times, and so many more. As of the time this article is written, it is the largest free, open-source API management software by market share.
The Cache-arang
A lot of the language surrounding GraphQL refers to databases, but it works just as well for caches. This is where Redis steps in. As a non-relational, in-memory database, Redis revolutionized what caching could be in 2009 with its sub-millisecond latency. While BuQL focuses on Redis’ caching ability so that the user may decide what database software to use, its multi-model structure allows it to support multiple database paradigms. What BuQL does take advantage of, however, is the Alibaba-designed Redis client, ioredis. This npm package allows Redis to work a little faster while maintaining almost identical syntax. The differences are few, but it makes a notable impact as Ably discovered upon migration. Because of the similar syntax, the comprehensive resources that Redis has to offer, such as an active YouTube channel, a book, and an extensive, user-friendly website, all remain valid for troubleshooting, which was another reason we chose to use this in our stack.
BuQL to the Rescue
How to Call Upon the Powers of BuQL
With the power of Bun, GraphQL, and ioredis built in, BuQL removes the need for a deep understanding of these technologies and how they work. All you need to do is some installation!
- First, we recommend setting your project up with the wonder of Bun! While a lot of BuQL could work without it, there may be some unexpected issues. Check out their docs on how to get set up.
- Then, the best part: BuQL up! Run “bun install @buql/buql” in your command line! (if the name looks a little funky, that’s because it is, plain-jane “buql” was not available) Import this into the file you’ll be working on (usually this file would be called “index.js”!)
- Now let’s get the caching set up. Run “$ bun install ioredis” in the command line, then import it into the same file as BuQL. We recommend ioredis due to its performance, but BuQL will work with the original Redis software due to the syntactic similarity.
- Just like we did with BuQL and ioredis, run “$ bun install express-graphql” and import it into the same file.
- If you haven’t already, go ahead and create the schemas for your GraphQL routes, and import them into the file you’ll be working on, the same one as BuQL.
With everything in place, you’re all BuQLed in! Now you can set up your routes with BuQL.
For instance, in our demo’s frontend we wrote:
const buqlResponse = await fetch('http://localhost:8080/buql', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({query: selectedQuery.query}),
});
const responseObj = await buqlResponse.json();
let { source, cacheHits, nonCache, response } = responseObj;
From there, it travels to the backend, which has this code,
app.use('/buql', buql.security, buql.cache, (req, res) => {
return res.status(200).send(res.locals.response);
});
app.use('/clearCache', buql.clearCache, (req, res) => {
return res.status(200).send('cache cleared');
});
// Standalone graphql route
app.use(
'/graphql',
graphqlHTTP({
schema,
graphiql: true,
})
);
Once the response object travels through these routes, it will have four properties as can be seen in the destructuring in the frontend code. These properties are:
- source: this specifies whether the response came from database or cache
- cachedHits: the number of hits to the cache
- nonCache: the number of hits to the database
- response: the actual GraphQL query response
How does this all work? Let’s take a look:
BuQL In Action
First, the queries run through an optional security filter (see below).
Then, safe GraphQL queries are broken down at the field level, with the value for each field returned from the cache or queried from the database if available.
The GraphQL response for each field in the query is saved in a local Redis cache instance using its in-memory data store to drastically speed up the process of returning previous queries.
Always at the Ready
GraphQL was made for flexibility, with its architects planning for both small, shallow databases and massive, complex ones. Because of this flexibility, intrinsic security became a necessary sacrifice. Rest assured, BuQL accounts for this. First of all, BuQL mitigates the risk of injection attacks through an allowed list of characters. Any query that has symbols not on this allow list, such as “=” or “!”, are immediately rejected, making sure that the malicious code never reaches your database. As of launch, the security route only checks for suspicious characters. (BuQL is open source, however, so feel free to add some more features!)
Further Docs
This is just the beginning. The BuQL team does hope to further develop this product. For now, take a look at the GitHub Repo and the README for the project, written by the BuQL team. You can also find other information at our website or find us on LinkedIn. Be sure to follow the project on X as well to stay up-to-date on features!
Roadmap for Future Features
From its conception, BuQL was developed to be an open-source product with a never ending journey to perfection! We gladly welcome any and all contributions, whether through iterations, additions, or general feedback! Here are some features we would love to see, more tools to go in the utility belt that we BuQL together:
- Client-side caching
- More in depth security
- More comprehensive testing
- The ability to handle nested queries
- A more agnostic, unopinionated approach, allowing for use beyond just the Express framework.
Thank you for reading this article, and thanks to our partners at OSLabs for helping to make BuQL a reality. We would also like to thank those who were kind enough to star the project; every bit of support propels us to bigger things!
If you would like to know more about our team, you can find us here:
- Dylan Briar | GitHub | LinkedIn | dylan.e.briar@gmail.com | X (formerly Twitter)
- Jacob Diamond | GitHub | LinkedIn
- Julien Kerekes | GitHub | LinkedIn
- Joseph McGarry | GitHub | LinkedIn