@alxia/create

中文文档
0.5.14发布于 2026/11/02MITES Module零依赖近 30 天下载 2907

Start an alxia app: bun create @alxia writes a minimal API, a spec-first one, a GraphQL one, or React Router's official template served by alxia

原始使用文档
文档目录8 节
  1. Templates
  2. Lint and format
  3. Docker
  4. Options
  5. Versions
  6. Other package managers
  7. API
  8. Documentation

@alxia/create

Start an alxia app in one command: a minimal one to try alxia with, an API with Zod written OpenAPI spec first, a GraphQL API written schema first, or React Router's official template served by alxia.

bun create @alxia my-app

bun create @alxia runs this package's bin, as bun create <name> runs create-<name> and bun create @scope runs @scope/create. It asks for what it is not given, writes the project, installs it and prints the next steps:

cd my-app
bun dev

It needs Bun 1.4.2 or later, which the projects it writes run on.

Templates

bun create @alxia my-app --template minimal      # the template to try alxia with
bun create @alxia my-api --template api
bun create @alxia my-graph --template graphql
bun create @alxia my-site --template react-router

minimal is the one to start with: one file, one dependency, a test, and bun dev answering in 30 seconds.

templatewhat it writes
minimalthe smallest alxia app: src/index.ts exports app = alxia().get("/", …) and listens on PORT only when it is the entry file, src/index.spec.ts calls it with app.request(); bun dev (NODE_ENV=development bun --hot), build, start, typecheck, a strict tsconfig.json, Biome and verify (check:ci, typecheck, then test), a Dockerfile running on oven/bun:1-alpine, .dockerignore, .gitignore, .vscode/ and a README. One dependency, @alxia/core: no OpenAPI, no code generation, no validator, no env file
apian @alxia/core app with Zod, OpenAPI spec first: openapi.yaml declares GET /todos, POST /todos and GET /todos/{id}; bun run generate writes src/generated/ from it with @nxgt/openapi-codegen (openapi-codegen.config.ts), committed, so nothing is generated at install or build; src/env.ts reads PORT, API_KEY, API_DOCS and an optional TRUSTED_PROXIES (CIDR ranges, which adds trustProxy({ trusted, untrusted: "refuse" }) to the base when set: ctx.ip from a trusted proxy's X-Forwarded-For, a forwarding header from any other connection refused, health probes untouched) with defineEnv from @alxia/env, API_KEY a secret defaulting to dev-key under NODE_ENV development and test alone, required anywhere else; src/context.ts holds the base the routes read, alxia({ errors: "problem" }) so alxia's own errors are RFC 9457 problems as openapi.yaml declares them, and registers it with @alxia/core's Register; src/routes/todos.ts, made with defineRoutes(), reads that context with no import of the app and binds each operation with route(operation, …middlewares, handler), as route(operations.createTodo, requireKey, handler), where requireKey, made with defineMiddleware, answers 401 without an API key, and the operation's schemas validate the request and check the handler's reply; src/app.ts mounts health() (GET /health, GET /ready), apiDocs from @alxia/openapi (the API reference at /docs, from openapi.yaml imported into the bundle; on in development, else with API_DOCS=true) and the routes on the base; a bun test spec, src/proxy.spec.ts, building the base behind TRUSTED_PROXIES and calling app.fetch with a peer, src/env.spec.ts, checking that a malformed TRUSTED_PROXIES stops the app, and a bun test spec calling the app through the typed @nxgt/openapi-httpyz client over the generated operations.ts, its fetch being app.fetch (in process, no server; replies narrowed on their status), keeping one app.request() test, and asserting matchesSpec from @alxia/openapi: every operation has its route, and strict: true also fails on a route outside the spec; bun dev (NODE_ENV=development) restarting on change, typecheck, build, a strict tsconfig.json, Biome (biome.json, lint, format, check, check:ci), verify, starting with generate --check, a Dockerfile running on oven/bun:1-alpine, .dockerignore, .gitignore, .env.example, .vscode/ and a README. @alxia/openapi is a dependency; @nxgt/httpyz, @nxgt/openapi-httpyz and @nxgt/openapi-codegen are devDependencies
graphqla GraphQL API with GraphQL Yoga through @alxia/graphql, schema first: schema.graphql is the contract (Query, Mutation, Subscription); bun run generate writes src/generated/resolvers.ts from it with GraphQL Code Generator (codegen.ts), committed, typing Resolvers with the app's context; src/context.ts holds the base and viewerOf, a middleware reading Authorization: Bearer <token> so every resolver reads a typed viewer; src/loaders.ts, createLoaders(), the DataLoaders of one request, built per request by the context option of graphql() in src/app.ts so Note.author loads the authors of a list of notes in one batch (findUsers) instead of one call per note; src/resolvers.ts; src/schema.ts and src/app.ts mounting health() and graphql(app, { schema, context }), GraphiQL on in dev alone; src/env.ts (PORT and the optional TRUSTED_PROXIES, as api) with defineEnv; src/store.ts, in memory, with the pub/sub of the noteAdded subscription (server-sent events); a bun test spec posting to /graphql through app.request(), src/proxy.spec.ts, the base behind TRUSTED_PROXIES, and src/env.spec.ts, a malformed one refused; the same scripts, Dockerfile, Biome and .vscode/ as api, verify starting with generate --check, and a .env.example. @alxia/graphql, @alxia/env, dataloader, graphql and graphql-yoga are dependencies; the code generator is a devDependency
react-routerReact Router's official template, as create-react-router writes it, shipped in this package and copied, with @alxia/react-router added as its README says: alxia() in vite.config.ts's plugins, dev under NODE_ENV=development, start running NODE_ENV=production bun build/server/index.js, a bunfig.toml starting React Router's CLI on Bun, a Dockerfile running on oven/bun:1-alpine in place of React Router's Node one, and Biome as the api project has it, the scaffold formatted by it once. No server file: the default one serves the pages; bunx alxia-react-router reveal writes it out to customise

The heart of the api project, its middleware and a route bound to an operation of openapi.yaml (the whole file, and how to add an operation, are in the guide):

// src/context.ts, in part: the base, registered
export const base = alxia().decorate({ todos });

declare module "@alxia/core" {
  interface Register {
    context: typeof base;
  }
}

// src/routes/todos.ts, in part: no import of the app
const requireKey = defineMiddleware(({ request, reply }, next) =>
  request.headers.get("x-api-key") === env.API_KEY
    ? next()
    : reply(401, { error: "unauthorized" as const }),
);

export const todoRoutes = defineRoutes()
  .route(operations.createTodo, requireKey, ({ body, todos, reply }) => {
    const todo = { id: todos.length + 1, title: body.title, done: false };
    todos.push(todo);
    return reply.created(todo);
  });

// src/app.ts
export const app = base.plugin(todoRoutes);

operations.createTodo is POST /todos as openapi.yaml declares it: the body is validated just before the handler, which reads it typed, and the handler's reply is checked against the spec's responses; requireKey's 401 is its own, sent as it is. To change the API, edit openapi.yaml and run bun run generate; never edit src/generated/.

The graphql project's heart, a schema and a resolver reading the typed viewer (the walk-through, with subscriptions and how to add a field, is in the guide):

// schema.graphql, in part
//   type Mutation { addNote(text: String!): Note! }

// src/context.ts, in part: a middleware, then the resolvers' context
const viewerOf = defineMiddleware(({ request }, next) => {
  const token = request.headers.get("authorization")?.match(/^Bearer (.+)$/i)?.[1];
  const id = token === undefined ? undefined : db.tokens.get(token);
  return next({ viewer: (id === undefined ? undefined : db.users.get(id)) ?? null });
});
export const base = alxia().decorate({ env, db }).use(viewerOf);
export type Context = GraphQLContext<typeof base, { loaders: Loaders }>;

// src/resolvers.ts, in part: `Resolvers` is generated from the schema
export const resolvers: Resolvers = {
  Mutation: {
    addNote: (_, { text }, { viewer, db }) => {
      if (viewer === null) {
        throw new GraphQLError("Sign in to add a note", {
          extensions: { code: "UNAUTHENTICATED" },
        });
      }
      // …
    },
  },
  Note: {
    // The notes of one query load their authors in one batch (N+1 otherwise).
    author: (note, _, { loaders }) => loaders.user.load(note.authorId),
  },
};

To change the API, edit schema.graphql and run bun run generate; never edit src/generated/.

Lint and format

Every project ships Biome, set up the same way: a biome.json of its own with Biome's recommended rules, spaces and double quotes, imports sorted, the build output (and .react-router/ or src/generated/, where the project has them) skipped; @biomejs/biome pinned exactly, as Biome asks; and .vscode/ recommending its extension, formatting on save. A new project passes bun run check:ci with no finding.

scriptruns
bun run checkbiome check --write: lint, format and sort imports, fixing what it can
bun run lintbiome lint
bun run formatbiome format --write
bun run check:cibiome ci: what CI runs, read-only
bun run verifyminimal: check:ci, typecheck, then test; api and graphql: generate --check, then those; react-router: check:ci, typecheck, then build
bun run generateapi: nxgt-openapi generate, src/generated/ from openapi.yaml; graphql: graphql-codegen, src/generated/resolvers.ts from schema.graphql; with --check, writes nothing and exits 1 when a file is stale
cd my-api
bun run verify

check:ci and not ci, since bun ci is Bun's frozen-lockfile install, which a script named ci would not replace. The guide has the settings and why.

Docker

Every project builds into an image as it is written: built on oven/bun:1, run on oven/bun:1-alpine as the non-root bun user, an image of about 130 MB. Each Dockerfile builds in a stage of its own, and the image holds the build output alone, no node_modules:

  • minimal: bun run build bundles src/index.ts into dist/index.js; the image holds dist/ and runs bun --no-install dist/index.js.
  • api: bun run build bundles src/server.ts and its dependencies into dist/server.js, src/generated/ included as it is committed, so the build generates nothing; the image holds dist/ and runs bun --no-install dist/server.js.
  • graphql: bun run build bundles src/server.ts, its dependencies and schema.graphql (imported as text) into dist/server.js, src/generated/ included as it is committed; the image holds dist/ and runs bun --no-install dist/server.js, with GraphiQL off (NODE_ENV=production, so alxia's dev switch is off).
  • react-router: bun run build, every dependency bundled into build/server/index.js by @alxia/react-router's plugin; the image holds build/ and runs bun --no-install build/server/index.js.
cd my-api
docker build -t my-api .
docker run -p 3000:3000 -e API_KEY=change-me my-api   # API_KEY is required: the app does not start without it

minimal and graphql run the same way, with no -e: docker run -p 3000:3000 my-app.

cd my-site
docker build -t my-site .
docker run -p 3000:3000 my-site

Commit the bun.lock that bun install wrote: the image installs from it with --frozen-lockfile. The guide has the stages, and troubleshooting what to do for a dependency that cannot be bundled, and for a native addon built for glibc alone, which does not load on Alpine.

Options

option
[dir]where to write the project: a directory that is empty or does not exist yet. Asked when not given
--template <name>, -tminimal, api, graphql or react-router. Asked when not given, minimal by default
--no-installwrite the files, skip bun install
--help, -hthe usage

Given both dir and --template, it asks nothing, so it runs in a script or CI.

Versions

  • alxia's packages — @alxia/core, @alxia/env (api, graphql), @alxia/openapi (api), @alxia/graphql (graphql) and @alxia/react-router (react-router) — are moved to the newest version on the registry within the ranges this release of @alxia/create was published with: ^0.3.1 writes ^0.3.4 once 0.3.4 is out, never ^0.4.0. Just after a release, while the registry does not serve that version yet, the newest of the same minor is written (^0.3.0, which takes 0.3.1 once it arrives), and the output says so. bunx @alxia/create@<version> picks an older set.
  • Everything else — Zod, TypeScript, Vite, React, React Router, GraphQL, Tailwind — is moved to the newest version on the registry when the project is written, within the range alxia's packages accept: TypeScript within ^6.0.3 || ^7.0.0, Vite within ^7.0.0 || ^8.0.0, React Router and its packages within ^8.0.0, Zod within ^4.2.0, graphql within ^16.11.0 || ^17.0.0 and graphql-yoga within ^5.16.0; what no alxia package constrains goes to npm's latest. A newer major outside alxia's range is left out, and the output says so.
  • Biome is pinned exactly, and written exactly: the newest patch of the template's minor, 2.5.15 writing 2.5.16 but never 2.6.0, whose new rules the template was not checked against.
  • The code generators are pinned exactly and kept at the template's versions, never moved to a newer patch: @nxgt/openapi-codegen (api, 0.11.0) and GraphQL Code Generator's @graphql-codegen/cli, typescript and typescript-resolvers (graphql). bun run verify runs generate --check over the committed src/generated/, which a release that writes the files differently would fail in a fresh project.

The registry is the one BUN_CONFIG_REGISTRY or npm_config_registry names, else npmjs.org; when it does not answer, the template's own versions stay, with a warning.

Other package managers

bunx @alxia/create my-app --template minimal
npm create @alxia my-app -- --template minimal

The bin runs on Bun (#!/usr/bin/env bun) whichever runner starts it, and the project it writes installs with bun install.

API

export
create-alxiathe bin bun create @alxia, bunx @alxia/create and npm create @alxia run. The package exports no module

Documentation

版本与发布记录

@alxia/create 共发布 33 个版本,首次发布于 2026/10/04;过去 12 个月发布了 33 个版本。

版本发布日期距今
0.5.14latest2026/11/026 天前
0.5.132026/10/317 天前
0.5.122026/10/2810 天前
0.5.112026/10/2811 天前
0.5.102026/10/2612 天前
0.5.92026/10/2612 天前

关于 @alxia/create 的常见问题

@alxia/create 是什么?

Start an alxia app: bun create @alxia writes a minimal API, a spec-first one, a GraphQL one, or React Router's official template served by alxia

如何安装 @alxia/create?

在项目目录运行 npm install @alxia/create,或使用 pnpm add / yarn add / bun add。它还提供命令行工具 create-alxia,可以用 npx create-alxia 直接运行。

@alxia/create 支持 TypeScript 吗?

该包的 package.json 没有声明类型入口。需要类型提示时,可以在 npm 上查找社区维护的 @types/alxia__create 是否存在。

@alxia/create 支持 ESM 还是 CommonJS?

以 ES Module 形式发布,推荐使用 import 引入。在 CommonJS 项目中,Node.js 20.19+ / 22.12+ 可以直接 require() 不含顶层 await 的 ES Module,更早的版本需要改用动态 import()。

@alxia/create 可以商用吗?

@alxia/create 使用 MIT 许可。宽松许可:允许商业使用、修改和再分发,通常只需在分发时保留原版权与许可声明。以上为概括,不构成法律意见,请以许可原文为准。

@alxia/create 的下载量有多少?

根据 npm 官方下载统计,@alxia/create 在 2026/10/06 – 2026/11/04 这 30 天内被下载 2,907 次,日均约 97 次。下载量会把 CI 构建、镜像同步和作为其他包依赖的安装一并计入,适合横向比较热度,不能直接等同于使用人数。

@alxia/create 还在维护吗?

最新版本 0.5.14 发布于 2026/11/02(6 天前);至今共发布 33 个版本,其中过去 12 个月发布了 33 个;首次发布于 2026/10/04。发布频率只是参考信号:功能稳定的工具可能很少需要更新,建议结合源码仓库的 issue 和提交记录判断。