Token导航 LogoToken导航TokenDH.com
研究检索需要联网github未标认证来源可访问许可证需确认审计通过

api-restAPI rest 搜索

Agent Skill

用于辅助 API 设计、接口文档、请求响应结构和服务集成说明。它适合让 Agent 梳理 endpoint、生成 OpenAPI 草稿、检查字段命名、整理错误码或辅助前后端联调。使用时需要确认真实业务语义、鉴权方式、分页和错误处理规则;涉及生成接口文档时,应避免凭空补字段,最好从现有代码、schema 或接口样例中提取事实。

总安装

267

周安装

11

GitHub Stars

4

下载量

87
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:api-rest(API rest 搜索)
来源仓库:https://github.com/aussiegingersnap/cursor-skills
仓库路径:skills/api-rest
安装命令:
npx skills add https://github.com/aussiegingersnap/cursor-skills --skill api-rest
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/aussiegingersnap/cursor-skills --skill api-rest

简介

用于辅助 REST API 设计与开发,提供接口文档生成、请求响应结构梳理和前后端联调支持。

  • 适合梳理 endpoint、生成 OpenAPI 草稿、检查字段命名规范及错误码整理。
  • 使用时需确认业务语义、鉴权方式、分页规则和错误处理逻辑,避免凭空补字段。
  • 安装方式:通过 GitHub 仓库安装,命令为 npx skills add <repo> --skill api-rest。
  • 建议结合现有代码或 schema 提取事实,确保接口定义准确可靠。

SKILL.md

REST API Skill

Conventions and patterns for building REST APIs in Next.js App Router with type-safe validation and consistent error handling.

When to Use This Skill

  • Creating new API routes
  • Implementing CRUD operations
  • Setting up validation with Zod
  • Establishing error handling patterns
  • Designing resource endpoints

Core Conventions

Resource Naming

Resources are always plural:

ResourceEndpoint
Project/api/projects
User/api/users
Task/api/tasks

HTTP Methods

MethodPurposeExample
GETReadGET /api/projects
POSTCreatePOST /api/projects
PATCHPartial updatePATCH /api/projects/:id
PUTFull replacePUT /api/projects/:id
DELETERemoveDELETE /api/projects/:id

URL Structure

/api/{resource}           # Collection
/api/{resource}/{id}      # Single item
/api/{resource}/{id}/{sub-resource}  # Nested resource

Examples:

GET    /api/projects              # List all projects
POST   /api/projects              # Create project
GET    /api/projects/123          # Get project 123
PATCH  /api/projects/123          # Update project 123
DELETE /api/projects/123          # Delete project 123
GET    /api/projects/123/tasks    # List tasks for project 123

Directory Structure

src/app/api/
├── projects/
│   ├── route.ts              # GET (list), POST (create)
│   └── [id]/
│       ├── route.ts          # GET, PATCH, DELETE
│       └── tasks/
│           └── route.ts      # Nested resource
├── users/
│   ├── route.ts
│   └── [id]/
│       └── route.ts
└── _lib/
    ├── errors.ts             # Error utilities
    ├── validation.ts         # Zod helpers
    └── response.ts           # Response helpers

Response Format

Success Responses

// Single resource
{
  "data": {
    "id": "123",
    "name": "Project Alpha",
    "createdAt": "2025-01-15T10:30:00Z"
  }
}

// Collection
{
  "data": [
    { "id": "123", "name": "Project Alpha" },
    { "id": "124", "name": "Project Beta" }
  ],
  "meta": {
    "total": 42,
    "page": 1,
    "pageSize": 20
  }
}

// Empty success (204 No Content for DELETE)
// No body

Error Responses

{
  "error": {
    "code": "VALIDATION_ERROR",
    "message": "Invalid request body",
    "details": [
      { "field": "email", "message": "Invalid email format" }
    ]
  }
}

Setup

Error Utilities

Create src/app/api/_lib/errors.ts:

import { NextResponse } from 'next/server';
import { ZodError } from 'zod';

export type ApiErrorCode =
  | 'VALIDATION_ERROR'
  | 'NOT_FOUND'
  | 'UNAUTHORIZED'
  | 'FORBIDDEN'
  | 'CONFLICT'
  | 'INTERNAL_ERROR';

interface ApiError {
  code: ApiErrorCode;
  message: string;
  details?: unknown;
}

export function errorResponse(
  code: ApiErrorCode,
  message: string,
  status: number,
  details?: unknown
) {
  const error: ApiError = { code, message };
  if (details) error.details = details;

  return NextResponse.json({ error }, { status });
}

export function validationError(error: ZodError) {
  const details = error.errors.map((err) => ({
    field: err.path.join('.'),
    message: err.message,
  }));

  return errorResponse('VALIDATION_ERROR', 'Invalid request', 400, details);
}

