BoxLang and GraphQL
bx-graphql brings GraphQL to native BoxLang via graphql-java, wrapping it in a schema-agnostic module with convention-based resolvers and zero framework lock-in. To prove it out beyond source code, Robert built a fully offline demo pairing it with boxlang-express — a JSONPlaceholder-style schema, thin resolvers that mostly fall through to `PropertyDataFetcher`, and a no-build-step query console. Both modules are live on ForgeBox: `box install` and go.
I've enjoyed working with GraphQL for years, and lately more than ever — I'm in the middle of several Shopify integrations that lean on it heavily. What keeps pulling me back is how precisely you can ask for exactly the data you need, nothing more. At some point I got curious what it would take to bring that same flexibility to Adobe ColdFusion, Lucee, and BoxLang — first as a ColdBox module, then as a native BoxLang module.
bx-graphql
I've been working on bx-graphql, a schema-agnostic GraphQL server module for native BoxLang that wraps graphql-java, and for a while the only way to actually see it do anything was to read the source or trust me. That's not a great pitch. So I put together bx-graphql-demo — a small, fully offline app that exposes a GraphQL API and a browser console for poking at it, with nothing to configure beyond box install.
What it's showing off
The demo pairs bx-graphql with boxlang-express, a module I've also been building, which handles the HTTP side. The whole app is one file:
app = boxExpress()
app.use( boxExpressJSON() )
app.use( boxExpressStatic( expandPath( "./public" ) ) )
graphQLService = bxGraphQL( {
"schemaPaths" : [ expandPath( "./schema/schema.graphqls" ) ],
"resolverBasePackage" : "resolvers"
} )
app.post( "/graphql", ( req, res ) => {
res.json( graphQLService.execute(
query = req.body.query,
queryVariables = req.body.variables ?: {},
context = req
) )
} )
app.listen( 3000 )
That's boxlang-express wiring up JSON parsing, static file serving, and one POST route; bx-graphql handles everything from the schema down.
Writing bx-graphql itself
The module predates the demo by a while, and the design has one goal running through it: stay out of the way. bx-graphql ships with zero domain schema of its own — it parses whatever .graphqls files you hand it, wires resolvers by convention, and hands back a plain {data, errors} struct. No ColdBox, no WireBox, no HTTP framework required. How requests get to it — boxlang-express here, anything else elsewhere — is entirely your app's problem, not the module's.
There's an earlier ColdBox edition of this same idea, and the BoxLang-native rewrite fixed the part of it I liked least. graphql-java is vendored under libs/, and BoxLang's own module system auto-loads those jars onto the module's classloader — so createObject("java", ...) and createDynamicProxy() always resolve graphql-java classes through the same classloader, no separate registration step, no per-engine branching. The ColdBox version had to work around Lucee and Adobe CF each doing classloading differently; targeting BoxLang only means that whole category of workaround just isn't needed.
The resolver convention is the other load-bearing decision: GraphQLService looks for {resolverBasePackage}/{TypeName}Resolver.bx, and calls a same-named method if it exists. If it doesn't — no file, or a field the resolver class doesn't implement — it falls back to graphql-java's PropertyDataFetcher, which reads a matching key straight off whatever the parent resolver returned. You only write code for fields that need it. That fallback is most of why the demo's resolver files stay so thin.
I also made the module fail loudly rather than quietly at startup — bad schemaPaths, a wildcard that matches nothing, a missing resolverBasePackage — all of it throws BxGraphQL.ConfigurationException at construction, not on the first request that happens to hit the broken path. I'd rather see that in a stack trace at boot than debug a null resolver three requests into a demo.
What it doesn't do yet, on purpose rather than by accident: custom scalars, interface/union TypeResolver wiring (every object type gets its own convention-based resolver, but nothing yet resolves which concrete type implements an interface or union at runtime), and no introspection on/off toggle — it's always on, per graphql-java's own default. All three are solvable; none of them were blocking the demo, so they stayed out.
Tests are TestBox specs that run headlessly against a real graphql-java engine — no server, no HTTP layer in the loop. run-tests.bxs exits non-zero on failure, so it drops into CI without ceremony; the one wrinkle is a self-referencing boxlang_modules/bxgraphql symlink that setup-tests.bxs creates once per checkout, because BoxLang only discovers modules — and loads libs/ onto the classloader — at process startup, not on demand.
Data shaped like something real
Rather than invent a toy schema, I copied the shape of JSONPlaceholder — users, posts, comments, with the same relationships (a post belongs to a user, a comment belongs to a post). It's a schema people already have a mental model for, which makes it easier to tell "this is bx-graphql behaving correctly" from "this is just an unfamiliar toy example." The data itself is static JSON checked into the repo, so the whole thing runs with zero network dependency — no waiting on the real JSONPlaceholder API, no flaky demo during a talk.
The resolver convention, in practice
In the demo, that convention mostly disappears. User.name, Post.title, Comment.body — none of it needs a resolver, so none of it gets one. What's left is small on purpose:
QueryResolver.bx— the root fields (users,user,userByEmail,posts,post,postsByUser,comments,commentsByPost)UserResolver.bx— justUser.postsPostResolver.bx—Post.authorandPost.commentsCommentResolver.bx—Comment.postSeedRepository.bx— the actual lookups, kept out of the resolvers entirely
That last split matters more than it looks. Resolvers stay thin translation layers between GraphQL args and a plain BoxLang object; none of them know or care that the data happens to come from JSON files instead of a database.
A query console with no build step
public/index.html is a single static file — a query editor, a variables editor, a handful of clickable example queries covering every relationship in the schema, and a pretty-printed result pane. No CDN dependency, no build step, no syntax highlighting or autocomplete either — I traded GraphiQL's polish for something that works completely offline and that I can read start to finish in one sitting. If you want the real thing, swapping this file for a GraphiQL build is a one-line change to what boxlang-express serves at /.
Try it
Both modules are on ForgeBox now, so setup is just:
box install
boxlang app.bxs
Then open http://localhost:3000/ for the console, or hit the endpoint directly:
curl -s localhost:3000/graphql -H "Content-Type: application/json" \
-d '{"query":"{ post(id: \"1\") { title comments { name body } } }"}'
It's a small demo on purpose. The point was never to impress anyone with the seed data — it's to give bx-graphql something concrete to point at.
No comments yet — be the first.