# Hand-Crafted Setup

If you have an existing project and want to add Quickback-generated API endpoints, you can set up the definitions directory manually instead of using a template.

## Prerequisites

- An existing Hono-based project on Cloudflare Workers (the only supported runtime target)
- Node.js 18+ to run the CLI
- The Quickback CLI: `npm install -g @quickback-dev/cli`

## Setup

### 1. Create the Quickback Directory

Create a `quickback/` directory in your project root with the following structure:

```
your-project/
├── quickback/
│   ├── quickback.config.ts
│   └── features/
│       └── (your features go here)
├── src/                          # Your existing code
├── package.json
└── ...
```

```bash
mkdir -p quickback/features
```

### 2. Write Your Config

Create `quickback/quickback.config.ts`:

```typescript
import { defineConfig } from "@quickback/compiler";

export default defineConfig({
  name: "my-app",
  template: "hono",
  features: {
    organizations: true,
  },
  auth: {
    // Required for "+" role expansion in access rules (e.g. roles: ["member+"]).
    // Lowest → highest. Omit if you always list roles explicitly.
    roleHierarchy: ["member", "admin", "owner"],
  },
  providers: {
    runtime: { name: "cloudflare", config: {} },
    database: {
      name: "cloudflare-d1",
      config: { binding: "DB" },
    },
    auth: { name: "better-auth", config: {} },
  },
});
```

Adjust the providers to match your existing stack. See [Providers](/configure/providers) for all options.

### 3. Create Your First Feature

Create a feature directory with a schema + security definition:

```bash
mkdir quickback/features/candidates
```

Create `quickback/features/candidates/candidates.ts`:

```typescript
import { sqliteTable, text } from "drizzle-orm/sqlite-core";
import { defineTable } from "@quickback/compiler";

export const candidates = sqliteTable("candidates", {
  id: text("id").primaryKey(),
  name: text("name").notNull(),
  email: text("email").notNull(),
  phone: text("phone"),
  source: text("source"),
  organizationId: text("organization_id").notNull(),

  // Compiler-managed columns. Every feature table declares the audit quartet;
  // the deletedAt/deletedBy pair is required because this resource soft-deletes.
  // In the `q` DSL these collapse to `...q.audit()` / `...q.softDelete()` —
  // raw Drizzle tables spell them out. `quickback migrate visible-columns`
  // writes these lines for you.
  createdAt: text("created_at").notNull().default('1970-01-01T00:00:00.000Z').$defaultFn(() => new Date().toISOString()),
  modifiedAt: text("modified_at").notNull().default('1970-01-01T00:00:00.000Z').$defaultFn(() => new Date().toISOString()).$onUpdate(() => new Date().toISOString()),
  createdBy: text("created_by"),
  modifiedBy: text("modified_by"),
  deletedAt: text("deleted_at"),
  deletedBy: text("deleted_by"),
});

export default defineTable(candidates, {
  firewall: [{ field: 'organizationId', equals: 'ctx.activeOrgId' }],
  guards: {
    createable: ["name", "email", "phone", "source"],
    updatable: ["name", "phone"],
  },
  masking: {
    email: { type: "email", show: { roles: ["hiring-manager", "recruiter"] } },
    phone: { type: "phone", show: { roles: ["hiring-manager", "recruiter"] } },
  },
  read: {
    access: { roles: ["owner", "hiring-manager", "recruiter", "interviewer"] },
  },
  create: { access: { roles: ["owner", "hiring-manager", "recruiter"] } },
  update: { access: { roles: ["owner", "hiring-manager", "recruiter"] } },
  delete: { access: { roles: ["owner", "hiring-manager"] }, mode: "soft" },
});
```

### 4. Log In and Compile

```bash
quickback login
quickback compile
```

The compiler generates a complete `src/` directory with route handlers, middleware, database schemas, and migrations.

### 5. Integrate with Your Existing Code

The compiled output creates a self-contained Hono app in `src/index.ts`. If you need to integrate the generated routes into an existing Hono app, you can import the feature routes directly:

```typescript
import { Hono } from "hono";
import candidatesRoutes from "./features/candidates/routes";

const app = new Hono();

// Your existing routes
app.get("/", (c) => c.json({ status: "ok" }));

// Mount generated feature routes
app.route("/api/v1/candidates", candidatesRoutes);

export default app;
```

## Directory Structure

The compiler expects this structure inside `quickback/`:

```
quickback/
├── quickback.config.ts           # Required: compiler configuration
└── features/                     # Required: feature definitions
    ├── candidates/
    │   ├── candidates.ts          # Schema + security (defineTable)
    │   └── actions/               # Optional: one file per action
    │       └── shortlist.ts
    ├── jobs/
    │   ├── jobs.ts
    │   └── actions/
    │       └── close.ts
    └── ...
```

Each feature directory should contain:
- **`{name}.ts`** — The main definition file using `defineTable()` (required)
- **`actions/<name>.ts`** — One custom action per file, `export default defineAction({ ... })` (optional)

The action file imports its `defineAction` from the generated per-feature helper,
which bakes in the record's row type:

```typescript
// quickback/features/candidates/actions/shortlist.ts
import { z } from "zod";
import { defineAction } from "../.quickback/define-action";

export default defineAction({
  description: "Shortlist a candidate for the hiring manager's review.",
  input: z.object({}),
  access: { roles: ["hiring-manager"] },
  async execute({ db, record }) { /* ... */ },
});
```

`path:` is what makes an action standalone. Without it the action binds to `:id`
and receives `record`.

## Adding Features

To add a new feature, create a new directory under `quickback/features/` and recompile:

```bash
mkdir quickback/features/jobs
# Create jobs/jobs.ts with defineTable(...)
quickback compile
```

The compiler detects all features automatically — no registration needed.

## Recompiling

After any change to your definitions, recompile to regenerate the output:

```bash
quickback compile
```

The compiler regenerates the entire `src/` directory. Your definitions in `quickback/` are the source of truth — never edit the generated files directly.

**Warning:** Don't edit files in `src/` manually. They will be overwritten on the next compile. All changes should be made in your `quickback/` definitions.

## Next Steps

- [Configuration reference](/configure) — All config options
- [Schema definitions](/define/schema) — Define tables with `feature()` / `defineTable()`
- [Security pillars](/define) — Firewall, Access, Guards, Masking
- [Templates](/start/templates) — Use a template for new projects instead
