Skip to content

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
  1. 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"].

  2. Install the packages. graphql 16 is a peer dependency.

    Terminal window
    bun add @nxara/olio @nxara/olio-tools @nxara/olio-http graphql@16
    bun add -d @nxara/olio-codegen typescript @types/bun
  3. Write the module’s SDL. A module extends the root types, and olio adds Query itself.

    src/modules/users/typedefs/users.gql
    type User {
    id: ID!
    name: String!
    }
    extend type Query {
    user(id: ID!): User
    }
  4. Generate the types. There’s no CLI: a short script drives codegen. Every direct child folder of modules is 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, or node codegen.ts on Node 24. It writes src/generated.ts. Rerun it whenever the SDL changes.

  5. 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 };
    }
    }
  6. Write the resolvers and the module. query.users accepts only the fields the users module 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 compiled
    resolvers: await loadResolvers(new URL("query.{js,ts}", import.meta.url))
    });
  7. Compose the app. olio() merges the modules’ SDL, checks ownership and every middleware key, and returns the app. app.schema is a plain GraphQLSchema that 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)
    )
    });
  8. Serve it. Because the app registers a ctx, the handler requires a context hook 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 });
  9. Type-check and start it.

    Terminal window
    bunx tsc
    bun src/server.ts
  10. 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" } } }