Project

bx-graphql

A schema-agnostic GraphQL server for native BoxLang, wrapping graphql-java (vendored, v26.0). Ships with zero domain schema of its own — it parses your .graphqls files, wires resolvers by convention, and hands back a plain {data, errors} struct. No ColdBox, no WireBox, no HTTP framework required — the pure-BoxLang sibling to coldbox-graphql.

Schema-first

Drop .graphqls files in a directory, point schemaPaths at it — every file is parsed and merged into one schema automatically, forward references across files included.

Convention over config

A resolver class is only needed for fields with custom logic — everything else falls back to PropertyDataFetcher against a plain struct.

Zero framework

A global bxGraphQL() BIF, auto-registered on module load. execute() never touches HTTP — wiring POST /graphql up is entirely your app's own router's job.

Auto-loaded jars

BoxLang's own module system loads the vendored libs/ jars onto the module's classloader — no separate classloader-registration step, unlike the ColdBox edition of this module.

Install

bashbox install bx-graphql

Requires BoxLang 1.15.0+. This module only runs on BoxLang — it isn't tested against Lucee or Adobe ColdFusion.

Demo

bx-graphql-demo is a runnable, fully offline example — bx-graphql served over HTTP by boxlang-express, exposing a small seed dataset (users, posts, comments) shaped like the classic JSONPlaceholder API through a single /graphql endpoint, plus a built-in browser query console for poking at it without a separate GraphQL client.

bashgit clone https://github.com/robertz/bx-graphql-demo.git
cd bx-graphql-demo
box install
boxlang app.bxs

Then open http://localhost:3000/ for the query console, or hit the API directly:

bashcurl -s localhost:3000/graphql -H "Content-Type: application/json" \
	-d '{"query":"{ post(id: \"1\") { title comments { name body } } }"}'

The schema (User, Post, Comment, plus nested Address/Company on User) mirrors JSONPlaceholder's own relationships — a post belongs to a user, a comment belongs to a post — with root fields for each direction (users, postsByUser(userId), commentsByPost(postId), and so on). One {TypeName}Resolver.bx per type handles the fields that need real lookups (e.g. User.posts, Post.comments); everything else — Post.title, Comment.body, etc. — resolves straight off the seed record via PropertyDataFetcher, no resolver code needed. All data is local, static JSON, so it runs with no dependency on the real JSONPlaceholder API.

Quick start

javascriptgraphQLService = bxGraphQL( {
	"schemaPaths"         : [ expandPath( "/graphql/schema" ) ],
	"resolverBasePackage" : "models.resolvers"
} )

result = graphQLService.execute(
	query          = '{ widget(id: "1") { name } }',
	queryVariables = {},
	context        = ""
)
// result == { "data" : { "widget" : { "name" : "..." } }, "errors" : [] }

GraphQLService builds the graphql-java engine once, at construction time — build it once (app startup) and reuse it. new bxModules.bxgraphql.models.GraphQLService( settings ) works identically if you'd rather be explicit about where bxGraphQL() comes from.

Resolver convention

For a schema type TypeName with field fieldName, the module looks for {resolverBasePackage}/{TypeName}Resolver.bx and, if it exists and implements a fieldName() method, calls it. Otherwise it falls back to graphql-java's PropertyDataFetcher — reading a same-named key off the parent object. Resolver methods receive four named arguments:

javascriptany function fieldName( any source, struct args, any context, any env ){
	// source  — the parent object; NULL for root Query/Mutation fields
	// args    — the field's GraphQL arguments, as a plain struct/array
	// context — whatever was passed as `context` to execute()
	// env     — the raw graphql.schema.DataFetchingEnvironment, for advanced use
}

Mutation gets no special treatment — it's just another type name to the same convention, with top-level mutation fields executing serially per the GraphQL spec rather than in parallel like queries.

Not yet implemented

  • Custom scalars
  • Interface/union TypeResolver wiring — every object type gets its own convention-based resolver, but resolving which concrete type implements an interface/union at runtime isn't wired up
  • An introspection on/off toggle (introspection is always available, per graphql-java's default)

Tests

TestBox specs, run headlessly — no server needed:

bashboxlang setup-tests.bxs   # once per checkout
boxlang run-tests.bxs     # every time after that