AgentSkillsCN

wix-cli-backend-api

在构建REST API端点、服务器端数据处理,或后端HTTP处理器时,此技能可助你精准定位需求——只有当用户明确要求构建后端端点时,才可启用此技能。触发条件包括“API”、“端点”、“后端”、“服务器路由”、“HTTP方法”、“CRUD操作”、“数据变更”、“服务器端验证”、“表单处理”等。

SKILL.md
--- frontmatter
name: wix-cli-backend-api
description: Use when building REST API endpoints, server-side data processing, or backend HTTP handlers. Use only when the user specifically asks for a backend endpoint. Triggers include API, endpoint, backend, server route, HTTP methods, CRUD operations, data mutations, server-side validation, form handling.
compatibility: Requires Wix CLI development environment.

Wix Backend API Builder

Creates Astro server endpoint extensions for Wix CLI applications. Backend APIs are server-side routes that handle HTTP requests, process data, and return responses. They are automatically discovered and don't require extension registration. Endpoints are generated based on API specifications and are designed to integrate well with frontend applications in fullstack applications.

Architecture

Astro server endpoints are TypeScript files (.ts extension) in the src/pages/api/ directory that export named functions for HTTP methods. They are automatically converted to API routes.

Key characteristics:

  • Files use the .ts extension
  • Files export named functions: GET, POST, PUT, DELETE, PATCH
  • Always import APIRoute type from 'astro' for proper typing
  • Always return Response objects
  • Use async/await for asynchronous operations
  • No extension registration needed (auto-discovered)

File Structure and Naming

Basic Endpoint

File path determines the endpoint URL:

code
src/pages/api/users.ts → /api/users

Dynamic Routes

Use square brackets for dynamic parameters:

code
src/pages/api/users/[id].ts → /api/users/:id
src/pages/api/posts/[slug].ts → /api/posts/:slug
src/pages/api/users/[userId]/posts/[postId].ts → /api/users/:userId/posts/:postId

HTTP Methods

Export named functions for each HTTP method you want to support. Always import the APIRoute type from 'astro' for proper typing:

typescript
import type { APIRoute } from "astro";

export async function GET({ params, request }) {
  // Handle GET request
}

export async function POST({ params, request }) {
  // Handle POST request
}

export async function PUT({ params, request }) {
  // Handle PUT request
}

export async function DELETE({ params, request }) {
  // Handle DELETE request
}

export async function PATCH({ params, request }) {
  // Handle PATCH request
}

Request Handling

Path Parameters

Extract dynamic route parameters from params:

typescript
export async function GET({ params }) {
  const { id } = params; // From /api/users/[id]

  if (!id) {
    return new Response(JSON.stringify({ error: "ID required" }), {
      status: 400,
      statusText: "Bad Request",
      headers: { "Content-Type": "application/json" },
    });
  }

  // Use id to fetch data
}

Query Parameters

Extract query string parameters from request.url using new URL(request.url).searchParams:

typescript
export async function GET({ request }) {
  const url = new URL(request.url);
  const search = url.searchParams.get("search");
  const limit = parseInt(url.searchParams.get("limit") || "10", 10);
  const offset = parseInt(url.searchParams.get("offset") || "0", 10);

  // Use query parameters
}

Note: Use request.url to access the full URL, and new URL(request.url).searchParams for query parameters.

Request Body

Parse JSON body from POST/PUT requests:

typescript
export async function POST({ request }) {
  try {
    const body = await request.json();
    const { title, content } = body;

    // Validate required fields
    if (!title || !content) {
      return new Response(
        JSON.stringify({ error: "Title and content required" }),
        {
          status: 400,
          statusText: "Bad Request",
          headers: { "Content-Type": "application/json" },
        }
      );
    }

    // Process data
  } catch (error) {
    return new Response(JSON.stringify({ error: "Invalid JSON" }), {
      status: 400,
      statusText: "Bad Request",
      headers: { "Content-Type": "application/json" },
    });
  }
}

Headers

Access request headers:

typescript
export async function GET({ request }) {
  const authHeader = request.headers.get("Authorization");
  const contentType = request.headers.get("Content-Type");

  // Use headers
}

Response Patterns

