# Project Structure

Cloudwerk uses a file-based routing system where your directory structure defines your application's routes. This guide covers all the file conventions you need to know.

## Overview

A typical Cloudwerk project has this structure:

<FileTree>
- app/
  - page.tsx          # Home page (/)
  - layout.tsx        # Root layout
  - middleware.ts     # Global middleware
  - error.tsx         # Error boundary
  - not-found.tsx     # 404 page
  - about/
    - page.tsx        # About page (/about)
  - users/
    - page.tsx        # Users list (/users)
    - [id]/
      - page.tsx      # User profile (/users/:id)
      - edit/
        - page.tsx    # Edit user (/users/:id/edit)
  - api/
    - health/
      - route.ts      # Health endpoint (/api/health)
    - users/
      - route.ts      # Users API (/api/users)
      - [id]/
        - route.ts    # User API (/api/users/:id)
  - auth/             # Authentication config
    - config.ts       # Auth configuration
    - callbacks.ts    # Auth callbacks
    - rbac.ts         # Role definitions
    - providers/
      - github.ts     # OAuth provider
      - credentials.ts # Email/password
  - queues/           # Queue consumers
    - email.ts        # Email queue
    - notifications.ts # Notifications queue
  - services/         # Service definitions
    - email/
      - service.ts    # Email service
    - payments/
      - service.ts    # Payments service
  - objects/          # Durable Objects
    - counter.ts      # Counter DO
    - chat-room.ts    # Chat room DO
  - triggers/         # Event triggers
    - daily-cleanup.ts # Scheduled trigger
    - stripe-webhook.ts # Webhook trigger
- public/             # Static assets
- cloudwerk.config.ts # Configuration
- wrangler.toml       # Cloudflare config
- package.json
- tsconfig.json
</FileTree>

## File Conventions

### page.tsx

The `page.tsx` file defines a publicly accessible page at that route:

```tsx
// app/about/page.tsx -> /about
export default function AboutPage() {
  return <h1>About Us</h1>;
}
```

Pages can export a `loader()` function for server-side data fetching:

```tsx
export async function loader({ params, context }: LoaderArgs) {
  return { data: await fetchData() };
}

export default function Page({ data }: PageProps & { data: Data }) {
  return <div>{/* render data */}</div>;
}
```

### layout.tsx

The `layout.tsx` file wraps pages and nested layouts:

```tsx
// app/layout.tsx
export default function RootLayout({ children }: LayoutProps) {
  return (
    <html>
      <body>{children}</body>
    </html>
  );
}
```

Layouts can also have their own `loader()` functions:

```tsx
export async function loader() {
  const user = getUser()
  const session = getSession()
  return { user, session }
}

export default function DashboardLayout({ children, user }: LayoutProps & { user: User | null }) {
  return (
    <div>
      <Sidebar user={user} />
      <main>{children}</main>
    </div>
  );
}
```

### route.ts

The `route.ts` file defines API endpoints that handle HTTP methods:

```typescript
// app/api/users/route.ts
export async function GET() {
  const { results: users } = await DB.prepare('SELECT * FROM users').all()
  return json(users)
}

export async function POST(request: Request) {
  const body = await request.json()
  const id = crypto.randomUUID()
  await DB
    .prepare('INSERT INTO users (id, name, email) VALUES (?, ?, ?)')
    .bind(id, body.name, body.email)
    .run()
  return json({ id, ...body }, { status: 201 })
}
```

Import bindings directly from `@cloudwerk/core/bindings` for clean, ergonomic access. Run `cloudwerk bindings generate-types` to enable TypeScript autocomplete.

Supported methods: `GET`, `POST`, `PUT`, `PATCH`, `DELETE`, `HEAD`, `OPTIONS`

### middleware.ts

The `middleware.ts` file runs before route handlers:

```typescript
// app/middleware.ts
export const middleware: Middleware = async (request, next) => {
  // Run before the route handler
  console.log(`${request.method} ${request.url}`);

  const response = await next();

  // Run after the route handler
  return response;
};
```