export function notFound(resource: string) {
  return errorResponse('NOT_FOUND', `${resource} not found`, 404);
}

export function unauthorized(message = 'Unauthorized') {
  return errorResponse('UNAUTHORIZED', message, 401);
}

export function forbidden(message = 'Forbidden') {
  return errorResponse('FORBIDDEN', message, 403);
}

export function conflict(message: string) {
  return errorResponse('CONFLICT', message, 409);
}

export function internalError(message = 'Internal server error') {
  return errorResponse('INTERNAL_ERROR', message, 500);
}

Validation Helpers

Create src/app/api/_lib/validation.ts:

import { z, ZodSchema, ZodError } from 'zod';
import { NextRequest } from 'next/server';

export async function parseBody<T>(
  request: NextRequest,
  schema: ZodSchema<T>
): Promise<{ data: T; error: null } | { data: null; error: ZodError }> {
  try {
    const body = await request.json();
    const data = schema.parse(body);
    return { data, error: null };
  } catch (error) {
    if (error instanceof ZodError) {
      return { data: null, error };
    }
    throw error;
  }
}

export function parseQuery<T>(
  request: NextRequest,
  schema: ZodSchema<T>
): { data: T; error: null } | { data: null; error: ZodError } {
  try {
    const params = Object.fromEntries(request.nextUrl.searchParams.entries());
    const data = schema.parse(params);
    return { data, error: null };
  } catch (error) {
    if (error instanceof ZodError) {
      return { data: null, error };
    }
    throw error;
  }
}

// Common schemas
export const paginationSchema = z.object({
  page: z.coerce.number().int().positive().default(1),
  pageSize: z.coerce.number().int().positive().max(100).default(20),
});

export const idParamSchema = z.object({
  id: z.string().min(1),
});

Response Helpers

Create src/app/api/_lib/response.ts:

import { NextResponse } from 'next/server';

export function json<T>(data: T, status = 200) {
  return NextResponse.json({ data }, { status });
}

export function jsonList<T>(
  data: T[],
  meta: { total: number; page: number; pageSize: number }
) {
  return NextResponse.json({ data, meta });
}

export function created<T>(data: T) {
  return NextResponse.json({ data }, { status: 201 });
}

export function noContent() {
  return new NextResponse(null, { status: 204 });
}

Route Implementations

Collection Route (List + Create)

Create src/app/api/projects/route.ts:

import { NextRequest } from 'next/server';
import { z } from 'zod';
import { db, project } from '@/lib/db';
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { eq, desc, sql } from 'drizzle-orm';
import { json, jsonList, created } from '../_lib/response';
import { parseBody, parseQuery, paginationSchema } from '../_lib/validation';
import { validationError, unauthorized, internalError } from '../_lib/errors';

// Query params schema
const listQuerySchema = paginationSchema.extend({
  status: z.enum(['active', 'archived']).optional(),
  search: z.string().optional(),
});

// Create body schema
const createSchema = z.object({
  name: z.string().min(1).max(255),
  description: z.string().optional(),
});

// GET /api/projects
export async function GET(request: NextRequest) {
  try {
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) return unauthorized();

    const { data: query, error } = parseQuery(request, listQuerySchema);
    if (error) return validationError(error);

    const { page, pageSize, status, search } = query;
    const offset = (page - 1) * pageSize;

    // Build where conditions
    const conditions = [eq(project.userId, session.user.id)];
    if (status) conditions.push(eq(project.status, status));
    // Add search if needed

    const [items, countResult] = await Promise.all([
      db.query.project.findMany({
        where: and(...conditions),
        orderBy: desc(project.createdAt),
        limit: pageSize,
        offset,
      }),
      db.select({ count: sql<number>`count(*)` })
        .from(project)
        .where(and(...conditions)),
    ]);

    return jsonList(items, {
      total: Number(countResult[0]?.count ?? 0),
      page,
      pageSize,
    });
  } catch (error) {
    console.error('GET /api/projects error:', error);
    return internalError();
  }
}

// POST /api/projects
export async function POST(request: NextRequest) {
  try {
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) return unauthorized();

    const { data: body, error } = await parseBody(request, createSchema);
    if (error) return validationError(error);

    const [newProject] = await db.insert(project).values({
      ...body,
      userId: session.user.id,
    }).returning();

    return created(newProject);
  } catch (error) {
    console.error('POST /api/projects error:', error);
    return internalError();
  }
}

Item Route (Get + Update + Delete)

Create src/app/api/projects/[id]/route.ts:

import { NextRequest } from 'next/server';
import { z } from 'zod';
import { db, project } from '@/lib/db';
import { auth } from '@/lib/auth';
import { headers } from 'next/headers';
import { eq, and } from 'drizzle-orm';
import { json, noContent } from '../../_lib/response';
import { parseBody } from '../../_lib/validation';
import { validationError, unauthorized, notFound, forbidden, internalError } from '../../_lib/errors';

interface RouteParams {
  params: Promise<{ id: string }>;
}

const updateSchema = z.object({
  name: z.string().min(1).max(255).optional(),
  description: z.string().optional(),
  status: z.enum(['active', 'archived']).optional(),
});

// GET /api/projects/:id
export async function GET(request: NextRequest, { params }: RouteParams) {
  try {
    const { id } = await params;
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) return unauthorized();

    const item = await db.query.project.findFirst({
      where: eq(project.id, id),
    });

    if (!item) return notFound('Project');
    if (item.userId !== session.user.id) return forbidden();

    return json(item);
  } catch (error) {
    console.error('GET /api/projects/:id error:', error);
    return internalError();
  }
}

// PATCH /api/projects/:id
export async function PATCH(request: NextRequest, { params }: RouteParams) {
  try {
    const { id } = await params;
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) return unauthorized();

    const { data: body, error } = await parseBody(request, updateSchema);
    if (error) return validationError(error);

    // Check ownership
    const existing = await db.query.project.findFirst({
      where: eq(project.id, id),
    });

    if (!existing) return notFound('Project');
    if (existing.userId !== session.user.id) return forbidden();

    const [updated] = await db.update(project)
      .set({ ...body, updatedAt: new Date() })
      .where(eq(project.id, id))
      .returning();

    return json(updated);
  } catch (error) {
    console.error('PATCH /api/projects/:id error:', error);
    return internalError();
  }
}

// DELETE /api/projects/:id
export async function DELETE(request: NextRequest, { params }: RouteParams) {
  try {
    const { id } = await params;
    const session = await auth.api.getSession({ headers: await headers() });
    if (!session) return unauthorized();

    // Check ownership
    const existing = await db.query.project.findFirst({
      where: eq(project.id, id),
    });

    if (!existing) return notFound('Project');
    if (existing.userId !== session.user.id) return forbidden();

    await db.delete(project).where(eq(project.id, id));

    return noContent();
  } catch (error) {
    console.error('DELETE /api/projects/:id error:', error);
    return internalError();
  }
}

Query Parameters

Filtering

GET /api/projects?status=active&priority=high
const filterSchema = z.object({
  status: z.enum(['active', 'archived']).optional(),
  priority: z.enum(['low', 'medium', 'high']).optional(),
});

Sorting

GET /api/projects?sort=createdAt&order=desc
const sortSchema = z.object({
  sort: z.enum(['createdAt', 'name', 'updatedAt']).default('createdAt'),
  order: z.enum(['asc', 'desc']).default('desc'),
});

Search

GET /api/projects?search=alpha
// In query
if (search) {
  conditions.push(
    or(
      ilike(project.name, `%${search}%`),
      ilike(project.description, `%${search}%`)
    )
  );
}

Pagination

GET /api/projects?page=2&pageSize=20

Response includes meta:

{
  "data": [...],
  "meta": {
    "total": 42,
    "page": 2,
    "pageSize": 20
  }
}

HTTP Status Codes

CodeMeaningUsage
200OKSuccessful GET, PATCH, PUT
201CreatedSuccessful POST
204No ContentSuccessful DELETE
400Bad RequestValidation error
401UnauthorizedNo/invalid auth
403ForbiddenNo permission
404Not FoundResource doesn't exist
409ConflictDuplicate, constraint violation
500Internal ErrorUnexpected server error

Best Practices

Do

  • Use plural resource names (/projects not /project)
  • Return consistent response shapes
  • Validate all inputs with Zod
  • Use appropriate HTTP methods and status codes
  • Include pagination for list endpoints
  • Log errors server-side

Don't

  • Don't use verbs in URLs (/getProjects ❌)
  • Don't return different shapes for same endpoint
  • Don't expose internal error details to clients
  • Don't skip authentication checks
  • Don't use GET for mutations

Type Safety

Export types from your schemas:

// In a shared types file
import { z } from 'zod';

export const createProjectSchema = z.object({
  name: z.string().min(1).max(255),
  description: z.string().optional(),
});

export type CreateProjectInput = z.infer<typeof createProjectSchema>;

Use in frontend:

import type { CreateProjectInput } from '@/lib/api-types';

async function createProject(data: CreateProjectInput) {
  const res = await fetch('/api/projects', {
    method: 'POST',
    headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify(data),
  });
  // ...
}

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Codex

32%
按下载量换算28

Claude

29.53%
按下载量换算26

Cursor

20.33%
按下载量换算18

Gemini CLI

8.78%
按下载量换算8

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills