api-handler

StepLeague API route pattern using withApiHandler wrapper. Use when creating or modifying any API route in the /api directory. Keywords: API, route, endpoint, handler, auth, POST, GET, PUT, DELETE, validation.

16 stars

Best use case

api-handler is best used when you need a repeatable AI agent workflow instead of a one-off prompt.

StepLeague API route pattern using withApiHandler wrapper. Use when creating or modifying any API route in the /api directory. Keywords: API, route, endpoint, handler, auth, POST, GET, PUT, DELETE, validation.

Teams using api-handler should expect a more consistent output, faster repeated execution, less prompt rewriting.

When to use this skill

  • You want a reusable workflow that can be run more than once with consistent structure.

When not to use this skill

  • You only need a quick one-off answer and do not need a reusable workflow.
  • You cannot install or maintain the underlying files, dependencies, or repository context.

Installation

Claude Code / Cursor / Codex

$curl -o ~/.claude/skills/api-handler/SKILL.md --create-dirs "https://raw.githubusercontent.com/diegosouzapw/awesome-omni-skill/main/skills/backend/api-handler/SKILL.md"

Manual Installation

  1. Download SKILL.md from GitHub
  2. Place it in .claude/skills/api-handler/SKILL.md inside your project
  3. Restart your AI agent — it will auto-discover the skill

How api-handler Compares

Feature / Agentapi-handlerStandard Approach
Platform SupportNot specifiedLimited / Varies
Context Awareness High Baseline
Installation ComplexityUnknownN/A

Frequently Asked Questions

What does this skill do?

StepLeague API route pattern using withApiHandler wrapper. Use when creating or modifying any API route in the /api directory. Keywords: API, route, endpoint, handler, auth, POST, GET, PUT, DELETE, validation.

Where can I find the source code?

You can find the source code on GitHub using the link provided at the top of the page.

SKILL.md Source

# API Handler Skill

## Overview

Use `withApiHandler` for all API routes. It eliminates boilerplate and ensures consistent auth, validation, and error handling.

---

## Basic Usage

```typescript
import { withApiHandler } from "@/lib/api/handler";
import { z } from "zod";

const mySchema = z.object({
  name: z.string(),
  count: z.number().optional(),
});

export const POST = withApiHandler({
  auth: 'required',
  schema: mySchema,
}, async ({ user, body, adminClient }) => {
  const { data } = await adminClient
    .from("table")
    .insert({ ...body, user_id: user.id })
    .select()
    .single();
  
  return { success: true, data };
});
```

---

## Auth Levels

| Level | Description | Context Provided |
|-------|-------------|------------------|
| `'none'` | No auth required | `user` may be null |
| `'required'` | Must be logged in | `user` guaranteed |
| `'superadmin'` | Site-wide superadmin | `user` guaranteed, verified superadmin |
| `'league_member'` | Must be league member | `user`, `membership` |
| `'league_admin'` | Must be admin or owner | `user`, `membership` (admin/owner role) |
| `'league_owner'` | Must be owner | `user`, `membership` (owner role) |

### League Auth Examples

```typescript
// Any league member can access
export const GET = withApiHandler({
  auth: 'league_member',
}, async ({ user, membership }) => {
  // membership.role is 'owner', 'admin', or 'member'
  return { role: membership?.role };
});

// Only admins and owners
export const PUT = withApiHandler({
  auth: 'league_admin',
  schema: updateSchema,
}, async ({ user, body, adminClient, membership }) => {
  // membership.role is guaranteed 'admin' or 'owner'
  return { updated: true };
});
```

### League ID Resolution

For league auth, the handler looks for `league_id` in this order:
1. Request body (`{ league_id: "..." }`)
2. URL params (`/api/leagues/[id]`)
3. Query params (`?league_id=...`)

---

## Handler Context

The handler function receives:

```typescript
interface HandlerContext<T> {
  user: User | null;           // Authenticated user
  body: T;                     // Parsed & validated request body
  adminClient: SupabaseClient; // Admin client (bypasses RLS)
  request: Request;            // Original request
  params: Record<string, string>; // URL params (e.g., { id: 'xxx' })
  membership: Membership | null;  // For league_* auth levels
}
```

---

## Schema Validation

Use Zod for request validation:

```typescript
const createSchema = z.object({
  name: z.string().min(1).max(100),
  description: z.string().optional(),
  is_active: z.boolean().default(true),
  league_id: z.string().uuid(),
});

export const POST = withApiHandler({
  auth: 'required',
  schema: createSchema,
}, async ({ body }) => {
  // body is fully typed and validated
  console.log(body.name); // string
  console.log(body.is_active); // boolean (defaulted to true if not provided)
});
```

### Validation Errors

If validation fails, the handler automatically returns:

```json
{
  "error": "Validation failed: name: Required, league_id: Invalid uuid"
}
```

---

## Returning Responses

### Return Object (Auto-wrapped)

```typescript
return { success: true, data };
// Becomes: Response with JSON { success: true, data }
```

### Return Response Directly

```typescript
import { json, badRequest, forbidden } from "@/lib/api";

// Custom status or headers
return json({ data }, { status: 201 });

// Error responses
return badRequest("Invalid input");
return forbidden("Not allowed");
```

---

## Error Handling

Errors thrown in the handler are caught and logged:

```typescript
export const POST = withApiHandler({
  auth: 'required',
}, async ({ adminClient }) => {
  const { data, error } = await adminClient.from("table").insert({});
  
  if (error) {
    // Use AppError for typed errors
    throw new AppError({
      code: ErrorCode.DB_INSERT_FAILED,
      message: error.message,
      context: { table: 'table' },
    });
  }
  
  return { success: true };
});
```