Middleware is inherited by child routes. Place middleware at any level:

<FileTree>
- app/
  - middleware.ts       # Runs for all routes
  - admin/
    - middleware.ts     # Runs for /admin/* routes
    - page.tsx
</FileTree>

### error.tsx

The `error.tsx` file handles errors in that route segment:

```tsx
// app/error.tsx
export default function ErrorPage({ error, errorType }: ErrorBoundaryProps) {
  return (
    <div>
      <h1>Something went wrong</h1>
      <p>{error.message}</p>
      {error.digest && <p>Error ID: {error.digest}</p>}
      <a href="/">Go home</a>
    </div>
  );
}
```

### not-found.tsx

The `not-found.tsx` file handles 404 errors:

```tsx
// app/not-found.tsx
export default function NotFoundPage() {
  return (
    <div>
      <h1>404 - Page Not Found</h1>
      <a href="/">Go home</a>
    </div>
  );
}
```

---

## Convention Directories

Cloudwerk uses convention directories for authentication, queues, services, durable objects, and triggers. These are automatically discovered and registered.

### app/auth/ - Authentication

Authentication configuration files:

<FileTree>
- app/auth/
  - config.ts         # Main auth configuration
  - callbacks.ts      # Lifecycle callbacks
  - pages.ts          # Custom page paths
  - rbac.ts           # Role and permission definitions
  - providers/
    - github.ts       # OAuth provider
    - google.ts       # OIDC provider
    - credentials.ts  # Email/password
    - email.ts        # Magic link
</FileTree>

```typescript
// app/auth/config.ts
export default defineAuthConfig({
  basePath: '/auth',
  session: {
    strategy: 'database',
    maxAge: 30 * 24 * 60 * 60,
  },
})

// app/auth/providers/github.ts
export default defineProvider(
  github({
    clientId: process.env.GITHUB_CLIENT_ID!,
    clientSecret: process.env.GITHUB_CLIENT_SECRET!,
  })
)
```

<Aside type="tip">
See the [Authentication Guide](/guides/authentication/) for complete documentation.
</Aside>

### app/queues/ - Queue Consumers

Queue consumers are defined as individual files:

<FileTree>
- app/queues/
  - email.ts              # email queue
  - image-processing.ts   # imageProcessing queue
  - notifications.ts      # notifications queue
</FileTree>

File names are converted to camelCase queue names.

```typescript
// app/queues/email.ts
interface EmailMessage {
  to: string
  subject: string
  body: string
}

export default defineQueue<EmailMessage>({
  async process(message) {
    await sendEmail(message.body)
    message.ack()
  },
})
```

<Aside type="tip">
See the [Queues Guide](/guides/queues/) for complete documentation.
</Aside>

### app/services/ - Service Definitions

Services are defined in named directories:

<FileTree>
- app/services/
  - email/
    - service.ts      # Required: service definition
    - utils.ts        # Optional: helper utilities
  - payments/
    - service.ts
  - analytics/
    - service.ts
</FileTree>

Directory names are converted to camelCase service names (`email/` → `email`, `user-management/` → `userManagement`).

```typescript
// app/services/email/service.ts
export default defineService({
  methods: {
    async send({ to, subject, body }) {
      const response = await fetch('https://api.resend.com/emails', {
        method: 'POST',
        headers: {
          'Authorization': `Bearer ${this.env.RESEND_API_KEY}`,
          'Content-Type': 'application/json',
        },
        body: JSON.stringify({ to, subject, html: body }),
      })
      return response.json()
    },
  },
})
```

<Aside type="tip">
See the [Services Guide](/guides/services/) for complete documentation.
</Aside>

### app/objects/ - Durable Objects

Durable Objects are defined as individual files:

<FileTree>
- app/objects/
  - counter.ts        # Counter DO
  - chat-room.ts      # ChatRoom DO
  - game-session.ts   # GameSession DO
</FileTree>