Always return a Response object with appropriate status codes and headers. Use JSON.stringify() for JSON responses:

Success Response

typescript
return new Response(JSON.stringify({ data: result }), {
  status: 200,
  headers: {
    "Content-Type": "application/json",
  },
});

Created Response

typescript
return new Response(JSON.stringify({ id: newId, ...data }), {
  status: 201,
  headers: {
    "Content-Type": "application/json",
  },
});

Error Responses

typescript
// Bad Request (400)
return new Response(JSON.stringify({ error: "Invalid input" }), {
  status: 400,
  statusText: "Bad Request",
  headers: { "Content-Type": "application/json" },
});

// Not Found (404)
return new Response(JSON.stringify({ error: "Resource not found" }), {
  status: 404,
  statusText: "Not Found",
  headers: { "Content-Type": "application/json" },
});

// Internal Server Error (500)
return new Response(JSON.stringify({ error: "Internal server error" }), {
  status: 500,
  statusText: "Internal Server Error",
  headers: { "Content-Type": "application/json" },
});

Wix Data Integration

Use @wix/data SDK to interact with Wix Data collections:

typescript
import { items } from "@wix/data";

export async function GET({ params }) {
  const { id } = params;

  try {
    if (id) {
      // Get single item
      const item = await items.get("collectionId", id);
      return new Response(JSON.stringify(item), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    } else {
      // Get all items
      const results = await items.query("collectionId").limit(50).find();

      return new Response(JSON.stringify(results.items), {
        status: 200,
        headers: { "Content-Type": "application/json" },
      });
    }
  } catch (error) {
    return new Response(JSON.stringify({ error: "Failed to fetch data" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

export async function POST({ request }) {
  try {
    const body = await request.json();

    const newItem = await items.insert("collectionId", body);

    return new Response(JSON.stringify(newItem), {
      status: 201,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Failed to create item" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

export async function PUT({ params, request }) {
  const { id } = params;

  if (!id) {
    return new Response(JSON.stringify({ error: "ID required" }), {
      status: 400,
      statusText: "Bad Request",
      headers: { "Content-Type": "application/json" },
    });
  }

  try {
    const body = await request.json();

    const updatedItem = await items.update("collectionId", {
      _id: id,
      ...body,
    });

    return new Response(JSON.stringify(updatedItem), {
      status: 200,
      headers: { "Content-Type": "application/json" },
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Failed to update item" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

export async function DELETE({ params }) {
  const { id } = params;

  if (!id) {
    return new Response(JSON.stringify({ error: "ID required" }), {
      status: 400,
      statusText: "Bad Request",
      headers: { "Content-Type": "application/json" },
    });
  }

  try {
    await items.remove("collectionId", id);

    return new Response(null, {
      status: 204,
    });
  } catch (error) {
    return new Response(JSON.stringify({ error: "Failed to delete item" }), {
      status: 500,
      headers: { "Content-Type": "application/json" },
    });
  }
}

Query methods: eq, ne, gt, ge, lt, le, between, contains, startsWith, endsWith, hasSome, hasAll, isEmpty, isNotEmpty, and, or, not, ascending, descending, limit, skip, include

Output Structure

code
src/pages/api/
├── users.ts              # /api/users endpoint
├── users/
│   └── [id].ts          # /api/users/:id endpoint
└── posts.ts             # /api/posts endpoint

Examples

For additional examples, see references/backend-api-examples.md.

Extension Registration

Backend API endpoints do NOT require extension registration. They are automatically discovered from the src/pages/api/ directory structure.

Verification

After implementation, use wix-cli-app-validation to validate TypeScript compilation, build, preview, and runtime behavior.

Code Quality Requirements

  • Strict TypeScript (no any, explicit return types)
  • Import APIRoute type from 'astro' for proper typing
  • Always return Response objects
  • Use JSON.stringify() for JSON responses
  • Use proper HTTP status codes (200, 201, 400, 404, 500, etc.)
  • Include proper headers (Content-Type: application/json)
  • Include statusText in error responses for clarity
  • Handle errors gracefully with try/catch blocks
  • Validate input parameters and request bodies
  • Use async/await for asynchronous operations
  • Keep endpoints focused and single-purpose
  • No @ts-ignore comments