Reference the `error-handling` skill for more on AppError.

---

## Complete Example

```typescript
// src/app/api/leagues/[id]/members/route.ts

import { withApiHandler } from "@/lib/api/handler";
import { z } from "zod";
import { AppError, ErrorCode } from "@/lib/errors";

const addMemberSchema = z.object({
  user_id: z.string().uuid(),
  role: z.enum(['member', 'admin']).default('member'),
});

// GET - List members (any league member can view)
export const GET = withApiHandler({
  auth: 'league_member',
}, async ({ params, adminClient }) => {
  const { data } = await adminClient
    .from("memberships")
    .select("*, users(*)")
    .eq("league_id", params.id);
  
  return { members: data };
});

// POST - Add member (only admins/owners)
export const POST = withApiHandler({
  auth: 'league_admin',
  schema: addMemberSchema,
}, async ({ params, body, adminClient }) => {
  const { data, error } = await adminClient
    .from("memberships")
    .insert({
      league_id: params.id,
      user_id: body.user_id,
      role: body.role,
    })
    .select()
    .single();
  
  if (error) {
    throw new AppError({
      code: ErrorCode.DB_INSERT_FAILED,
      message: "Failed to add member",
      context: { error: error.message },
    });
  }
  
  return { success: true, member: data };
});
```

---

## Legacy Pattern

For existing routes not yet migrated:

```typescript
import { createServerSupabaseClient, createAdminClient } from "@/lib/supabase/server";
import { json, badRequest, unauthorized } from "@/lib/api";

export async function GET(request: Request) {
  const supabase = await createServerSupabaseClient();
  const { data: { user } } = await supabase.auth.getUser();
  if (!user) return unauthorized();

  const adminClient = createAdminClient();
  const { data } = await adminClient.from("table").select("*");
  return json({ data });
}
```

**Rule:** Use `withApiHandler` for all NEW routes. Migrate legacy routes only when modifying them for other reasons.

---

## Reference Files

| File | Purpose |
|------|---------|
| `src/lib/api/handler.ts` | The withApiHandler implementation |
| `src/lib/api.ts` | Response helpers (json, badRequest, etc.) |

---

## Related Skills

- `supabase-patterns` - Database operations with adminClient
- `error-handling` - Error codes and AppError usage
- `architecture-philosophy` - Why we use this pattern

Related Skills

globalexceptionhandler-class

16
from diegosouzapw/awesome-omni-skill

Structure of GlobalExceptionHandler class.

create-event-handlers

16
from diegosouzapw/awesome-omni-skill

Sets up RabbitMQ event publishers and consumers following ModuleImplementationGuide.md Section 9. RabbitMQ only (no Azure Service Bus). Creates publishers with DomainEvent (tenantId preferred), consumers with handlers, naming {domain}.{entity}.{action}, required fields (id, type, version, timestamp, tenantId, source, data). Use when adding event-driven communication, async workflows, or integrating via events.

add-nodebridge-handler

16
from diegosouzapw/awesome-omni-skill

Use this skill when adding a new NodeBridge handler to src/nodeBridge.ts, including updating types in src/nodeBridge.types.ts and optionally testing with scripts/test-nodebridge.ts

bgo

10
from diegosouzapw/awesome-omni-skill

Automates the complete Blender build-go workflow, from building and packaging your extension/add-on to removing old versions, installing, enabling, and launching Blender for quick testing and iteration.

Coding & Development

moai-lang-r

16
from diegosouzapw/awesome-omni-skill

R 4.4+ best practices with testthat 3.2, lintr 3.2, and data analysis patterns.

moai-lang-python

16
from diegosouzapw/awesome-omni-skill

Python 3.13+ development specialist covering FastAPI, Django, async patterns, data science, testing with pytest, and modern Python features. Use when developing Python APIs, web applications, data pipelines, or writing tests.

moai-icons-vector

16
from diegosouzapw/awesome-omni-skill

Vector icon libraries ecosystem guide covering 10+ major libraries with 200K+ icons, including React Icons (35K+), Lucide (1000+), Tabler Icons (5900+), Iconify (200K+), Heroicons, Phosphor, and Radix Icons with implementation patterns, decision trees, and best practices.

moai-foundation-trust

16
from diegosouzapw/awesome-omni-skill

Complete TRUST 4 principles guide covering Test First, Readable, Unified, Secured. Validation methods, enterprise quality gates, metrics, and November 2025 standards. Enterprise v4.0 with 50+ software quality standards references.

moai-foundation-memory

16
from diegosouzapw/awesome-omni-skill

Persistent memory across sessions using MCP Memory Server for user preferences, project context, and learned patterns

moai-foundation-core

16
from diegosouzapw/awesome-omni-skill

MoAI-ADK's foundational principles - TRUST 5, SPEC-First TDD, delegation patterns, token optimization, progressive disclosure, modular architecture, agent catalog, command reference, and execution rules for building AI-powered development workflows

moai-cc-claude-md

16
from diegosouzapw/awesome-omni-skill

Authoring CLAUDE.md Project Instructions. Design project-specific AI guidance, document workflows, define architecture patterns. Use when creating CLAUDE.md files for projects, documenting team standards, or establishing AI collaboration guidelines.

moai-alfred-language-detection

16
from diegosouzapw/awesome-omni-skill

Auto-detects project language and framework from package.json, pyproject.toml, etc.