Many modules.
One schema.
olio builds GraphQL servers out of small, self-contained modules. Each module owns its SDL, resolvers and middleware. olio composes them into one schema, and codegen checks every resolver at compile time.
- Modules own their fields. No one else can resolve them.
- Typed from your SDL. Resolvers, args and middleware keys.
- Runs anywhere. app.schema works with any GraphQL server.
npm i @nxara/olio graphqlBuilt for APIs that keep growing
One schema file works on day one. A few months later it's the file everyone edits and nobody owns. olio gives every part of your API a home from the start.
Modules own their fields
A module resolves what its SDL declares, and nothing else. Another module's field is a compile error, at the field.
How ownership works →Typed from your SDL
Codegen turns the schema into types. Every resolver, argument and middleware key is checked as you write it.
Generate types →Middleware in layers
App, module and feature each add a layer. A field opts out with @bypass in its SDL, and each middleware decides.
Layered middleware →Features for big modules
Split a growing module into features: subfolders that own part of its schema, with builders of their own.
Split a module →Test one module
Root types come for free, so a module composes on its own: olio({ modules: [users] }).
Testing modules →Errors that point at files
SDL and schema errors come back as file:line:column, so the fix is one click away.
Composition checks →A module is a slice of your schema
It holds the SDL it declares, the resolvers that answer it and the middleware around them. It can extend other modules' types, so each part of the API stays small and readable.
// src/modules/users/index.ts
import { module, mw } from "@nxara/olio";
import { loadTypeDefs } from "@nxara/olio-tools";
export const usersModule = module.users({
typeDefs: await loadTypeDefs(new URL("typedefs/*.gql", import.meta.url)),
resolvers: {
Query: { user: (root, { id }) => findUser(id) }, // id: string
User: { name: (user) => user.name } // user: what User maps to
},
middleware: mw.users({ Query: { user: [cache()] } })
});Four steps, each one line that matters
- 1
Write SDL
Each module keeps its schema next to its code.
extend type Query { user(id: ID!): User } - 2
Generate types
One script, one generated.ts for the whole app.
codegen({ modules: "src/modules" }) - 3
Compose
olio merges the modules and checks every key.
olio({ modules, middleware }) - 4
Serve
Fetch or Node, on the framework you already use.
fetchHandler(app, { context })
Add only what you need
Start with the core. Every other package is optional, and they all share one version.
Start with one module.
Add the next one when you need it.