File names are converted to PascalCase class names.

```typescript
// app/objects/counter.ts
interface CounterState {
  count: number
}

export default defineDurableObject<CounterState>({
  init: async (ctx) => ({ count: 0 }),

  methods: {
    async increment(ctx, amount: number = 1) {
      ctx.setState({ count: ctx.state.count + amount })
      await ctx.storage.put('state', ctx.state)
      return ctx.state.count
    },

    async getCount(ctx) {
      return ctx.state.count
    },
  },
})
```

<Aside type="tip">
See the [Durable Objects Guide](/guides/durable-objects/) for complete documentation.
</Aside>

### app/triggers/ - Event Triggers

Event triggers are defined as individual files:

<FileTree>
- app/triggers/
  - daily-cleanup.ts      # Scheduled trigger
  - stripe-webhook.ts     # Webhook trigger
  - image-uploaded.ts     # R2 trigger
  - support-email.ts      # Email trigger
</FileTree>

```typescript
// app/triggers/daily-cleanup.ts
export default defineTrigger({
  source: {
    type: 'scheduled',
    cron: '0 0 * * *',  // Daily at midnight
  },
  async handle(event, ctx) {
    await ctx.env.DB.exec('DELETE FROM sessions WHERE expires_at < datetime("now")')
  },
})

// app/triggers/stripe-webhook.ts
export default defineTrigger({
  source: {
    type: 'webhook',
    path: '/webhooks/stripe',
    verifier: stripeVerifier({
      secret: process.env.STRIPE_WEBHOOK_SECRET!,
    }),
  },
  async handle(event, ctx) {
    const stripeEvent = event.payload
    if (stripeEvent.type === 'checkout.session.completed') {
      await fulfillOrder(stripeEvent.data.object)
    }
  },
})
```

<Aside type="tip">
See the [Triggers Guide](/guides/triggers/) for complete documentation.
</Aside>

---

## Dynamic Routes

### Single Segment

Use brackets for dynamic segments:

```
app/users/[id]/page.tsx -> /users/:id
```

Access params in your loader or component:

```tsx
export async function loader({ params }: LoaderArgs) {
  // params.id is the dynamic value
  return { user: await getUser(params.id) };
}
```

### Catch-All Segments

Use `[...slug]` to catch all subsequent segments:

```
app/docs/[...slug]/page.tsx -> /docs/*
```

```tsx
export async function loader({ params }: LoaderArgs) {
  // params.slug is an array like ['guides', 'routing']
  const path = params.slug.join('/');
  return { doc: await getDoc(path) };
}
```

### Optional Catch-All

Use `[[...slug]]` for optional catch-all:

```
app/shop/[[...categories]]/page.tsx
-> /shop
-> /shop/electronics
-> /shop/electronics/phones
```

## Route Groups

Use `(groupName)` to organize routes without affecting the URL:

<FileTree>
- app/
  - (marketing)/
    - page.tsx          # /
    - about/
      - page.tsx        # /about
  - (dashboard)/
    - layout.tsx        # Dashboard layout
    - dashboard/
      - page.tsx        # /dashboard
    - settings/
      - page.tsx        # /settings
</FileTree>

## Colocation

You can colocate components, utilities, and tests alongside your routes:

<FileTree>
- app/
  - users/
    - page.tsx
    - UserCard.tsx        # Component (not a route)
    - user.utils.ts       # Utility (not a route)
    - page.test.ts        # Test (not a route)
</FileTree>

Only `page.tsx`, `layout.tsx`, `route.ts`, and `middleware.ts` are treated as routes.

## Next Steps

- **[Routing Guide](/guides/routing/)** - Deep dive into routing patterns
- **[Data Loading](/guides/data-loading/)** - Server-side data fetching
- **[Authentication](/guides/authentication/)** - Add auth to your app
- **[Services](/guides/services/)** - Build reusable services
- **[Queues](/guides/queues/)** - Background job processing
- **[Triggers](/guides/triggers/)** - Event-driven architecture