Quick start
This guide builds an API with one module and generated types, and serves it over HTTP. You need Bun, or Node 24+. On Node, the codegen script runs directly and the server runs through tsx.
The finished project looks like this:
- package.json
- tsconfig.json
- codegen.ts
Directorysrc/
- olio.d.ts
- generated.ts (written by codegen)
- app.ts
- server.ts
Directorymodules/
Directoryusers/
- index.ts
- query.ts
Directorytypedefs/
- users.gql
-
Set up the project in an empty folder. olio’s packages are ESM only and the files below use top-level
await, so the project is ESM too:package.json {"type": "module"}tsconfig.json {"compilerOptions": {"module": "nodenext","strict": true,"noEmit": true,"types": ["bun"]}}On Node, use
"types": ["node"]. -
Install the packages.
graphql16 is a peer dependency.Terminal window bun add @nxara/olio @nxara/olio-tools @nxara/olio-http graphql@16bun add -d @nxara/olio-codegen typescript @types/bunTerminal window npm i @nxara/olio @nxara/olio-tools @nxara/olio-http graphql@16npm i -D @nxara/olio-codegen typescript @types/node tsxTerminal window pnpm add @nxara/olio @nxara/olio-tools @nxara/olio-http graphql@16pnpm add -D @nxara/olio-codegen typescript @types/node tsx -
Write the module’s SDL. A module extends the root types, and olio adds
Queryitself.src/modules/users/typedefs/users.gql type User {id: ID!name: String!}extend type Query {user(id: ID!): User} -
Generate the types. There’s no CLI: a short script drives codegen. Every direct child folder of
modulesis a module.codegen.ts import { codegen } from "@nxara/olio-codegen";await codegen({root: import.meta.dirname,modules: "src/modules",typeDefs: "**/typedefs/**/*.gql",output: "src/generated.ts"});Run it with
bun codegen.ts, ornode codegen.tson Node 24. It writessrc/generated.ts. Rerun it whenever the SDL changes. -
Register the app. The builders get their types from here: the generated
Application, and your context type,ctx.src/olio.d.ts import type { Application } from "./generated.js";declare module "@nxara/olio" {interface Olio {application: Application;ctx: { userId?: string };}} -
Write the resolvers and the module.
query.usersaccepts only the fields theusersmodule declares, with their argument and return types.src/modules/users/query.ts import { query } from "@nxara/olio";const users = new Map([["1", { id: "1", name: "Ada" }]]);export const Query = query.users({user: (_root, { id }) => users.get(id) ?? null});src/modules/users/index.ts import { module } from "@nxara/olio";import { loadResolvers, loadTypeDefs } from "@nxara/olio-tools";export const usersModule = module.users({typeDefs: await loadTypeDefs(new URL("typedefs/*.gql", import.meta.url)),// .js too, so the same code finds the file once compiledresolvers: await loadResolvers(new URL("query.{js,ts}", import.meta.url))}); -
Compose the app.
olio()merges the modules’ SDL, checks ownership and every middleware key, and returns the app.app.schemais a plainGraphQLSchemathat any GraphQL server can run.src/app.ts import { olio } from "@nxara/olio";import { loadModules } from "@nxara/olio-tools";export const app = olio({modules: await loadModules(new URL("modules/*/index.{js,ts}", import.meta.url))}); -
Serve it. Because the app registers a
ctx, the handler requires acontexthook that builds it from each request.src/server.ts import { fetchHandler } from "@nxara/olio-http/fetch";import { app } from "./app.js";const graphql = fetchHandler(app, {context: (request) => ({userId: request.headers.get("x-user-id") ?? undefined})});Bun.serve({ port: 4000, fetch: graphql });src/server.ts import { createServer } from "node:http";import { nodeHandler } from "@nxara/olio-http/node";import { app } from "./app.js";const graphql = nodeHandler(app, {context: (req) => ({ userId: req.headersDistinct["x-user-id"]?.[0] })});createServer(graphql).listen(4000);nodeHandleralso plugs into Express or any framework built onnode:http. -
Type-check and start it.
Terminal window bunx tscbun src/server.tsTerminal window npx tscnpx tsx src/server.ts -
Query it from another terminal.
Terminal window curl localhost:4000/graphql \-H 'content-type: application/json' \-d '{"query":"{ user(id: \"1\") { name } }"}'{ "data": { "user": { "name": "Ada" } } }
Where to next
Section titled “Where to next”- Add a second module: create another folder under
modules/, then rerun codegen. The new module canextend type Userto add fields of its own. - Add middleware for auth and logging, at the app, module or feature level, down to single fields.
- See how the pieces fit and how to lay out a project.
- Split a growing module into features.
- Call the API from a typed client with
@nxara/olio-client.