- Graphoria
- Contents
⚠️ Pre-1.0 Stability- Features
- Why Graphoria?
- Who is this for?
- How it works
- Coming Before v1.0
- Prerequisites
- Installation
- Try it now
- Quick Start
- API Endpoints
- Supported Databases
- Environment Variables
- Troubleshooting
- Production Checklist
- Packages
- Documentation
- Contributing
- License
- Author & Maintainer
- Acknowledgments
Instant GraphQL & REST APIs from your database — with built-in auth, message queues, cron jobs, and real-time subscriptions.
Graphoria connects to your database, introspects the schema, and generates a complete GraphQL and REST API layer — including CRUD operations, relationships, filtering, and ordering. On top of that, it integrates RabbitMQ and Kafka for event-driven workflows, supports scheduled background tasks with cron, and provides custom operations with type-safe handlers.
Built with Bun and TypeScript. Nested relationships resolve as a single SQL statement per request rather than one query per parent row — 100 projects with their tasks is one statement, 5.96 ms at p50 on the reference dataset. See Performance for the method, the hardware, and what that figure excludes.
v0.1.0 — All features are working. Breaking changes are expected before v1.0. See below for details.
Graphoria is under active development. Until v1.0:
- Breaking changes are expected — the configuration shape, API surface, and package structure may change between minor versions.
- Every breaking change is documented in the GitHub release notes with migration guidance.
- Semantic versioning applies from v1.0 onward. Before then, treat each
0.xbump as potentially breaking.
We ship fast and fix forward. If you're evaluating Graphoria for production, pin your version (0.1.0 not ^0.1.0) and read the release notes before upgrading.
- Auto-generated GraphQL API from database schema with zero configuration
- Auto-generated REST API from GraphQL schema for maximum compatibility
- Multi-database support — PostgreSQL, SQL Server (MSSQL), MySQL
- JWT or PASETO authentication with argon2id password hashing and role-based access control (RBAC)
- Row-level security — per-role filters with session variable injection
- Real-time subscriptions via WebSocket (
graphql-wsprotocol) - Message queues — RabbitMQ and Kafka with pub/sub and cache invalidation
- Cron jobs — scheduled background tasks with optional GraphQL query execution
- Custom operations — type-safe query and handler-based endpoints with Zod validation
- Remote GraphQL schemas — stitch external GraphQL APIs into the unified schema
- Remote REST APIs — proxy external OpenAPI services under
/rest - Virtual columns — computed columns powered by SQL expressions or functions
- GraphQL directives —
@where,@truncate,@replace,@concat, and more for data transformation - AI agent — admin-only natural-language → database Q&A, exposed as GraphQL
askquery and REST endpoint - MCP server — Model Context Protocol server so AI editors can explore your schema as tools
- Admin console — web UI at
/_consolefor tables, roles, permissions, API docs, and runtime status - LRU cache with queue-driven invalidation
- Built-in playgrounds — GraphiQL and Scalar API documentation
- OpenAPI spec generation from operations
- React SDK —
@graphoria/reactwith auth hooks, Apollo Client, and route-based access control - Structured logging — pino-based JSON logging with configurable levels
Graphoria gives you an auto-generated GraphQL API with a feature surface comparable to Hasura CE — plus queues, cron, an AI agent, an MCP server, and an admin console — in a single Bun binary. No stitching together five services. No YAML-driven middleware. Write a TypeScript config file and you're done.
| You want… | Hasura CE | PostGraphile | Graphoria |
|---|---|---|---|
| Auto-generated GraphQL + REST | ✅ | ✅ | ✅ |
| PostgreSQL, MySQL, MSSQL | ✅ | ✅ (PG) | ✅ |
| JWT + PASETO auth with RBAC | ✅ | — | ✅ |
| Built-in message queues (RabbitMQ/Kafka) | — | — | ✅ |
| Built-in cron jobs | ✅ | — | ✅ |
| Admin console UI | ✅ | — | ✅ |
| AI agent (NL → DB queries) | — | — | ✅ |
| MCP server (AI editor integration) | — | — | ✅ |
| Remote schema stitching + REST proxy | ✅ | — | ✅ |
| Virtual columns + directives | — | ✅ (PG) | ✅ |
| Runtime | Haskell | Node.js | Bun |
Graphoria is for backend developers and small-to-medium teams who want:
- A comparable feature set to Hasura CE without the operational overhead of Haskell or the limitations of the Community Edition. The comparison is on features, not on benchmarked throughput.
- One binary that handles the API layer, authentication, async workloads (queues + cron), and AI integration — no stitching together five services.
- TypeScript-native configuration — no YAML, no DSL, just a
graphoria.tsfile with full autocomplete. - AI-ready schemas — the built-in MCP server exposes your database as tools to AI editors with zero extra setup.
flowchart TB
subgraph Clients[" "]
GQL["GraphQL<br/>(queries, mutations, subscriptions)"]
REST["REST<br/>(operations, remote proxies)"]
AI["AI / MCP<br/>(agent tools, editor)"]
CONSOLE["Admin Console<br/>(/_console)"]
end
subgraph Graphoria["Graphoria"]
AUTH["Auth<br/>(JWT, PASETO, RBAC)"]
CACHE["Query Cache<br/>(LRU per-role)"]
QUEUES["Queues<br/>(RabbitMQ, Kafka)"]
CRON["Cron<br/>(scheduled jobs)"]
AGENT["AI Agent<br/>(LLM tools)"]
MCP["MCP Server<br/>(schema tools)"]
REMOTE["Remote<br/>(GQL stitch, REST proxy)"]
end
subgraph Data[" "]
PG[("PostgreSQL")]
MSSQL[("SQL Server")]
MYSQL[("MySQL")]
REDIS[("Redis")]
end
Clients --> Graphoria
Graphoria --> Data
What's actively being built or on the near-term roadmap:
| Milestone | Status |
|---|---|
| Website & documentation hub | Planned |
| Configuration stabilization — single source of truth via Zod, final shape lock-in | In progress |
graphoria init CLI — scaffold a project with one command |
Planned |
| Official Docker images — multi-arch, published to GHCR | Planned |
| SQLite support — embedded database for edge and local dev | Planned |
| Rate limiting & throttling — per-role, per-operation | Planned |
| Metrics & tracing — OpenTelemetry integration | Planned |
Want to influence the roadmap? Open an issue or upvote existing ones.
- Bun 1.3.4 or newer
- A running database — PostgreSQL, MySQL, or SQL Server. The examples use PostgreSQL on
localhost:5432. - Redis (or Valkey) — only required if you enable authentication. The default URL is
redis://localhost:6379.
Don't have Postgres/Redis handy? The examples/ folder ships a docker-compose.yml that starts Postgres, Redis, and RabbitMQ with credentials matching the examples below:
docker compose -f examples/docker-compose.yml up -dbun add @graphoria/serverThe examples/taskly/ folder is a complete, ready-to-run demo — a multi-tenant task tracker with auth, RBAC, queues, cron, subscriptions, operations, and a React frontend. Clone the repo, docker compose up, bun run index.ts, and you're live in minutes. Every feature in this README is wired up and documented there.
Secrets are read from the environment, not passed as options. Bun auto-loads a .env file. ADMIN_SECRET is always required (the server will not boot without it), and JWT_SECRET is required for the default JWT auth strategy:
# .env
ADMIN_SECRET=dev-admin-change-me
JWT_SECRET=dev-secret-change-meSee .env.example for every supported variable.
import { createBunServer } from "@graphoria/server";
// ADMIN_SECRET and JWT_SECRET are read from the environment (e.g. a .env file).
const { server, prefixes } = await createBunServer({
port: 3000,
configuration: "./graphoria.ts",
});
console.log(`Server running on http://localhost:${server.port}`);
console.log(`GraphQL: ${prefixes.graphql}`);
console.log(`REST: ${prefixes.rest}`);
console.log(`GraphiQL: ${prefixes.graphiql}`);Graphoria uses pino for structured JSON logging. Set LOG_LEVEL to control verbosity (debug in dev, info in prod by default). In development, pino-pretty formats output for readability.
LOG_LEVEL=trace bun run devTo inject your own pino logger or customize options:
import pino from "pino";
import { createBunServer, configureLogging } from "@graphoria/server";
// Option A: pass via createBunServer
const { server } = await createBunServer({
logger: pino({ level: "trace", redact: ["req.headers.authorization"] }),
});
// Option B: pass pino options (you own full config)
const { server } = await createBunServer({
logger: {
level: "trace",
transport: { target: "pino/file", options: { destination: "/var/log/app.log" } },
},
});
// Option C: call configureLogging before anything else (first-write-wins)
configureLogging({ level: "trace" });Every privileged action is written as one structured record through a pino child logger tagged component: "audit". The records go wherever the rest of the log goes, but they are emitted at info regardless of LOG_LEVEL, so a deployment that runs at warn still gets them. To route them to their own sink, filter on component in a transport of your own (see Logging for injecting one).
| Action | Actor | Target | When |
|---|---|---|---|
admin_secret.used |
admin_secret, address, and at /ai or /mcp the scope |
the endpoint (method, path), websocket or mcp |
Any request that presented the admin secret or a scoped credential |
auth.login |
credentials, username |
auth, with via and (on success) the role |
auth_login over GraphQL or REST; outcome is success or failure |
auth.logout |
the session | auth, with the JTIs it revoked |
auth_logout, when it revoked at least one token |
queue.publish |
the session | the publisher | A queue publisher mutation |
ai.ask |
the session | ai, with via |
The agent over REST or the GraphQL ask field. The record carries the prompt. |
console.login |
admin_secret, address, and on success the scope |
console |
Console login, success or failure |
console.logout |
console, address |
console |
Console logout that revoked a live session |
console.queue.publish |
console, address |
the publisher and routing key | A publish from the console |
console.cron.{trigger,pause,resume} |
console, address |
the job | Cron control from the console |
A record is { action, actor: { type, sub?, role?, ip? }, target: { kind, … }, outcome?, time, … }. Before it is written, any key named password, secret, secrets, token, access_token, refresh_token, authorization or cookie is replaced with [REDACTED], at any depth. A queue message body is never recorded, only the publisher and key. The AI prompt is recorded verbatim, so a prompt that quotes sensitive data lands in the log.
Rate-limit rejections are not audit events: under an attack they would flood the sink.
import { createHandlers } from "@graphoria/server";
const { serverHandlers } = await createHandlers({
configuration: "./graphoria.ts",
});
const server = Bun.serve({
port: 3000,
routes: {
"/health": () => new Response("OK"),
...serverHandlers.routes,
},
websocket: serverHandlers.websocket,
});Create a graphoria.ts configuration file:
import type { ConfigurationFn } from "@graphoria/server/config";
export default (({ operation }) => ({
name: "my-api",
version: "1.0.0", // your API's version — not Graphoria's
databases: [
{
name: "pg",
type: "pg",
enabled: true,
connection: {
host: "localhost",
port: 5432,
user: "postgres",
password: "postgres",
database: "my_app",
},
},
],
auth: {
enabled: true,
database: "pg",
permissions: {
anonymous: { tables: "ALL", operations: "ALL" },
admin: {
tables: "ALL",
storedProcedures: "ALL",
queues: "ALL",
operations: "ALL",
},
},
},
operations: {
getProducts: operation({
query: `query { public_products { id name price } }`,
description: "Get all products",
rest: { path: "/products", method: "GET" },
}),
},
})) satisfies ConfigurationFn;Start the server:
bun run index.tsOpen http://localhost:3000/graphiql in your browser. The playground lists every table from your database, with relationships, filters, ordering, and pagination wired up automatically. Try a query:
query {
public_products(limit: 10, where: { id: { eq: 1 } }) {
id
name
}
}Or hit the REST endpoint declared by the operation above:
curl 'http://localhost:3000/rest/products'Generated GraphQL fields follow the {schema}_{name} pattern by default — e.g. the products table in the public schema becomes public_products. Override it per database with the fieldNaming config field, using the {database}, {schema}, {name}, and {type} placeholders (e.g. "{database}_{schema}_{name}" to disambiguate tables across multiple databases).
| Verb | Path | Description |
|---|---|---|
| GET/POST | /graphql |
GraphQL HTTP. WebSocket upgrade on GET (graphql-ws protocol). |
| GET/POST | /rest/* |
REST API (operations + remote-REST proxies). |
| GET | /graphiql |
Bundled GraphiQL playground. |
| GET | /scalar |
Bundled Scalar API docs. |
| GET | /openapi.json |
Unified OpenAPI spec (operations + remote-REST). |
| POST | /mcp |
Model Context Protocol server (anonymous, opt-in; gate with ADMIN_SECRET or AI_MCP_SECRET). |
| POST | /ai |
AI agent — NL → database Q&A (ADMIN_SECRET or AI_SECRET, opt-in). |
| GET | /_console |
Admin console UI + status APIs (session from ADMIN_SECRET or a console credential, opt-in). |
All paths are configurable via environment variables. Auth: Authorization: Bearer <token>. Admin: x-admin-secret header carrying ADMIN_SECRET or a scoped credential.
| Database | Status | Features |
|---|---|---|
| PostgreSQL | Full support | Tables, views, relationships, stored procs |
| SQL Server (MSSQL) | Full support | Tables, views, relationships, stored procs |
| MySQL | Full support | Tables, views, relationships |
The variables you'll most likely touch for a first run. See .env.example for the full list.
| Variable | Default | Description |
|---|---|---|
ADMIN_SECRET |
(required) | Master secret; sent in the admin header it bypasses RBAC. No default — the server won't boot without it. Comma-separated list to rotate. |
JWT_SECRET |
(required for JWT) | HMAC secret for signing tokens under the default jwt strategy. Comma-separated list to rotate: first signs, all verify. |
PORT |
3000 |
HTTP port the server listens on. |
CACHE_STORE |
memory |
memory or redis. Use redis (and REDIS_URL) when auth is enabled. |
REDIS_URL |
redis://localhost:6379 |
Redis/Valkey URL — required when authentication is enabled. |
AUTH_STRATEGY |
jwt |
jwt, paseto_local, or paseto_public. |
- Server exits immediately on boot —
ADMIN_SECRETis unset. Add it to your.env. - Auth requests fail / token errors — Redis isn't reachable. Start it (
docker compose -f examples/docker-compose.yml up -d redis) and checkREDIS_URL. Cannot connect to database— confirm the database is running and theconnectionblock ingraphoria.tsmatches its host/port/credentials.
Before going to production:
- Set a strong
ADMIN_SECRET— it bypasses all RBAC. - Rotate
JWT_SECRET(or the PASETO keys) — never use the dev default. Each accepts a comma-separated list so a rotation needs no cut-over; see Rotating secrets. - Put Redis behind authentication — set a password and use
redis://user:pass@host:portinREDIS_URL. - Enable CORS properly — set
CORS_ORIGINto your frontend's origin, not*. - Set
RATE_LIMIT_MAXandRATE_LIMIT_ANONYMOUS_MAX— rate limiting ships off, and nothing warns you at boot. - Set
MAX_QUERY_COST— the query cost budget also ships off with no boot warning.100000is the recommended starting value. - Set
CACHE_STORE=redisif you run in cluster mode — with the memory store each worker keeps its own rate-limit bucket. - Use a reverse proxy (nginx, Caddy) for TLS termination.
- Pin your Graphoria version —
0.1.0, not^0.1.0— until v1.0.
The three limits that ship on — query depth, page size and the statement timeout — need no action. Resource Limits has every default, variable and override path.
| Package | Description |
|---|---|
@graphoria/server |
Main server — API generation, auth, queues, cron |
@graphoria/react |
React hooks for auth, Apollo Client, and route-based access control |
Getting started
| Guide | Description |
|---|---|
| Quickstart | Zero to a running server in five minutes |
| Configuration Reference | Full configuration schema — databases, auth, operations, queues, cron |
| API Reference | Complete package exports for server, config, and react |
Features
| Guide | Description |
|---|---|
| Authentication | JWT and PASETO strategies, argon2id passwords, refresh-token rotation |
| Permissions & Access Control | RBAC, row-level filtering, session variables, ordering |
| Resource Limits | Depth, page size, timeouts, rate limiting and the query cost budget |
| Operations | Custom query and handler operations, hooks, caching |
| Cron Jobs | Scheduled background work with cron expressions and ISO datetimes |
| Queues | RabbitMQ and Kafka publishers, subscribers, cache invalidation |
| Subscriptions | GraphQL subscriptions over WebSockets |
| GraphQL Directives | Built-in data-transformation and @when control-flow directives |
| Virtual Columns | Computed columns powered by SQL expressions or functions |
| Performance | Query strategy, caching behaviour, measured numbers and the gate |
| Remote GraphQL Schemas | Stitch external GraphQL APIs into the unified schema |
| Remote REST APIs | Proxy external OpenAPI services under /rest |
| AI Agent | Admin-only natural-language → database Q&A over GraphQL and REST |
| MCP Server | Model Context Protocol server — schema as tools for AI editors |
| Admin Console | Web UI for tables, roles, permissions, API docs, and runtime status |
| React SDK | @graphoria/react hooks, providers, and Apollo integration |
We welcome contributions! Please follow these steps:
git clone https://github.com/graphoria/graphoria.git
cd graphoriabun installcp .env.example .env
bun run dev- Create a feature branch:
git checkout -b feature/amazing-feature - Write tests for new functionality
- Ensure all tests pass:
bun test - Follow the existing code style
- Add documentation for new features
- Commit your changes:
git commit -m 'Add amazing feature' - Push to your branch:
git push origin feature/amazing-feature - Open a Pull Request with a clear description
This project is licensed under the Apache License 2.0 — see the LICENSE and NOTICE files for details.
Copyright 2026 Alex Ferreli
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Alex Ferreli — Creator & Lead Developer
- GitHub: @Alex-Ferreli
- Email: ferreli.ale@gmail.com
- Repository: graphoria
- Inspired by: Hasura — For pioneering instant GraphQL APIs
- Powered by: Bun — For incredible runtime performance
- Built with: GraphQL — For modern API architecture
- TypeScript — For developer experience and type safety
Star this repo if you find it useful!
Made with care by developers, for developers