Curated Skills
by lstudlo

cloudflare

references/pages/api.md

.md 205 lines
Content
# Functions API

## File-Based Routing

```
/functions/index.ts              → example.com/
/functions/api/users.ts          → example.com/api/users
/functions/api/users/[id].ts     → example.com/api/users/:id
/functions/api/users/[[path]].ts → example.com/api/users/* (catchall)
/functions/_middleware.ts        → Runs before all routes
```

**Rules**: `[param]` = single segment, `[[param]]` = multi-segment catchall, more specific wins.

## Request Handlers

```typescript
import type { PagesFunction } from '@cloudflare/workers-types';

interface Env {
  DB: D1Database;
  KV: KVNamespace;
}

// All methods
export const onRequest: PagesFunction<Env> = async (context) => {
  return new Response('All methods');
};

// Method-specific
export const onRequestGet: PagesFunction<Env> = async (context) => {
  const { request, env, params, data } = context;
  
  const user = await env.DB.prepare(
    'SELECT * FROM users WHERE id = ?'
  ).bind(params.id).first();
  
  return Response.json(user);
};

export const onRequestPost: PagesFunction<Env> = async (context) => {
  const body = await context.request.json();
  return Response.json({ success: true });
};

// Also: onRequestPut, onRequestPatch, onRequestDelete, onRequestHead, onRequestOptions
```

## Context Object

```typescript
interface EventContext<Env, Params, Data> {
  request: Request;              // HTTP request
  env: Env;                      // Bindings (KV, D1, R2, etc.)
  params: Params;                // Route parameters
  data: Data;                    // Middleware-shared data
  waitUntil: (promise: Promise<any>) => void;  // Background tasks
  next: () => Promise<Response>; // Next handler
  passThroughOnException: () => void;  // Error fallback (not in advanced mode)
}
```

## Dynamic Routes

```typescript
// Single segment: functions/users/[id].ts
export const onRequestGet: PagesFunction = async ({ params }) => {
  // /users/123 → params.id = "123"
  return Response.json({ userId: params.id });
};

// Multi-segment: functions/files/[[path]].ts
export const onRequestGet: PagesFunction = async ({ params }) => {
  // /files/docs/api/v1.md → params.path = ["docs", "api", "v1.md"]
  const filePath = (params.path as string[]).join('/');
  return new Response(filePath);
};
```

## Middleware

```typescript
// functions/_middleware.ts
// Single
export const onRequest: PagesFunction = async (context) => {
  const response = await context.next();
  response.headers.set('X-Custom-Header', 'value');
  return response;
};

// Chained (runs in order)
const errorHandler: PagesFunction = async (context) => {
  try {
    return await context.next();
  } catch (err) {
    return new Response(err.message, { status: 500 });
  }
};

const auth: PagesFunction = async (context) => {
  const token = context.request.headers.get('Authorization');
  if (!token) return new Response('Unauthorized', { status: 401 });
  context.data.userId = await verifyToken(token);
  return context.next();
};

export const onRequest = [errorHandler, auth];
```

**Scope**: `functions/_middleware.ts` → all; `functions/api/_middleware.ts` → `/api/*` only

## Bindings Usage

```typescript
export const onRequestGet: PagesFunction<Env> = async ({ env }) => {
  // KV
  const cached = await env.KV.get('key', 'json');
  await env.KV.put('key', JSON.stringify({data: 'value'}), {expirationTtl: 3600});
  
  // D1
  const result = await env.DB.prepare('SELECT * FROM users WHERE id = ?').bind(userId).first();
  
  // R2, Queue, AI - see respective reference docs
  
  return Response.json({success: true});
};
```

## Advanced Mode

Full Workers API, bypasses file-based routing:

```javascript
// functions/_worker.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);
    
    // Custom routing
    if (url.pathname.startsWith('/api/')) {
      return new Response('API response');
    }
    
    // REQUIRED: Serve static assets
    return env.ASSETS.fetch(request);
  }
};
```

**When to use**: WebSockets, complex routing, scheduled handlers, email handlers.

## Smart Placement

Automatically optimizes function execution location based on traffic patterns.

**Configuration** (in wrangler.jsonc):
```jsonc
{
  "placement": {
    "mode": "smart"  // Enables optimization (default: off)
  }
}
```

**How it works**: Analyzes traffic patterns over time and places functions closer to users or data sources (e.g., D1 databases). Requires no code changes.

**Trade-offs**: Initial requests may see slightly higher latency during learning period (hours-days). Performance improves as system optimizes.

**When to use**: Global apps with centralized databases or geographically concentrated traffic sources.

## getRequestContext (Framework SSR)

Access bindings in framework code:

```typescript
// SvelteKit
import type { RequestEvent } from '@sveltejs/kit';
export async function load({ platform }: RequestEvent) {
  const data = await platform.env.DB.prepare('SELECT * FROM users').all();
  return { users: data.results };
}

// Astro
const { DB } = Astro.locals.runtime.env;
const data = await DB.prepare('SELECT * FROM users').all();

// Solid Start (server function)
import { getRequestEvent } from 'solid-js/web';
const event = getRequestEvent();
const data = await event.locals.runtime.env.DB.prepare('SELECT * FROM users').all();
```

**✅ Supported adapters** (2026):
- **SvelteKit**: `@sveltejs/adapter-cloudflare`
- **Astro**: Built-in Cloudflare adapter
- **Nuxt**: Set `nitro.preset: 'cloudflare-pages'` in `nuxt.config.ts`
- **Qwik**: Built-in Cloudflare adapter
- **Solid Start**: `@solidjs/start-cloudflare-pages`

**❌ Deprecated/Unsupported**:
- **Next.js**: Official adapter (`@cloudflare/next-on-pages`) deprecated. Use Vercel or self-host on Workers.
- **Remix**: Official adapter (`@remix-run/cloudflare-pages`) deprecated. Migrate to supported frameworks.

See [gotchas.md](./gotchas.md#framework-specific) for migration guidance.