Skip to content

Quick start

From zero to a running API in 5 minutes.

Scaffold a project

bash
npx @capixjs/cli new my-api
cd my-api
pnpm install
pnpm dev

The server starts at http://localhost:3000. You should see:

[capix] Server started on http://localhost:3000
[capix] Routes:
  GET  /items/:id    items.getItem
  GET  /items        items.listItems
  POST /items        items.createItem

Make a request

bash
curl http://localhost:3000/items
# → { "data": [] }

curl -X POST http://localhost:3000/items \
  -H 'Content-Type: application/json' \
  -d '{"name": "My first item"}'
# → { "data": { "id": "1", "name": "My first item" } }

curl http://localhost:3000/items/1
# → { "data": { "id": "1", "name": "My first item" } }

What was generated

my-api/
├── src/
│   ├── capabilities.ts   # cap and authCap factories
│   ├── context.ts        # buildContext, errors
│   ├── server.ts         # server entry point
│   └── capabilities/
│       └── items/
│           ├── get.ts
│           ├── list.ts
│           └── create.ts
├── tsconfig.json
└── package.json

Add a capability

bash
# Generate a capability file with an input schema
npx capix generate capability users getUser --input

# → src/capabilities/users/get-user.ts

The generated file:

ts
import { z } from 'zod';
import { capability } from '@capixjs/core';

const inputSchema = z.object({
  id: z.string(),
});

export const getUser = capability(inputSchema, async ({ id }, _ctx) => {
  return { id };
});

Register it in your capabilities index:

ts
// src/capabilities/index.ts
import { getUser } from './users/get-user.js';

export const capabilities = {
  items: { getItem, listItems, createItem },
  users: { getUser },  // ← add this line
};

The REST transport infers GET /users/:id from the name getUser automatically.

Add a guard

Guards are preconditions — they run before the resolver and throw to reject the request:

ts
// src/context.ts
import { defineGuard, defineError } from '@capixjs/core';

const errors = {
  Unauthorized: defineError(401, 'Unauthorized'),
};

export const mustBeUser = defineGuard((ctx) => {
  if (!ctx.user) throw errors.Unauthorized();
});

Apply it to your capability:

ts
export const getUser = cap(
  z.object({ id: z.string() }),
  async ({ id }, ctx) => ({ id }),
  'query',
).guard(mustBeUser);

Now any request without a valid user gets 401 Unauthorized.

Next steps

  • Capabilities — the full capability API
  • Guards — guards, input guards, and narrowing
  • Context — building request context
  • Errors — typed error factories
  • Enhancers — caching, rate limiting, circuit breakers
  • Transports — REST, WebSocket, GraphQL, Queue, MCP

Released under the MIT License.